From bc2e3fce2d2705b86073acea19865bec4e4d59ab Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 11:45:36 -0700 Subject: [PATCH 001/234] refactor: reinitialize project using the fission rust template --- .cargo/config | 2 - .envrc | 2 +- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 23 +- .github/dependabot.yml | 17 +- .github/workflows/audit.yml | 17 + .github/workflows/bench.yml | 58 +++ .../workflows/{coverage.yaml => coverage.yml} | 17 +- .github/workflows/nix_build.yml | 38 -- .../workflows/{release.yaml => release.yml} | 25 +- ...n_test_suite.yaml => tests_and_checks.yml} | 62 +-- .gitignore | 20 +- .pre-commit-config.yaml | 22 +- .release-please-manifest.json | 3 +- CODE_OF_CONDUCT.md | 108 +++++ CONTRIBUTING.md | 151 ++++++ Cargo.toml | 66 ++- LICENSE | 2 +- README.md | 124 +++-- SECURITY.md | 17 + assets/a_logo.png | Bin 0 -> 19259 bytes assets/logo.png | Bin 63496 -> 0 bytes benches/a_benchmark.rs | 16 + codecov.yaml => codecov.yml | 5 +- deny.toml | 200 ++++++++ examples/counterparts.rs | 6 + flake.lock | 18 +- flake.nix | 141 +----- release-please-config.json | 5 +- src/lib.rs | 30 ++ src/test_utils/mod.rs | 5 + src/test_utils/rvg.rs | 63 +++ tests/integration_test.rs | 4 + ucan-key-support/CHANGELOG.md | 67 --- ucan-key-support/Cargo.toml | 62 --- ucan-key-support/README.md | 36 -- ucan-key-support/src/ed25519.rs | 92 ---- ucan-key-support/src/fixtures/rsa_key.pk8 | Bin 1216 -> 0 bytes ucan-key-support/src/lib.rs | 9 - ucan-key-support/src/p256.rs | 86 ---- ucan-key-support/src/rsa.rs | 114 ----- ucan-key-support/src/web_crypto.rs | 261 ---------- ucan-key-support/webdriver.json | 6 - ucan/CHANGELOG.md | 84 ---- ucan/Cargo.toml | 52 -- ucan/LICENSE | 201 -------- ucan/README.md | 39 -- ucan/src/builder.rs | 310 ------------ ucan/src/capability/caveats.rs | 86 ---- ucan/src/capability/data.rs | 229 --------- ucan/src/capability/mod.rs | 9 - ucan/src/capability/proof.rs | 79 ---- ucan/src/capability/semantics.rs | 301 ------------ ucan/src/chain.rs | 289 ----------- ucan/src/crypto/did.rs | 67 --- ucan/src/crypto/key.rs | 79 ---- ucan/src/crypto/mod.rs | 6 - ucan/src/crypto/signature.rs | 12 - ucan/src/ipld/mod.rs | 7 - ucan/src/ipld/principle.rs | 66 --- ucan/src/ipld/signature.rs | 193 -------- ucan/src/ipld/ucan.rs | 201 -------- ucan/src/lib.rs | 92 ---- ucan/src/serde.rs | 46 -- ucan/src/store.rs | 130 ----- ucan/src/tests/attenuation.rs | 447 ------------------ ucan/src/tests/builder.rs | 205 -------- ucan/src/tests/capability.rs | 91 ---- ucan/src/tests/chain.rs | 254 ---------- ucan/src/tests/crypto.rs | 27 -- ucan/src/tests/fixtures/capabilities/email.rs | 63 --- ucan/src/tests/fixtures/capabilities/mod.rs | 5 - ucan/src/tests/fixtures/capabilities/wnfs.rs | 96 ---- ucan/src/tests/fixtures/crypto.rs | 39 -- ucan/src/tests/fixtures/identities.rs | 60 --- ucan/src/tests/fixtures/mod.rs | 9 - ucan/src/tests/fixtures/store.rs | 48 -- ucan/src/tests/helpers.rs | 56 --- ucan/src/tests/mod.rs | 8 - ucan/src/tests/ucan.rs | 235 --------- ucan/src/time.rs | 8 - ucan/src/ucan.rs | 270 ----------- 82 files changed, 931 insertions(+), 5570 deletions(-) delete mode 100644 .cargo/config create mode 100644 .github/workflows/audit.yml create mode 100644 .github/workflows/bench.yml rename .github/workflows/{coverage.yaml => coverage.yml} (80%) delete mode 100644 .github/workflows/nix_build.yml rename .github/workflows/{release.yaml => release.yml} (68%) rename .github/workflows/{run_test_suite.yaml => tests_and_checks.yml} (52%) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 assets/a_logo.png delete mode 100644 assets/logo.png create mode 100644 benches/a_benchmark.rs rename codecov.yaml => codecov.yml (75%) create mode 100644 deny.toml create mode 100644 examples/counterparts.rs create mode 100644 src/lib.rs create mode 100644 src/test_utils/mod.rs create mode 100644 src/test_utils/rvg.rs create mode 100644 tests/integration_test.rs delete mode 100644 ucan-key-support/CHANGELOG.md delete mode 100644 ucan-key-support/Cargo.toml delete mode 100644 ucan-key-support/README.md delete mode 100644 ucan-key-support/src/ed25519.rs delete mode 100644 ucan-key-support/src/fixtures/rsa_key.pk8 delete mode 100644 ucan-key-support/src/lib.rs delete mode 100644 ucan-key-support/src/p256.rs delete mode 100644 ucan-key-support/src/rsa.rs delete mode 100644 ucan-key-support/src/web_crypto.rs delete mode 100644 ucan-key-support/webdriver.json delete mode 100644 ucan/CHANGELOG.md delete mode 100644 ucan/Cargo.toml delete mode 100644 ucan/LICENSE delete mode 100644 ucan/README.md delete mode 100644 ucan/src/builder.rs delete mode 100644 ucan/src/capability/caveats.rs delete mode 100644 ucan/src/capability/data.rs delete mode 100644 ucan/src/capability/mod.rs delete mode 100644 ucan/src/capability/proof.rs delete mode 100644 ucan/src/capability/semantics.rs delete mode 100644 ucan/src/chain.rs delete mode 100644 ucan/src/crypto/did.rs delete mode 100644 ucan/src/crypto/key.rs delete mode 100644 ucan/src/crypto/mod.rs delete mode 100644 ucan/src/crypto/signature.rs delete mode 100644 ucan/src/ipld/mod.rs delete mode 100644 ucan/src/ipld/principle.rs delete mode 100644 ucan/src/ipld/signature.rs delete mode 100644 ucan/src/ipld/ucan.rs delete mode 100644 ucan/src/lib.rs delete mode 100644 ucan/src/serde.rs delete mode 100644 ucan/src/store.rs delete mode 100644 ucan/src/tests/attenuation.rs delete mode 100644 ucan/src/tests/builder.rs delete mode 100644 ucan/src/tests/capability.rs delete mode 100644 ucan/src/tests/chain.rs delete mode 100644 ucan/src/tests/crypto.rs delete mode 100644 ucan/src/tests/fixtures/capabilities/email.rs delete mode 100644 ucan/src/tests/fixtures/capabilities/mod.rs delete mode 100644 ucan/src/tests/fixtures/capabilities/wnfs.rs delete mode 100644 ucan/src/tests/fixtures/crypto.rs delete mode 100644 ucan/src/tests/fixtures/identities.rs delete mode 100644 ucan/src/tests/fixtures/mod.rs delete mode 100644 ucan/src/tests/fixtures/store.rs delete mode 100644 ucan/src/tests/helpers.rs delete mode 100644 ucan/src/tests/mod.rs delete mode 100644 ucan/src/tests/ucan.rs delete mode 100644 ucan/src/time.rs delete mode 100644 ucan/src/ucan.rs diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 437de666..00000000 --- a/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(target_arch="wasm32")'] -runner = "wasm-bindgen-test-runner" diff --git a/.envrc b/.envrc index 3550a30f..c4b17d79 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake +use_flake diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 63541e1f..fcc837a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Default -* @cdata @ucan-wg/fission +* @ucan-wg diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8711a73a..e667a230 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,9 +30,10 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: -1. Use function '...' -1. Run command '...' -2. See error +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error **Expected behavior** @@ -42,12 +43,18 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. -**Environment (please fill in relevant information):** +**Desktop (please complete the following information):** - - OS: [e.g. macOS] - - Version [e.g. 13.3.1] - - Browser: [e.g. Chrome 113.0.5672.126, Firefox 113.01] - - Rust Version [1.68.2] + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] **Additional context** diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b278a820..d4b1e629 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,26 +7,13 @@ version: 2 updates: - package-ecosystem: "cargo" - directory: "/ucan" - commit-message: - prefix: "chore" - include: "scope" - target-branch: "main" - schedule: - interval: "weekly" - labels: - - "chore" - - - package-ecosystem: "cargo" - directory: "/ucan-key-support" + directory: "/" commit-message: prefix: "chore" include: "scope" target-branch: "main" schedule: interval: "weekly" - labels: - - "chore" - package-ecosystem: "github-actions" directory: "/" @@ -36,5 +23,3 @@ updates: target-branch: "main" schedule: interval: "weekly" - labels: - - "chore" diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000..24eeb6e0 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,17 @@ +name: 🛡 Audit-Check + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + security-audit: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Run Audit-Check + uses: rustsec/audit-check@v1.3.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 00000000..76052f66 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,58 @@ +name: 📈 Benchmark + +on: + push: + branches: [ main ] + + pull_request: + branches: [ '**' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + benchmark: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Install Rust Toolchain + uses: actions-rs/toolchain@v1 + with: + override: true + toolchain: stable + + - name: Cache Project + uses: Swatinem/rust-cache@v2 + + - name: Run Benchmark + run: cargo bench --features test_utils -- --output-format bencher | tee output.txt + + - name: Upload Benchmark Result Artifact + uses: actions/upload-artifact@v3 + with: + name: bench_result + path: output.txt + + - name: Create gh-pages Branch + uses: peterjgrainger/action-create-branch@v2.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + branch: gh-pages + + - name: Store Benchmark Result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@ucan-wg' diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yml similarity index 80% rename from .github/workflows/coverage.yaml rename to .github/workflows/coverage.yml index a9ad95be..bbcf0c75 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yml @@ -5,7 +5,11 @@ on: branches: [ main ] pull_request: - branches: [ '*' ] + branches: [ '**' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: coverage: @@ -13,7 +17,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust Toolchain uses: actions-rs/toolchain@v1 @@ -30,9 +34,9 @@ jobs: env: CARGO_INCREMENTAL: '0' LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - run: cargo test --all + RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' + RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' + run: cargo test --all-features - name: Install grcov run: "curl -L https://github.com/mozilla/grcov/releases/download/v0.8.12/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf -" @@ -51,7 +55,8 @@ jobs: - name: Upload to codecov.io uses: codecov/codecov-action@v3 + continue-on-error: true with: token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + fail_ci_if_error: false files: lcov.info diff --git a/.github/workflows/nix_build.yml b/.github/workflows/nix_build.yml deleted file mode 100644 index 23f2cf5a..00000000 --- a/.github/workflows/nix_build.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: 📦 Nix Build - -on: - push: - branches: [ main ] - - pull_request: - branches: [ '**' ] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - run-checks: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v4 - - - name: Cache Magic - uses: DeterminateSystems/magic-nix-cache-action@v2 - - - name: Check Nix flake inputs - uses: DeterminateSystems/flake-checker-action@v5 - with: - ignore-missing-flake-lock: false - fail-mode: true - - - name: Nix Build - run: | - nix develop --show-trace -c irust --version - nix develop --show-trace -c rustc --version diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yml similarity index 68% rename from .github/workflows/release.yaml rename to .github/workflows/release.yml index 63a74b08..58388fb1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' outputs: - releases_created: ${{ steps.release.outputs['ucan--release_created'] || steps.release.outputs['ucan-key-support--release_created'] }} + release_created: ${{ steps.release.outputs.release_created }} steps: - name: Run release-please @@ -45,13 +45,12 @@ jobs: permissions: contents: write - pull-requests: write - if: ${{ needs.release-please.outputs.releases_created || github.event.inputs.force-publish }} - steps: + if: ${{ needs.release-please.outputs.release_created || github.event.inputs.force-publish }} + steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache Project uses: Swatinem/rust-cache@v2 @@ -63,20 +62,12 @@ jobs: profile: minimal toolchain: stable - - name: Install Cargo Workspaces - env: - RUSTFLAGS: '-Copt-level=1' - uses: actions-rs/cargo@v1 - with: - args: --force cargo-workspaces - command: install - - name: Verify Publishing of crate - uses: katyo/publish-crates@v1 + uses: katyo/publish-crates@v2 with: dry-run: true - name: Cargo Publish to crates.io - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo workspaces publish --from-git --allow-dirty + uses: katyo/publish-crates@v2 + with: + registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/run_test_suite.yaml b/.github/workflows/tests_and_checks.yml similarity index 52% rename from .github/workflows/run_test_suite.yaml rename to .github/workflows/tests_and_checks.yml index 16e20c77..05357ca4 100644 --- a/.github/workflows/run_test_suite.yaml +++ b/.github/workflows/tests_and_checks.yml @@ -5,7 +5,7 @@ on: branches: [ main ] pull_request: - branches: [ '*' ] + branches: [ '**' ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,38 +20,58 @@ jobs: rust-toolchain: - stable - nightly + # minimum version + - 1.67 steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Smarter caching action, speeds up build times compared to regular cache: # https://github.com/Swatinem/rust-cache - name: Cache Project uses: Swatinem/rust-cache@v2 + # Widely adopted suite of Rust-specific boilerplate actions, especially + # toolchain/cargo use: https://actions-rs.github.io/ - name: Install Rust Toolchain - uses: dtolnay/rust-toolchain@master + uses: actions-rs/toolchain@v1 with: + override: true + components: rustfmt, clippy toolchain: ${{ matrix.rust-toolchain }} - components: clippy, rustfmt - name: Check Format - run: cargo +${{ matrix.rust-toolchain }} fmt --all -- --check + uses: actions-rs/cargo@v1 + with: + args: --all -- --check + command: fmt + toolchain: ${{ matrix.rust-toolchain }} - name: Run Linter - run: cargo +${{ matrix.rust-toolchain }} clippy --all -- -D warnings + uses: actions-rs/cargo@v1 + with: + args: --all -- -D warnings + command: clippy + toolchain: ${{ matrix.rust-toolchain }} - - name: Install Cargo Audit + # Check for security advisories + - name: Check Advisories if: ${{ matrix.rust-toolchain == 'stable' }} - run: cargo install --force cargo-audit + uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check advisories + continue-on-error: true - - name: Run Audit on Deps + # Audit licenses, unreleased crates, and unexpected duplicate versions. + - name: Check Bans, Licenses, and Sources if: ${{ matrix.rust-toolchain == 'stable' }} - run: cargo-audit audit + uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check bans licenses sources # Only "test" release build on push event. - name: Test Release - if: ${{ matrix.rust-toolchain == 'stable' && github.event_name == 'push' }} + if: ${{ matrix.rust-toolchain == 'stable' && github.event_name == 'push' }} run: cargo build --release run-tests: @@ -64,7 +84,7 @@ jobs: - nightly steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Environment Packages run: | @@ -75,22 +95,10 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Install Rust Toolchain - uses: dtolnay/rust-toolchain@master + uses: actions-rs/toolchain@v1 with: + override: true toolchain: ${{ matrix.rust-toolchain }} - name: Run Tests - run: cargo test --all - - - name: Install Rust/WASM Test Dependencies - run: | - rustup target install wasm32-unknown-unknown - cargo install toml-cli - WASM_BINDGEN_VERSION=`toml get ./Cargo.lock . | jq '.package | map(select(.name == "wasm-bindgen"))[0].version' | xargs echo` - cargo install wasm-bindgen-cli --vers "$WASM_BINDGEN_VERSION" - - - name: Setup Chrome and Chromedriver - uses: nanasess/setup-chromedriver@v2 - - - name: Run Rust Headless Browser Tests - run: CHROMEDRIVER=/usr/local/bin/chromedriver cargo test --target wasm32-unknown-unknown + run: cargo test --all-features diff --git a/.gitignore b/.gitignore index c9131f0a..ff787987 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ Cargo.lock -**/target/ - -**/.DS_Store -README.html +# These are backup files generated by rustfmt +**/*.rs.bk +# Ignore local environment settings +.envrc.custom .direnv -dist -bundle -lib +# Other files + dirs +private +*.temp +*.tmp +.history +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14f2c3fb..5145061a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,17 @@ # See https://pre-commit.com for more information +# pre-commit install +# pre-commit install --hook-type commit-msg +exclude: ^(LICENSE|LICENSE*) repos: - repo: local hooks: - # allow for crate import granularity: - # https://github.com/rust-lang/rustfmt/issues/4991 - id: fmt name: fmt description: Format rust files. - entry: cargo +nightly fmt + entry: cargo fmt language: system types: [rust] - args: ["--all", "--", "--check"] + args: ["--", "--check"] - id: cargo-check name: cargo check description: Check the package for errors. @@ -23,15 +24,21 @@ repos: description: Lint via clippy entry: cargo clippy language: system - args: ["--workspace", "--", "-D", "warnings"] + args: ["--", "-D", "warnings"] types: [rust] pass_filenames: false + - id: alejandra + name: alejandra + description: Format nix files. + entry: alejandra + files: \.nix$ + language: system - repo: https://github.com/DevinR528/cargo-sort rev: v1.0.9 hooks: - id: cargo-sort - args: ["--workspace"] + args: [] - repo: https://github.com/compilerla/conventional-pre-commit rev: v2.1.1 @@ -49,11 +56,8 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - exclude: ^catalog-info.yaml - id: check-json - exclude: ^tests/data/ - id: check-added-large-files - id: detect-private-key - exclude: ^tests/data/ - id: check-executables-have-shebangs - id: check-toml diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0be28311..466df71c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,3 @@ { - "ucan": "0.4.0", - "ucan-key-support": "0.1.7" + ".": "0.1.0" } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..428e8687 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,108 @@ +# Code of Conduct + +**TL;DR Be kind, inclusive, and considerate.** + +In the interest of fostering an open, inclusive, and welcoming environment, all +members, contributors, and maintainers interacting within our online community +(including Discord, Discourse, etc.), on affiliated projects and repositories +(including issues, pull requests, and discussions on Github), and/or involved +with associated events pledge to accept and observe the following Code of +Conduct. + +As members, contributors, and maintainers, we pledge to make participation in +our projects and community a harassment-free experience, ensuring a safe +environment for all, regardless of background, gender, gender identity and +expression, age, sexual orientation, disability, physical appearance, body size, +race, ethnicity, religion (or lack thereof), or any other dimension of +diversity. + +Sexual language and imagery will not be accepted in any way. Be kind to others. +Do not insult or put down people within the community. Behave professionally. +Remember that harassment and sexist, racist, or exclusionary jokes are not +appropriate in any form. Participants violating these rules may be sanctioned or +expelled from the community and related projects. + +## Spelling it out. + +Harassment includes offensive verbal comments or actions related to or involving + +- background +- gender +- gender identity and expression +- age +- sexual orientation +- disability +- physical appearance +- body size +- race +- ethnicity +- religion (or lack thereof) +- economic status +- geographic location +- technology choices +- sexual imagery +- deliberate intimidation +- violence and threats of violence +- stalking +- doxing +- inappropriate or unwelcome physical contact in public spaces +- unwelcomed sexual attention +- influencing unacceptable behavior +- any other dimension of diversity + +## Our Responsibilities + +Maintainers of the community and associated projects are not only subject to the +anti-harassment policy, but also responsible for executing the policy, +moderating related forums, and for taking appropriate and fair corrective action +in response to any instances of unacceptable behavior that breach the policy. + +Maintainers have the right to remove and reject comments, threads, commits, +code, documentation, pull requests, issues, and contributions not aligned with +this Code of Conduct. + +## Scope + +This Code of Conduct applies within all project and community spaces, as well as +in any public spaces where an individual representing the community is involved. +This covers + +- Interactions on the Github repository, including discussions, issues, pull + requests, commits, and wikis +- Interactions on any affiliated Discord, Slack, IRC, or related online + communities and forums like Discourse, etc. +- Any official project emails and social media posts +- Individuals representing the community at public events like meetups, talks, + and presentations + +## Enforcement + +All instances of abusive, harassing, or otherwise unacceptable behavior should +be reported by contacting the project and community maintainers at +[quinn@fission.codes][support-email]. All complaints will be reviewed and +investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. + +Maintainers of the community and associated projects are obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Anyone asked to stop abusive, harassing, or otherwise unacceptable behavior are +expected to comply immediately and accept the response decided on by the +maintainers of the community and associated projects. + +## Need help? + +If you are experiencing harassment, witness an incident or have concerns about +content please contact us at [quinn@fission.codes][support-email]. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant, v2.1][contributor-cov], +among other sources like [!!con’s Code of Conduct][!!con] and +[Mozilla’s Community Participation Guidelines][mozilla]. + +[!!con]: https://bangbangcon.com/conduct.html +[contributor-cov]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ +[mozilla]: https://www.mozilla.org/en-US/about/governance/policies/participation/ +[support-email]: mailto:quinn@fission.codes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2c6d8dcf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ +# Contributing to rs-ucan + +We welcome everyone to contribute what and where they can. Whether you are brand +new, just want to contribute a little bit, or want to contribute a lot there is +probably something you can help out with. Check out our +[good first issues][good-first-issues] label for in the issues tab to see a list +of issue that good for those new to the project. + +## Where to Get Help + +The main way to get help is on our [discord server](https://discord.gg/4UdeQhw7fv). +Though, this guide should help you get started. It may be slightly lengthy, but it's +designed for those who are new so please don't let length intimidate you. + +## Code of Conduct + +Please be kind, inclusive, and considerate when interacting when interacting +with others and follow our [code of conduct](./CODE_OF_CONDUCT.md). + +## How to Contribute + +If the code adds a feature that is not already present in an issue, you can +create a new issue for the feature and add the pull request to it. If the code +adds a feature that is not already present in an issue, you can create a new +issue for the feature and add the pull request to it. + +### Contributing by Adding a Topic for Discussion + +#### Issues + +If you have found a bug and would like to report it or if you have a feature +that you feel we should add, then we'd love it if you opened an issue! ❤️ +Before you do, please search the other issues to avoid creating a duplicate +issue. + +To submit a new issue just hit the issue button and a choice between two +templates should appear. Then, follow along with the template you chose. If you +don't know how to fill in all parts of the template go ahead and skip those +parts. You can edit the issue later. + +#### Discussion + +If you have a new discussion you want to start but it isn't a bug or feature +add, then you can start a [GitHub discussion][gh-discussions]. Some examples of +what kinds of things that are good discussion topics can include, but are not +limited to the following: + +- Community announcements and/or asking the community for feedback +- Discussing a new release +- Asking questions, Q&A that isn't for sure a bug report + +### Contributing through Code + +In order to contribute through code follow the steps below. Note that you don't +need to be the best programmer to contribute. Our discord is open for questions. + + 1. **Pick a feature** you would like to add or a bug you would like to fix + - If you wish to contribute but what you want to fix/add is not already + covered in an existing issue, please open a new issue. + + 2. **Discuss** the issue with the rest of the community + - Before you write any code, it is recommended that you discuss your + intention to write the code on the issue you are attempting to edit. + - This helps to stop you from wasting your time duplicating the work of + others that maybe working on the same issue; at the same time. + - This step also allows you to get helpful pointers on the community on some + problems they may have encountered on similar issues. + + 3. **Fork** the repository + - A fork creates a copy of the code on your Github, so you can work on it + separately from everyone else. + - You can learn more about forking [here][forking]. + + 4. Ensure that you have **commit signing** enabled + - This ensures that the code you submit was committed by you and not someone + else who claims to be you. + - You can learn more about how to setup commit signing [here][commit-signing]. + - If you have already made some commits that you wish to put in a pull + request without signing them, then you can follow [this guide][post-signing] + on how to fix that. + + 5. **Clone** the repository to your local computer + - This puts a copy of your fork on your computer so you can edit it + - You can learn more about cloning repositories [here][git-clone]. + + 6. **Build** the project + - For a detailed look on how to build rs-ucan look at our + [README file](./README.md). + + 7. **Start writing** your code + - Open up your favorite code editor and make the changes that you wanted to + make to the repository. + - Make sure to test your code with the test command(s) found in our + [README file](./README.md). + + 8. **Write tests** for your code + - If you are adding a new feature, you should write tests that ensure that + if someone make changes to the code it cannot break your new feature + without breaking the test. + - If your code adds a new feature, you should also write at least one + documentation test. The documentation test's purpose is to demonstrate and + document how to use the API feature. + - If your code fixes a bug, you should write tests that ensure that if + someone makes code changes in the future the bug does not re-emerge + without breaking test. + - Please create integration tests, if the addition is large enough to + warrant them, and unit tests. + * Unit tests are tests that ensure the functionality of a single + function or small section of code. + * Integration tests test large large sections of code. + * Read more about the differences [here][unit-and-integration]. + - For more information on test organization, take a look [here][test-org]. + + 9. Ensure that the code that you made follows our Rust **coding guidelines** + - You can find a list of some Rust guidelines [here][rust-style-guide]. This + is a courtesy to the programmers that come after you. The easier your code + is to read, the easier it will be for the next person to make modifications. + - If you find it difficult to follow the guidelines or if the guidelines or + unclear, please reach out to us through our discord linked above, or you + can just continue and leave a comment at the pull request stage. + + 10. **Commit and Push** your code + - This sends your changes to your repository branch. + - You can learn more about committing code [here][commiting-code] and + pushing it to a remote repository [here][push-remote]. + - We use conventional commits for the names and description of commits. + You can find out more about them [here][conventional-commits]. + + 11. The final step is to create **pull request** to our main branch 🎉 + - A pull request is how you merge the code you just worked so hard on with + the code everyone else has access to. + - Once you have submitted your pull request, we will review your code and + check to make sure the code implements the feature or fixes the bug. We + may leave some feedback and suggest edits. You can make the changes we + suggest by committing more code to your fork. + - You can learn more about pull requests [here][prs]. + + +[conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ +[commiting-code]: https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project +[commit-signing]: https://www.freecodecamp.org/news/what-is-commit-signing-in-git/ +[forking]: https://docs.github.com/en/get-started/quickstart/fork-a-repo +[gh-discussions]: https://docs.github.com/en/discussions +[git-clone]: https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository +[good-first-issues]: [https://build.prestashop-project.org/news/a-definition-of-the-good-first-issue-label/] +[post-signing]: https://dev.to/jmarhee/signing-existing-commits-with-gpg-5b58 +[prs]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests +[push-remote]: https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository +[rust-style-guide]: https://rust-lang.github.io/api-guidelines/about.html +[test-org]: https://doc.rust-lang.org/book/ch11-03-test-organization.html +[unit-and-integration]: https://www.geeksforgeeks.org/difference-between-unit-testing-and-integration-testing/ diff --git a/Cargo.toml b/Cargo.toml index 6b73d443..2a46729a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,64 @@ -[workspace] -members = [ - "ucan", - "ucan-key-support", -] +[package] +name = "rs-ucan" +version = "0.1.0" +description = "Rust implementation of UCAN" +keywords = [] +categories = [] +include = ["/src", "/examples", "/benches", "README.md", "LICENSE"] +license = "Apache-2.0" +readme = "README.md" +edition = "2021" +rust-version = "1.67" +documentation = "https://docs.rs/rs-ucan" +repository = "https://github.com/ucan-wg/rs-ucan" +authors = ["Quinn Wilton "] -resolver = "2" +[lib] +path = "src/lib.rs" +bench = false + +[[bench]] +name = "a_benchmark" +harness = false +required-features = ["test_utils"] + +[[example]] +name = "counterparts" +path = "examples/counterparts.rs" + +[dependencies] +proptest = { version = "1.1", optional = true } +thiserror = "1.0" +tracing = "0.1" + +[dev-dependencies] +criterion = "0.4" +proptest = "1.1" + +[features] +default = [] +test_utils = ["proptest"] + +[metadata.docs.rs] +all-features = true +# defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] +# +# See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. +# [profile.release] +# Do not perform backtrace for panic on release builds. +## panic = 'abort' +# Perform optimizations on all codegen units. +## codegen-units = 1 +# Tell `rustc` to optimize for small code size. +## opt-level = "s" # or 'z' to optimize "aggressively" for size +# Enable link time optimization. +## lto = true +# Amount of debug information. +# 0/false: no debug info at all; 1: line tables only; 2/true: full debug info +## debug = false +# Strip debug symbols +## strip = "symbols" # Speedup build on macOS # See https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#splitting-debug-information diff --git a/LICENSE b/LICENSE index 261eeb9e..aaa61a98 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2023 Ucan Wg Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index dd70d2c9..c36d8842 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,116 @@
- rs-ucan Logo + rs-ucan Logo

rs-ucan

- UCAN - - Crate Information + + Crate - Code Coverage + Code Coverage - Build Status + Build Status License - + Docs - + Discord

-This is a Rust library to help the next generation of web applications make use -of UCANs in their authorization flows. To learn more about UCANs and how you -might use them in your application, visit [https://ucan.xyz][ucan website] or -read the [spec][spec]. +
:warning: Work in progress :warning:
+ +## ## Outline -- [Crates](#crates) -- [Building the Project](#building-the-project) +- [Usage](#usage) - [Testing the Project](#testing-the-project) +- [Benchmarking the Project](#benchmarking-the-project) - [Contributing](#contributing) - [Getting Help](#getting-help) +- [External Resources](#external-resources) - [License](#license) -## Crates +## Usage + +Add the following to the `[dependencies]` section of your `Cargo.toml` file: -- [ucan](https://github.com/ucan-wg/rs-ucan/tree/main/ucan) -- [ucan-key-support](https://github.com/ucan-wg/rs-ucan/tree/main/ucan-key-support) +```toml +rs-ucan = "0.1.0" +``` -## Building the Project +## Testing the Project -- Clone the repository. +- Run tests - ```bash - git clone https://github.com/ucan-wg/rs-ucan.git + ```console + cargo test ``` -- Change directory +## Benchmarking the Project - ```bash - cd rs-ucan - ``` +For benchmarking and measuring performance, this project leverages +[criterion][criterion] and a `test_utils` feature flag +for integrating [proptest][proptest] within the the suite for working with +[strategies][strategies] and sampling from randomly generated values. -- Build the project +- Run benchmarks - ```bash - cargo build + ```console + cargo bench --features test_utils ``` -## Testing the Project +## Contributing -- Run tests +:balloon: We're thankful for any feedback and help in improving our project! +We have a [contributing guide](./CONTRIBUTING.md) to help you get involved. We +also adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md). - ```bash - cargo test - ``` +### Nix -## Contributing +This repository contains a [Nix flake][nix-flake] that initiates both the Rust +toolchain set in [rust-toolchain.toml](./rust-toolchain.toml) and a +[pre-commit hook](#pre-commit-hook). It also installs helpful cargo binaries for +development. Please install [nix][nix] and [direnv][direnv] to get started. + +Run `nix develop` or `direnv allow` to load the `devShell` flake output, +according to your preference. + +### Formatting + +For formatting Rust in particular, we automatically format on `nightly`, as it +uses specific nightly features we recommend by default. ### Pre-commit Hook -This library recommends using [pre-commit][pre-commit] for running pre-commit +This project recommends using [pre-commit][pre-commit] for running pre-commit hooks. Please run this before every commit and/or push. -- Once installed, Run `pre-commit install` to setup the pre-commit hooks - locally. This will reduce failed CI builds. - If you are doing interim commits locally, and for some reason if you _don't_ want pre-commit hooks to fire, you can run `git commit -a -m "Your message here" --no-verify`. +### Recommended Development Flow + +- We recommend leveraging [cargo-watch][cargo-watch], + [cargo-expand][cargo-expand] and [irust][irust] for Rust development. +- We recommend using [cargo-udeps][cargo-udeps] for removing unused dependencies + before commits and pull-requests. + ### Conventional Commits -This library *lightly* follows the -[Conventional Commits convention][commit-spec-site] to help explain +This project *lightly* follows the [Conventional Commits +convention][commit-spec-site] to help explain commit history and tie in with our release process. The full specification can be found [here][commit-spec]. We recommend prefixing your commits with a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: @@ -106,18 +125,31 @@ a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: ## Getting Help -For usage questions, usecases, or issues reach out to us in our `rs-ucan` -[Discord channel](https://discord.gg/3EHEQ6M8BC). +For usage questions, usecases, or issues reach out to us in our [Discord channel](https://discord.gg/4UdeQhw7fv). + +We would be happy to try to answer your question or try opening a new issue on Github. -We would be happy to try to answer your question or try opening a new issue on -Github. +## External Resources + +These are references to specifications, talks and presentations, etc. ## License -This project is licensed under the [Apache License 2.0](https://github.com/ucan-wg/rs-ucan/blob/main/LICENSE). +This project is licensed under the [Apache License 2.0](./LICENSE), or +[http://www.apache.org/licenses/LICENSE-2.0][apache]. + +[apache]: https://www.apache.org/licenses/LICENSE-2.0 +[cargo-expand]: https://github.com/dtolnay/cargo-expand +[cargo-udeps]: https://github.com/est31/cargo-udeps +[cargo-watch]: https://github.com/watchexec/cargo-watch [commit-spec]: https://www.conventionalcommits.org/en/v1.0.0/#specification [commit-spec-site]: https://www.conventionalcommits.org/ +[criterion]: https://github.com/bheisler/criterion.rs +[direnv]:https://direnv.net/ +[irust]: https://github.com/sigmaSd/IRust +[nix]:https://nixos.org/download.html +[nix-flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ -[spec]: https://github.com/ucan-wg/spec -[ucan website]: https://ucan.xyz +[proptest]: https://github.com/proptest-rs/proptest +[strategies]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..777f0214 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +## Report a security issue or vulnerability + +The rs-ucan team welcomes security reports and is committed to +providing prompt attention to security issues. Security issues should be +reported privately via [quinn@fission.codes][support-email]. Security issues should +not be reported via the public GitHub Issue tracker. + +## Security advisories + +The project team is committed to transparency in the security issue disclosure +process. The rs-ucan team announces security advisories through our +Github respository's [security portal][sec-advisories] and and the +[RustSec advisory database][rustsec-db]. + +[rustsec-db]: https://github.com/RustSec/advisory-db +[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories +[support-email]: mailto:quinn@fission.codes diff --git a/assets/a_logo.png b/assets/a_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f86c0ffbc0a7858a9787553f44bedf1561f19a08 GIT binary patch literal 19259 zcmV)IK)k<+P)RtF=rAP&2pAASTLn?O)v`rJTR_2~LB+G}l z+Iz1}3>*W;S{(By&m7*>+`Vs;xO)t{y^XOIX~qB;L9j&>CB%^02`+=-A|t&K8@rH} zFR-cQFXOMC{NkJS{yA_92acW&wY8b2Uf6LD!X7Q)Pzd`0XmOsgE-+ebf!`bOPDOgK zv#C0N-z(3o4gh}Oc+-yOPyERi?ak50DVz>q#|^x!UWG9?!^Shm?|k~>Y@;0j{J^nM zjwesO_FWJzFwoHw%$wkHJRz%ZN6olx_iIjH+3x|bwl;>H*YuGKz6LoF;0j7)wyy z-QAT{&2jAwymRm(wzbs;;J5LP$4|a$&xq!01P%o-^v(9906jxSKQvK1qc4HK!2z#h zzO)sL{eYF;FJcc!+dv!!a*%))KvD8mVvyYcI|VHx@RDSnRD?Or1`pw<7rsJJ1JLZ- zW5M(*Ms#&HU1FhQ2#(z3VhG|Xz1SiyW#(r%?jc9ZdV z)kT;Yaip*Bb?vm5HwnNCAddRn9=1|B8`RMdwsygb0Li4e3Z7B5>>?YwmB+b@270LH!b)qk>6#Aqgg z1NtXv!FB*&nA-GY1e#!mN!V+y3ec~y_>hO4h!jrFTTF|{KN15RM$oU zypB0{Te0<82!{Y<24a>2H~Srv-zmRX?n3chnDX-|`LYsh{Uox&oGvw=u;s07iw2UZp%Ql{S>_4gCBZ_l(jtrK*s-^MLwuYpg@>S(D>>20QVtr828sE?gDMZ{eo#yWvyol{{B*J7X=+G&^Np|wxX>-qfQ4N|@v)XzC427dI)(dU2i zpBeO;{%})mNXM6~@NEfHPuII&UM0GI!`V;Ywff9Hb!qgkpPMXet^-l=z$ccRC2@0H zD?%TsqVw`93to}<=RV-mU{$Dd_!jj*)(VL0ZVh4yHK`+}&f58x|25;E&S#%AD|*|I zjx%ZbgZhNFyM||!*|5TP8;_gE_j)%o%f`XuknXyqr+sEu4 z>P>j}>v0T?QN8}D({J2iy#TKhKD!-=t0lDh);6#f4LK?%_MZ8}h5a@7nPDKyELNzW zj|I1;@Lip}X{}k@`uL&ejs0f<{9{vR>@Umf07hW$MrJgxi*_|KRy|nz4Y~!a=gGhAh%TU(o1C&25-&ksYba+8R$PH^wDC?3XDN_G`~M+Fu-xWH~;%A&1N;pd`7QoeA; zL{a>U1(Plq@m2@>6Q}%i7X#i4qA9;6={+eL5jZSZd1jk^Y=k$8Yk$_H7q&gP(ZiJ< z|G1)W$l^WWHInKsib~uWmK%;95=}+=QfZA1l5fZW=Kjxe;jZ*XdCTJ6t5<7BvXMD8Oz< z%UxlxBQ{>CaWfQsF_VNeooj2PhTtwz5SVL?ZmB4`efOPCd2CG$8_!I;dRVpF z><`xVGc-p6+;1aT`e_rcpLT7p0I%uOoAAcSTV?G>!r)SHeDF0Snd%`_2@})M+UJWz zULq?`IAyk6s)IdEKshW;EdhOOEvgCVYZ7WUp0V$aU1#vhE30oMz&|$i`tw2a9nY{A zd~tc!+^xc3nEEb&BRkK7H1_*@G{-;ZdbH%tJ@wjstkqctn1WsvudRLMrhu2njIHdk z<#&B6U%t&O9(Isq4=uci%1M@Duu7LLDF#Uy%cCOL3%Lm89(KE z_Mc9;?zF$`HDlUYRAqkwe_V59z;!V2*lx)p9o4I#RWx4%*wpA$&aUWWM_CeB(pw=4 ze0P>){@iUAawCKACMd|vGT%Ae_dw^S0+Chq(^pKN-t<-g`&|?7YN^G#5n{7fVVZg_ z%!8srg2}niIo91j$^bGihS_Jr_0#@y-z(2-@5}c;{)VYfPT2XS_dxh}K<#~feFS^K zT@x>B=@IZc_OtH*^se$XR|gdXW>Gz`x>+>Da3dhfHVVNHqEc|XG`_ooh=ChavYETz zS&O5_&2QT6tpNBfw|L_kNxO-zJ4i1+l9%Lj7X|Z7xnud|Fcb#76&*wN-e<-s&u%bR zyRFSmxMAA2EOp;KQt}KcbD7etZ1T=4O_8Sbcd$Qt%FRbw@tFMDGPu-qc1Yhdm+KPC zp&>=bGt#$fGAB=%eO&j(_&j^fn0AL%?UMjI)``1U@k>G!!+xqE;5G5w7Qs#;iaxhW z(-CY?6egzxn56-2Y1vqV(&EAn;s&tH0iB*iEanU3EV@|u$bAQ$J+!}q{SQ-TZDVCG zBhZ@F!+4*U0Zm)Kq_z;#l=<`&h_6g&-RsL`wBCjV{l{mL{ARD%eif`{`En3H^w*Pa z+^pQfx3;{Kz?LfYO&XtBB}5jP2c0ORD%TK<%jJU;fV0eBQpt5t$wTh%ggs+nkNyhy zW>x!_mlL)%FyS4M8gWp7nlzneMvK^>= zPC)y50o`*5Dkqn_G{T{c7ZpofCl-vYg6xIdi{D^J$hTG%{7yNJlnQ;XB_#!kukI$M zBza3A`RCyN2KEOh&m0cnTNWBzdM$%INg-gRn?j+J%?3c+Hr(jT8%y%8NA_mMDZh@$ zTh|xp4l3S1_v~3iJ-}W$HAgiZwFnKja(?uAI8lb*FW1 zG9OmvhYT=Ouj#87h|g?`j;m`t&1mfZLLV zU*E1%RRbl@5hVFLL%tA34s97Us=t8!;ZtYrWi8LjsK=lHtSrY`W*k-g_xVUTW$zzP zUb-oL-rZ+ReURWieZ$AY(C}Y;z#FqYh{#J!xGoEOVm_S&M$y!GTuXr&cXJ)=#qOoN zf-)^J@S8?La5DkDpVt)-b=GP-^arpDVl4fKfU%8SS~+f>f0gMs+xS#(C!|kLm@)OG zP3>3jLdVr6m9lR*N?6L|Cdr{>^`z$tPloppI9*oq0aZ-szq8|v$eSPP- zHiLH_oMrxf8o3&7*B=1?$CGCrD&WX4c0pDW#)c*Ch2P6Ert~+KsE5wL$4($3WHRrwLwEyhFX72Tst4;*Wz6eA$LifctF8Om9T=V3gH)7bY?0@5@R&2_jooxr# z2=lAHIh%%8E7-xMC5;E#z)i^QNLYhZ!kd!?R>t$58MrMB5?m_F=K2|5&qXN&Y0AKF zm4iQZb8KSejb&w%4pgVl8Uf&|zWY+1RS68d6Y6VOV0dnf$8&=pN{E1q0WIw~iZRos^@M z4*Asw&f2dHZDv!v*|qNIG>AKv>2rEQRYfz@m;x3a|DPQe{HVXMQ;O~11ocfK%-Ye; zjka$T`c_M742t0@y z3Zqe?s@#}kX9LPOqm3`y|N6;~pdTG^bN6aU3;V=9GT1D;pPbeYOfrk6+wHuXrHi(Z$>aNq2|@?6Cv-w1+iz+WKM$H7mrjs3yOwS&N#0 z7#6XuKbIs7>ltE01pgY4c)<`i$BOPf=shEU$Bkqe?>qID-J7Kc0FH8-uURT6UZw9D@2+$$1hBfumAftp(AfM3H3#0Z z$=UME`YWd*QP-WvHv>>{8Gi} zz6Tyy_WT2f{@Y~_PW;TFn$$ZT!*xBH#mN9i!BuQy5yI-h#qoOxXbE#(e+2uiNwXpm zEq!9)fn^y>F%+Yh$6Eac=r`5ozzF;9RA|_cSNmdpm4d()iRElV$|0x24{U*naTyq8 z;7#2Y3!C}$)&Y3x>cI8miMk>h>9jBh4K10!j~xJ>D~oggyz;@t4^2Gp#;9U`CJg@T zzPGpaS$o<&r`|Zu0_RW(v#y*X4)@Elc#466Nfyn$3l|Q*1^w{Yd)UgswW_vZAEED$@^x?7Sk!Ly14ufx)G3R? zfXOMN#2BtDr;;(}!gBzHE!lF)0|ZMa7`1igC70)@gFKe4|7?}Wb=Yio$QMC=9)dk& z)SQr(4MOS6%yKx0Gi1#Fwk{w1+Fw8V#o2R@`1hlpKKhHJ*Uh@?GiB|MXds}Gvu6yb z%FNTe<$&u?`ZN0JQSCGn05tav-nqz2yi~jSU{ql{sKMD*9&qU@;llhPM-?x6x)rNf z!>(-G_E?j8Ijd{Y9cv!LVP7E6qQ{W){CaRIHyAE~jGUI;0&VRvt%6|Ex;pQg)IXIO z1ma&U;$sza;gd&x=^@G7M%I42=WQ1~x27A?WLu_NU?dq-oQjS^>1u2$n3+W2)v_LP4kh)b;h{G!45G zJrK%aQ?S-$XHsjP13FgSs>jymB220aFvT`~tp2++u#HGCVV_c_hOB#vWcocbCdXvY zcbOdjc_ixWPI`_cc}H@&p?Mm_Z7pc|-_=p|$qBRDyyoaTPr7l3ifQ^|1a4(me@_N^ z`2~|42b~0lDYN@Y&t9@tzO~==$A786g8hY4uWfCQ^=ANk_QB~^SW|sxB`|8LQCGwk z2e^~-R0vw>Bv#eX5B6>c1?v>vC`g0nY)$=nnd@VfQIdIJn)vJKm+BOx>$=49JD%JU zVZ!f`&1Krr@@| zwf8`;EEsk&tSEhgDAJ^97{40u>hBhe@IP6GbGE6=PJ z?h9vM`bKPZgW&tVX%Ol9(ryQmg>Ox*eD)@^(y^-z3Ew?Q{dDX6Y!h+R6yUjjWFEL$ zxjw!BmHPZ8?c!c8!JCppzl+Nw1ZgkbE=(aJsZ4FghLu}(@fm>MEY_ZGzyuhoBIkP`weeS8(?nCN>eMk4;4`?Kp z&8T*X6rBih8&|H5^am5j?TAMp>f`zKZt}?S*~=;)mH*y42_%<#ELw9UE=|$OXMdBN z+jy$QC5%TQnhhLeIXj%IL=vbWm!qDU{imaL$84t%hN2t8PzA68Ra?O-He-bbp&c!# zGJ>IjNn!<)oQXj-h^a)E{=XZJd!fIAT@as+ul@qSk!&z?U@ZgI4j}$38tuG8e(}9m zYr?m~a@wpwE3~GW)rW6sg0DCMUL06%YlHFt*&M*uwfZ(w0`_-<;_vb=n-z_f_p*dt z>1E^D9HAWcV7!C$TJGb!+l7lxHl#i&pqVj7p#wYO4UETY7^g+p+%82^+>UCk3!Swp zI^+6RS6oAPTtl~wUu|8|`osQg-Jd%7+H(aQ)98AxwPytT#Q4_uN%S-jVMUX@YIrwK z5O5&jTkec)Nz;{CMWgT27t2_csbaZ-soa`%r?N4#?k?w!I%^XnXKHE*u;YxjbSTTL z9HFuC4!BxRfg$f{XO@*MTM#`7I)@R19DOz-*WUEYsI!fvPD>JKMHQ{6VJug}!k|+X zEVs>AfhKe*f&~#7b6H*6rJuSoQ?7llfi6-Y=DR+Y-pJgPDjxTEhNVY*=KUgn1+djE z{Z+sM7nvEBurK-VtcvY=xf|ou%D}IFw4q-Q-=#^5$-KXFg;*0Bl1!Y6g!*$SM~`kK z9Lmvo3@KGvKbgs>$-M9k-rv?YQv-m8dGSK}evGs$O#4< zl5*t6;mQqNu%?fhY++=)eDM72F}OTE(n`xZunj@cS#19I2*3}Azjp&&%3+u2_MZ7c zbkZ|mAwCP>X+Tc^=Kmttxm(@zxh0!o5=s!CIq9l*s$y<{v`0^{Tzf08Uro5-^oeB& z$I@fT#B+yNqv%oq)BNq6#SW!LE=}(Fc#Mw%x5lL(3%q*}H^k}V)^&ZX7vLst((EVi z-|mEc-uJf&22F=aJPOo14LCSVH06M=1J$I?jRSZu$pn8P6n6pyKsVm+GH^SVJq5af zlm3;v0P+$yw`VmQgBxm|5P*z8y#n9~K)bc(_6G!{!GyO6+tb=thgy!dM_he_&!`X`QK*F5}gI)T6kBsFk2LXg+~r}mK8|Quc`3|JUJTh zxgOntt&J7$HRy@? zi*|l#ysMSj>!q^r$WL$48RPeG{VO^NCwGW}@&MO{vegcl5imf0d!FykxIVn^=i7|) zkG;ed{<0i;-{}urv$D}=YD1fO_xl#TEspFFlsOHCyGN|(wK1(|7ASsnBIZZqaIu%SFn-MD|vmi2>XN8B2 zrPwNXMXz8*N^7V?vIh2D&f6po_cLT-5b|N;PgFjIPE8BP(!um+J?i= z-qNCSFQh#~KOQJiB2W)N4;rwo*WeM90>dfyO%q~4xv={;7I>(6u|9azyvATJ1OLB# z=+#{Y$4!Tbm>z-quzutLX~B;#*4`jp4DLcmo)70dOm`2*b)grGGTTY>@X6m1vXTg~ zFnEh(>8P79BV6xINZcdGfaQNnNO+xwFG6@ZhUOb_K4o93spS=m6$>A)ZNK!m*~i6s z>?ia0=VxrTGVa{E!gwdJc^63UwA8);b^~D4us>_90Ns;ye|gc)FYO#EA+I&h^Vg4j zVzZW7^kWdmqcm9cEqo|`WK)5pU=euVyLIu(gIOQwt~F;~jv)MWRkJ3 zS7(#$0?vW}Xf5>RF-_Zl=ioVgt8ae)2S2@iWX!RaJS-v)Ay}aUev?`4BC}JcCynQ} zd5cuLQUCf|$yEsC~nfd{@T-&kmDV@w>?wDSl1?VZioQ=#3#=pA4uD!A9y$MhQ zjr`ZZ+6O0GKYecFB;a}+^CzC$vZ7kO+{#W8%t)mLSVTjq>UwhrlcsIgy-~r>bF2;4erQYXp&8^8ksW-pG+)Ps8%?9k^z zLwCLRw9SV_O`nZ{V*$Hss;e4;Kdpqf4z1}6q%x4uBSgJo$sY?tesbLJFJHVVr^)Ak z>_2wtmX42TJ`D0*25f~xR{}+sN6rvXfl1kY3NHg@Q#^cS1-d?QNuio=CFOJVo;yO1 zR(ARQ(4{H5GAxvrka7qN7F?R5{7BCheGUHnpK5W}#J$%bMY6$^wSNDePi$tJtMBNp z;RrzQx4<3{T77wIdMXGd%_E%>p0MEOAKud zax`FDt!M#tihP~YE7o#>W$i_A$cr{^ef79MUfO|veEf3SFNZB$vg|QJyEMaMi0Q@{ zbYK)aF~r1KkpLY4(k*gVp0!m7_^`f-!5GT`Fh27b(2*=!=`xr!3N{sFehUGsvnYG= z<=2j;x%;V-$)}8+V)vYjqm9_;d(G0{u3oS;n3!_XG!%a7aB8H({ zmfm+QoI3gfVw_eip=|a z<0hWD*R|7MUYq+d0Qe1j>^OJaJ8Hy%bov8KLK$ZH=mc?HFj%BPSR4<6sR@Hj4WrqO zF;N$Wn;1>>*B_^$g@{_B*-T&Ee^iIyKg^Al>SU~YyC0%JHs-HE$ z{A3g>=WluK)YWTyKL!B5fsfeoB%&Uy14{gKc_Rx+|L1zLjaY3fXh#Jv$Ab|Wi=n7t z*Gk7NBckrD3A`QT5IAC1-Ux2FL2Q{7Z7~1V<=@4O88VqrNpTo-xC5k$$2r@>>~j|X z4tYBf%hfWSzj=u;(|6zHrF+*^$$7ws-+;%DKRRohmS+1WLEEHrr3&2xN_xRON$i}f zB~x=LP@G#C+I-;T8z--P;v*N0+Bu$t54g%N`4j!Q!(tyM7 zvCu1Hh+Ih)mIkI?Y|((COaR|0U^dzLnLTElvt)hl$$*95fXAR_PPXJWxjUWPD|t<; zj=fFFiRDgHNp*aHtEb#JWyKUs0q(?70KWzBTS;6#`Nt!dt9?QQbFig8WPtGkHcNX+ zRO%wI+-(S}E7g1VCJoB7$U|=8H}8`uycUbQ1%X(yzkzh#9y6yu*%#ef0~US*9kV9Q zYHpAD8&U7^wAQ4?dcRWnAR7UsRz#J`ekad9d0uZ`|MjVtY||BE0>E*xupfc_1#Amo za7OWX#a%2J_T#EodTr+(N3T-kJL+^Hi{r_f6e=$vuoK|DwD|tYA@T3_O&YNK;urw@ z20CUOd&^;#yhE^+TzM$~y8JP5sY}BolFan$>yQ8Uskff`iFJKH_nq3-x@*_S-C(#2 zr9EoIz6N-^z^wobg44z3A|tEWQKQ1I5)lou5F5xnzpqo6NjQjHV2D|^VrK2O{j?|8 z#y+38GT_o|pu@6qG7wkl9Y@Tj6*4K4PJ=Z14y#iTCPy3)JX2{5AQy z6#yPT`M-vYvaLHSvO6orI|b|sa%VtW2pB1;myiKn1V?w zkB;paec;9#YVhJo-`Xs$4BC#iX=_5`1?&cxtq8k?fXx961=s?jMM8_5r7ADe3rMH| z>;lyeU^&2-0JA`_4_h_;_OD&To^!jUHuN!I;Wxlxj83(}*!0HdNg~zfhk$^LKQ;)y zb@ z*Hf-!vjA#oN7t%t&%FK2_JONAVBt5pqmx5FNZ4)jwmfIUmmAdC1l$RUm&y1kFAc7< zHrEe;y$^TaflJVLhag4~KUME-HF>8KO{QXy(t)I@H+<9Se+2=WtPK!kS5@|kfh#*; z;Wx=+_R06`h#KC*gx{vqBbxHjxuwsof11Mg80&f`#v?;aW&XgG9RU0$dBic#AWdry z@Su-FCXYnDFv>5$WfI^Tn9GiveQZ2%Wd~fEO)yxU+brqhsgIt>FJ(Dy}uIyXP!q1we=!IX*+rlE+f(BzFtI-zGaFR_HYLaC)X}cu0 z3zg+yyu4z#vKv~u*~p~Tz+8E7GO z1MC#h0dX0qMG_YYc$U&!nD|9t(95;mw|=#)jkOK^xvrT0aMQ5W%jOU`Fe?MjX+lexjW$QYc%M6h4EFBpJmKLh-IZK^yv|JnE37$*|^e=^t}!tk;#_Z;8| z>cdf-535Ecs)VE(V3q?~2Ji(-%!jp)G)2*q&5RbDck@Y8 zvG~h67u)9ay2rh%ZsC>9Ntu28%|{+j^xuKP|8y?RMN{W)-(0Pn^vJXGkC(VN3FCFf zm;I4D;-%2A;MZ3(sXHz9ISL>ufJRFg4QLmN9|R3*7I9V8C0{%4m8WH2`m?3^tAOVQ zH^t9<@unTtSq-5QnTZgZ=ydJpwKDD~z(jh<>2^v}vCK297O{Rh0QP=h;RW&16Q3Jz z5q}osQ2<9H5moA`^Ifj4d0B_M^EYf@;o+35`saaP(dSe!CGVjIU?G6NSeXZ`!P8*( zysz)E!@S;9>)m|9eLEuJBOpd)Q>=0`?hNyxmsO?rZ=Q7}@aNUdi{5uy4`M^@Y|q zx;jq*`9**`06~1_)j^ju2$ab$$cG0@EVUGN>x6^ZgWV#0ZfRg#+)R-YKtAJeT5drWyxJvuqR`7Kz;dxEl_8VE~$&AmG0< zY3`PlrpinJA5bvT)IWPD$`e@cGEq&1mb?7%eDFdb|5hT?tkTCDS^jbfxJ2K+DvSA} zE6ZNkx@F1Xb>XmF65PNhetD?0QvcZ~+~br|nLqy58xA{r0G69r&?<@FJ9+-$Hnvv@ z+On{Aycm*MPot|*fShov02xA(FH19w6vq6^>Y$ivIq^oGSJ=G z;Yc|bf>;Fus*7hX)1eBydVdE{nVOY)Sf4Dh$Bx7|1j)iGMl(AxH0pLFu3eE+W=a9_ z#!L3_MLaxeT;<^bNNz%azii@NEyL%}|E2-I52(clQ78l9V5lHbs1k!rzrUI4+4~{_ zYahUtY5?aF5z>;~p};8#fVFkN+Zbb-R@bRV`C9FsJUIPHALTJasvc}EJGkxO+5jXs z5tnAxq`A$1Z*IB>vj0}@`TLR+p3k1#ZwWofw#g50#h&=jVxw?q$>qa48?+V%)Q+Tc z*?Uh#j_P{#b?Y9X8zY*!6YA8J!gIH^^s^`%x9Vy)W_wzFB+lOm{kp${=`)jF9;EhR zTbU@@!Ad)b+6Bb66h~QTFeM|f(*R2V{=*8-RbXGR6)j-Xs)d)%-fn}7=CXH|eSS-A zGy63_7xd))ma197nFv=#Lzb{4DPYt?rZr4pslnr})Px%#=b;1}^7ZoOO9|H2P_tEx zZd#4)2e;=diNPEV7A5~PQKirY6R@ze`A<)-+Ts(ovV7i^x9{7pnms;o(z4B3R@UAD zMh8*mqX70$rnJ@@=m`=!K|Bp`js@=+YU9T)x^v{xHwp0KrIY8MFKaLIMae@j`X*hj z2#@H^at)_=mo5O6i{vZtn@Yv_T`35$#E%bY$Y9iq^7mMuEhSjRh;58G?#5mtm!pXS z9hLh~fQ~Gj)wweHb0y;Ui$yR6p~K?MX91{ZmE?o>=fZ5XuQ)X8GI}Wv8^GtRSY#9*mYQ^pDAVDtLlwg z@~nCq%)1#_;PtL1Jic_KGnX~#jxy#oi1!n^sd-q-uQtYZ-<#u{BbMzH(S8x)DIlBa z-SBWUPI(E5yh$Vu*&zQL3#}yZtEfxoegD^^UfSS*|L#f8?+~H;4+6IC$(=5oE&#o< ziO@Z`G%E6w#W_GZHK`t4n!*l27P795b`d#?sczYAV%=+q<0{@ZY$b+NY#xhK4cUIA z10f|(O3n?+A6_yH%d0KUByN*T`*7>#xsr$;1u?5Ls{Q<`+jd^K(JlJKCBq|w^F)3g zWQ#v6IWSI*kRIG`2G11=fKMh0YMCr%b#$!w^mTJ~>f9iJzwF%kEwv@~7l005qtg>? zRpCsbjEncm3raX{_K+bBOB5O=kg(=@8fRE%&2sWQJ4TI0C0}#BCS>&`p{LXzg8btGVrmw{H9Fh6nq? zLs^NwPnq5GVBaOuSgLca9-t=o=CI7d%bD1yq#nyYA6uSr<^OJeU!Md1;>q(qPU(u? zxIOA27%lq74Hk#19_z|B%PlWx;MSA^Uctf&&eNdICif%j);&%DAKNN+7}AbW&9&TT zXGus+C0a7h2An)CFR}99>t6LM=(gk%oqtyp|_8f=iB=ML*bX(1sJAQE6=6%ZJ zw;iO)8$%bL51I=p8h3V7IFZD+*rBshh6nj>X#n{2KRBd;WFg2}TmAMn-P?SnZO+>F zI+TL>zfOK_NMx5k1ZjM4Pvlx*kDZ}P{pO-8;kiT-(FL%BVAa;^%$o%>2uLS?`vE4( z1p31Xa>4Ii4RbHalm@ESB32D?Ttfxj7(cSZ4^UY?a51l?aord>ecM8J4BWqXIIMij zo?81{=EJzIO`hD!7DHWaIC=JSB(Gs)?^wOX7SCLHC9CTJ_SQFszJ4{plkx?IV7^pR z@*GI)HBu_}T++f5oh9NZwB;7wI-s|Zjc=|Fshxe@|LnAEy#aselzDBk`f9I%9-c~J z#y__J-u$2X=U!leKN|6&Sk?K>6*GTDbMrsit}FU#ZIjg8I_Rk_SsT7(HLmUu8SEx; zoTQyVvmIo&6HKLy<}7n-R8rTI4Mn!~1XFBd)ND7lZtcX_LA4-QE$m@|rY=2J#+`-m zx0TIUu%e|7<}z1?bga7ZfbJ)Z2b9Q=dodJK@ zk`nAHilJ^U4INMY6cbkT;JQwD2!If&c7D8d_;z^n1YUle!nBmZ(*er>~%uH+Gi`o`>y&;?*jP(;4eAxne#z>XKjF{;<&9sS#NSY zMVkNYR{K3)ylL+ZK5wR9);+4Fl;Ikd!GNEC8Uz!)fSLoVS&uT z*V!{`Yp}Avt+sa^fAgRATDVq#pEXI%uQbnF0Abs;2fWJ5yD~LpemAaGFaG@O39sQz zJlfi1hAdb(rlVTj$=2e%!02EB`w$psg(jP9o3V`%+bX0g_88HL3Kf(N_-QXcByLES z)~U$Ftyo!&$^+6A)TzY>p+b5I4&9wQqk9=lW*N{Qm+ir76&BpJF zY2Rk!+Fr4(wT@}&!j_@$USi$L>MYBu`ImJQkG>0>pl6`B zH9$CP*AI;UNm~!CnO^Z2tv<0XU@uPFq@#?>PoH=5ZkKP$^&l7Aw);u|bCWNeJ8^!? zkd}BWLw$5itNnh;46$skoF}s3PfaQtjr78breJ0m8^BWydkn(PfD9<9e_e_`1su3D zfkbK{&2HW_aMxWa^fNGQD(k_Wk$+WOb!4|duQW&Nlwi;kfN%;7a~j*qesA7y9(yL4 zeN}p7<-r%9@Z5HS-o37%2`fjEb+DHmm)||E>!SbJ<8Kp2c0?6g<2o&A=C)Xk z@5BLx{@eOjM@%fMnqt;M4UDq8AQXDS?(>peCa-oYpCRQ1f%42Vo$_VUWr^sQcL%-; z)#J?L@<^`4$P4UYhwzh8@JK%IPP7X6=A;8&9<)ZlGm4KO(7N{4z1(vn@uyME&F7<^ z9+yp=-(rE!DWozJKQ?RhXfOh=P@fOsg_TiBc?C=DI|IwFUWa0Am=cuaTV>hgFO1i` z5(Fyw{MR5G?j{Xc`aW1FnJe?OXZGYQKQ9yrn2~q9s#d584Aa*Th^*r z_XuIJV)Kd9uG^{KN=*zMz2f5%whJ@D%tzC?3}*l)dSDiasx?^LVLXeF-C~AdrGIj7 z8PHG5BOQF^A+Lu(D;TDh%5{hD!z=lbR}xa7AsKX&1yEM8&Y*#pufn1zoL4z}17|~% za_?Vp?Sf<8`|wr`0sp-P^WO$ypLKI(ikVUxUH7@4j(e=Xg8iD)9@&S6?9P)JX^YVN|(G zW3cb~!z8&hGSFm3{^$*(s=HFZwqRe)CEsSpaAW9NH;`QIZd%yFlwH5EI zAbK~zb1g8(z^uq!n;;)cSAm!%j1CLXDii1P;4as?`#TDg_@rJS#>TOM8YWoFUt5jd zP*AW}GOt#S&m!j96)Lb}DzG{Lwun_bvOr5p;w6*k?YI6CWCF!l2EXXf*1cw1&H%Yp z3ZP8OvY;TA8&A6JT<-JhJME%&bzB1g*UdS&6TpM@Fa7ZB!{+WHi-SN+lC(F8EtsL3 zg_M>bNH$9RFrXjHTu(X91`ErYaHS`hO&~0Fn@V{Y`L14x&re5rt>A@a!m>{Jhug&? zhh>5@3DJ$K>eT&oK-YVP%-=on;W36o=K>hJ?oiK8_l%7?_dWlHxA#k3^Q)#jvMXTj zC$$9{_Cl2uBs+M>x|~w~lKA12>-RrrO|SjwBcIr!T5WnSWIjglK+$YZFv>HkP9iAF zxH@#AicE%2c37|a(2xKt$vi11rMW0kQ30-by?)c9aaL; zastHl!oH-IMmC03Eh#amC!(ZUnt^Q>ngt1EN$lt=fr)baI}GJ0bq;V6r_ z8P&j!A4o?@IECak+3N>sRV9qN z0%t~k!L3&#Q{ehjS4J$OzI|3zMaa@snnOu{f0(E&393EcTrSvy7%EGm!T0x-WW?&h z>(Kyi&Vp70j>UQ?kvqv>SQ|gpAHm)>X>PN?Zx9$P(yQ1*sXSlV&kR}=xje4LGuQjQ z&1U!IcWn32<#%rX?_=7x-I1v5WyLQFc!s5Xb~%&e@%C8>0*lEn1&+MaEaN6!C6p^h zuxM;cx%;Bi)v&M_!BA%@P-UiqfHq%8R@{Hmq`8>@K?UT<^#Od&i}Lk|gZ|MU0l&?l z+L5yOFbX~@gW_ifwW0KDJ^Ui7|32;J1E1;Bg;{TDdouaD=-eY0y@Rnm7SM+Q^DlG- zt(+LzXF=pu?NjyPg7hkM*f4Rc9>P^mjiBi11=W#3sx}LZogP-@L8*d?<#Lix-edJP zK$n#oh1Zsh0I;~u(tyLUK8VkCbziMkR<7!AV4r#J{Ne3OSA7paM0tJZ4q(zbsxBSq z^Bt~0EQw{JZrIT8{qoz!J_g`10RQpHBVL^l$$SJd$3k|$hD?o+RABlw?)A@@YGQgp zOG#Kzq+W#74TRrl9|o-13Y1SRQbEU4ut=RGn(wN)wL!jXnfx@Qy`y7`4DgU0xvr|& zL9$@Q4xRJ*T`57w(p8@YxJ_0fp%Q(2=Oz{#)|?9StZWZIId$fKb2sMa_~GqarPZF_ z`Rw6~ck6~d-B3pe*bP9GswIda#hJTfdF6mpZd_)B1B%uKF5P+<`j$NFijyx)=vLgM zv##X$=Wq(%a_gVFH_CPb6&8sgR9M+5*%eU{qE&=lXWQ~)67tKAPRdhut6qfZ0ZdXv>c=}Ethn44+YR)tUO`NwGaoZd^DQlycGTT%u=T6!O2aQ<+mqqg!aYW z9zVE!%NGEA3BZ@8Pkd>YCN_N!;78%==QuO9SYpwrQhjJRY0+Zp$%%nHRRYQ5m6T;t zb0*A6g%ScK6A{y%WB+GcEQ)hWp=q-c0|UcleS#ii##&Z3>o?Z@N|Is|G>BpPuXTI|8?H3Sun9M(`SM;Znni(}rn0k*FMg}qbV-*a)f?63Q zN^c0Dp1C;kEx5f|Ti4cQArG1f-WiJ#(d zbnFA=Gcx-6TvM+;}WM=7S zQRRR<1GsFWHffzjl7hd;&&$bDS;UzTt*OL#0pjxYXK4V4AGu^wyvey{NGR=Xq5?MNUXU0e**YwVpcYVhl{lT5H(#rq>raKpx$%05XjbA9Q zS3r*|nA|A8{v{MuO2H|YlXt)?N1Y(}y-nSh>~WiXudGDUJ$LeUU(Q`RQUd<8E`EDS ziBgk|;$t_(8~wu*p4d(nmy^tPpt61mU!;{zAghw5dV;_bqH8u)E8P|MqUHpVD!*A?**?VLZB*l=jXP@ z<~!&|$B$-@T?ObldAS{j9wZ7);wAPv;Y8|#ni=c>3X(;+|L$97w;0U2*52k~OW|U$ zwx}eWo!fi8Y{5Qz+q|PzdVv2MmOce(y@mDjixGeE+s8b&2~yH#0vz^BuRN zA0BZW|4$m(DfnEjKOC0j7ON9gl(kOWk=)|vY>TY&ATtAKoVzT@t-AkJ_B%JIb}~Yrm+h)|AM5imya2CxAUOL$r;?> zy0-on&zGXgkgu_{ZMq*E!=k4p`=o2tJ&+3yGg1-^GN1&lOB>oC0F8aiZha;uQv^mF zSah4r*Rp|Bwk>yQJmx4{Y_D=lJz5I*k%I<3MQOboIG9x0Noo~z#l@4K+J9r-%NZxX zHsp^h=Uynw|4cf~dLCOD4pb`<>d$ukx~xSPX+AUU+Ff5pKRtdld+aJ0-YKW(%5^y` zY(eD!^65bYe+O@9K@x0wqiS+XZC#6P^PlT|sLF&=JstS$XIorI4i_J2X5Mo5-G_IT z0{*n?cIu?j?7lJYnL|7}veC~kneyDO8{wL zGU06>zhSR_t;_%bjH4T1RZE$UEOoD<)tAGa!_m-JXw5TAYm0n(V$FpZfD8KE7nd{a zprK2XWm|2c8}Izi--&A0;@a?Yd&-{O-S$cV>y~v@Ney+~SKgX&_5RCGnt#{^wOUgi zf16oS{i}qdTI+I19mzw?#05j0i@`cY_x6`t|x^e$?^5a8V;-)WFyM<7kp-{jkONTW1U?=&d+YlV4d0;Ru$``cRzmkJ>h4r6u}#RU5t+EjgoNhfSx^j z5P&n$)qTwOPI~4F7|i^)nY+(hcYgi8)|Is_j+5m%fZml4KbBwXm5wd)9&y0yOY>d)b@HYhY++kl?T zN7%?qB-s+)-c3fiE(|<(2B38Bww1B>mB*NcEnd9zq;-8UgL80AZ`u8^lYW6%ZsmOtXMw1cS$TLR2l8h>1_j$!8r)EQ9_rS3HIWRy_05;Zu(p=kK~r z-~*NR>O{AcphuR*J!+==djuD37_cLdrplX&RZzrt^~=lY7M9oMmru0_rqGqkGy?!@ zCTcnCtqyhoxboM#EN>!?5$x-kPNU7a@Hk~nZ0rpaQ#Lj-Wmak#+hT1EadIvtf9p=& zv#8}I1MW0FFVU2i6IkDG4~3ZOt8#HuB|5Puu=i-;>x|93!;Sb0KrI{Ya?2ZM=)E(S zSy9p27p%UD6ENsmeC89h6HAp%vG(0_OJLe3W=@zvzV-a}M#rg#%zZCq?ok1~$SRrrFMsCUdW^L!S$xc9%?t1hiYwSrdJw5ojo8ERe zfDK#|L&xWH!o&iHE=csHksWzLl}(i>SbP;~-vwU^nt}|?Ib*t?n)%NJb^y5UHxqtu z6`v{9I#SDAxAwK%GAEWgSM7DJvt#)Y2csp^Vb8U?x^HZg=N8KeiixM7^U16CLCpYX z^aku}1Uvu@A7n2BblXPA{W%~EeMEo$XhGqYp`%{rqWGC4UrO3(V=(+rGr0Y#<7OWh z zf-}FN1XYn|-|^_go7NU(Js#hmx?p?TUAqayyEoQ_&lD?3-&K(#`;d0&gsx61fafXz zqvxbka;DF3Ud3g{&7RPWf9gY=`o2e|)`0I)9D{;npGue;`5Kmi#=AWCrx?^Wdd%0G z$!Snh?Gn$r>#@UUVlC+6UPq(fJMsA~gxc=`joYZ#-kAnb=HHj`=*16L!UV~sEg2ib zFGqt`fA`EQ_pM^!002yR|0D0UwswnEGAu)m1|$NuCQLK|K|?hffFC7|*IvbA-qMl3 z@FFXF{IC9U#GG~YBYJiGaMIi@yDF965E#EvG4G7VER*~9T1acU|VR#xhxmrVWer-7^hF|*d%{k41kaLl5<;IKe+W|wpDD&NuK)#fCnJlR$=X~+a5cr z&nL?CNzFjpLHAWgZnf130KOt{-1@_~7jMiTl$sGbMJ7j}u#~_pLk97416F;*(3nTP z>z-|)`2Pv!AVGTq*j~~I0<9970Yu5|i2-y0>HxJ8(%&UKL-MzZ`0Km={E^o-ECJGo z$G1-U>t?NLIgQX+797Wobh$l$-lKB3f{K3?HSx>m%-G}E0bp*t*p)PJ z^h3axGCU{E+c~oF+hw91A&dnzQsO8=!$mejiU7tSx(F_ZwMz*uwBU;}da5#dx5qzq iIlBgKNe8= literal 0 HcmV?d00001 diff --git a/assets/logo.png b/assets/logo.png deleted file mode 100644 index 834b13ce6014b8be4bcaed853aa6bbba9525f540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63496 zcmZ5{cRZWz`!*584r)^oL0e*PMG&i~PPO-}QnRQTd+!pVHXZh;qH53DqqO#}P3@X* z+UNQH-g^GzlU(Or>$t+L2IoD*_eC3X|WTuKfj#TkBc#PA>BY=2q`(6z9j%DHhCMwZ$Hm9IjQI7 zeL7OIFZHRQ@7?={F$Rx~N?b>a9~!)Qx?OU(aI`tI9I+Xx*bbMn?;`Q zbM}}%A4=0a5LhlevmZx)TJmY97kYv3fd@qZLvbh}0HhxXjEnuFHULKj*^kbF(0?YX z5EimYU)R87X7}T(`EmASd)EsnTScX8lb<}h`PX;;98d-dL@puUO5i%GC_Y%ufAheA z&aL75JN*Xf7|HN_&MJZ1s#l*Bitq&E=#&ACLq#pF)u4^UnCg~SMp6ANC3+wkm&>`8 zJ-z3XLOZ@SGSNNNKa`|p>n^&Wro=6|GV>_=d&nfTJ?kt&{+gk!J=>Y zS^IhUMS87Pe4OYnxOBgDV{I?t@@LXYJ*(#m+M7Jx!(sa@{G_IpbL*3c?fU89#@3Fl z<#5>FmIy`cfkCg=G>mLgR{x%UJ1Pl`4>DHCX65)mb2J(;P9}UGa4-REc5A>~6 z&AE@Zp7tY{@b<)&_}2$;j2~XP%9#>QTy?y9GqPd`RxTJm&=?7i?zD~G{G_s0^O%8g&kLvPZ6NCG{rd6Xzo`IoHA7H?*J>HGJ0OfHEB*Abbw}M15t%1?!`yoHy%2wRE@*Fr45?$Fy2qfQf z1bEZ+x5cAh$q00-_^2A8Y00TX!~Q-y(-0_ZLkS0D2e`(i<=P4Lbb;Tg?}Wp-dZ0w= zpS@Exx&HR1H4z{*F)zp-9V~Z4le^LFT+%vs_LQ3LsYLTDU_M7qQ1AZ_SnS>3;-mOq ze)H2*OU({?tnpsv;JATF#iyp3E*O}fbx? zppru8R&k(`m$6UY+*HWdD;I$Pme~2tdbTY15oBy2-MiHbm6tI)Pm^di!A^l>x&gbwC9Dln80>Ed_4C{XW*^wfwTUN{J7Oiq$ ztkjR$>0drFEJChvt?FnFOf=2pFAu4|j1ERELo&li&fxQbHt7ZFJrI><;@<9DkLblb z39|^1D^Iuq#?{c@a-RZ(sr53_X;4vm@U**ZG#3i&;lIG04*xr<{6NPs2y9LG_FWmt zE&1a~iOwLBV~alDOkmL>WwGtDtc~%C@2`dWF_AO)JEh_DIv>O_B3Y;#=qzNEb|1`& z&ldfk3x8Ykwla{^b+anqNa!&p&R124|bys(YCEWTJRSgZ{Q)CO^B;#47B@^@2Z%whhCn@J_T-~qW#^CYZ)CwlZP=PhFT!`7BY&i|JxZ7W zj^R6xIJlCb5I_(Fn@2XE&G>Q^zcd_I%!CQ^ff7hN9N@xx)jmb)=7N^;2?9?Ih#8pb zi2e?JKV^_Qlk*Y(JOD5(#-wNVf$!UtmH(HMu5dN_ZTBhpT$Yy8+|jlix%T&05-~Cc z5g!v_t)Cm%a#7TAq&Mg8GxZEHEYQ$N(wWPd3CUeJBA-V6Z3;h-yT6~B{`Kmk!_E#x zl?Yx+S9PGdwUKfD;?~8Ur*y8;X)UDn1D8SM*Z=&s1vbBdn$N1vxJdI+b$sXHVcgwn z#HZ}8a8J@+}_l1B4&cDsN1H>H@xO5dWGQppb znsIQs;YbPHONnU^LmQ19d82;O%kK?&G+ehMsnjp7p|7+_cM;JFxcUPY6(Crm(M^8d zXSdF?Dqel`K+OiBaC8-sFQdkbNlK!Hv?8$Zp@$}*Hn3BH`h*YEVJ(s|ab4uz~h$5%sPxI0; z#w$ntqFm8Hb9{eI*fl_Y01-H5Ju@herHiEp(~BQR5HV~8-23b@UlQE<8}dVM$5$`1 zvYv5V@jDj?WFzNsvN=W$#(q!3a0n9>c8H&cj@=yh(SfP7Fq4rl?g!kAXqu4o z-6)ESE42k{i{jm#k8C|c@6a#nJEMvsJzxuCh^cE_)NFXN?0D0% zZin}Z0Y%tiUse0HBDiz#&8&?w$vt_^lZ0B7#@P#Fx!@2Pvs?db=Qz?7Fnfr&;H5wWW1$v*dO) z+2B>e%SeIGL2mycRU&Nsc0E(NoxKV;kQ))(Bt0GBocs`BP+R2b_2~wFNMcJaY2W!k z``K*j^R3%)xGAltX|LCQJYUtokcha#Wst_czKv`8gcq0KW~^owO*^LN&hvct}J< z;HfG#3rPxPFX@_r*iD8*FEDScx=lAt^7F`O$KvVhZEvn%IT%F+c1XAvZOfYkb6BX4 z$0KMKIWvAg)Lh-FR4M50LzN=OlGW?CnhM6$$sknGxzZGZVXbK(g(CJExg6kwV103U zQx*L?^su_C3C0TA=j<&9)q^7=Mrx~@B?5h8al??oKeoo}zblb(-(rnado<)ln1V}$ z*cXgV5f>6_Qt`6NJivL^MA!pBPg$4FqQfisHd=7ZJk3Eh*>%cMCE^up)= zT<9zx0B_Oa{q`P!Yu6EKna9DUQ>@^fNf{ndhu=UUKHM@m^MsY*NkIIe)R_@-*y($) zgoa_?=|q9H(0}p`0ATY7Yle0A$%Oh-jMF90X5NJeg4mhg#L1nv{bKQ9(|`#%j4CrN z8mQc`{1sguSlp<0HPLVzDE(S4dU8GP0vBplBgT^@nU4(01=l{IlMcMiM->4Lm4qBU zMVTpbFg`GzQ8RzUAwCL(sI1hXr}79tB#}YXO7A=V^i(gq+U9Vu;mFa~Q|pQ+nW3%2 zaHPYL$2-17uU5I->Rb>j$uF{i_Yv93U`BK*YUEK?9p|Ds;mBP^aVn|YQ=Q$8w>RLIvP-Z-Y|blfv+6u zp$+!2oJ7x>{zkVE{AG`~IeO2%xEz1_Zj=UJIaTBj`3an&Vbi11n)e(gic}-@+36M2 zGlk?5=rUFckd;w-l_yGlEUGvnMEMiZr*DsTo!^~*&;prdztRa}P<{gK*ls?X_REC2 zv3_}G14#`os!>@bTD*_z6@+s)RT!u!`;hfhE546v?}}pKm7K@6N+8sIAb|{jv1TxB zt%ujsTqTQZN)ROsLTi8R7yVV=58MXMgCc43Uj_T`uO)lXv&ZyxC4a@k5&VKUAK zY!c!ryCA!>X*nBezI9xeq_xrG>1$`UGb}NQx?+G(gd*sMhT1hdQvdzJl4V7Xvh>1jQjY8JJI0O@nPsGm9SNa$vJLTjz7gfBPX0 zcCfN`3`R-OgT_vh=m2x!Px^~Z>Uq5kjxU}0QCAXWALYTIu@!SmX zO6_fSnHcctK$4Ero>U22Gnc);5^o$(EZMd{z8~r_H2H`RN(@%fZot#fskP9!C$$>II-X3VJ^=IeKs+u>3;Bux!sPqCWRRo^eSE z023~LSk`>SLdZ-NAXxVaaIxlH{ho9s4}{PH93!|z6h#Vs)ilT|BVO#0Qc1xfEDRCh z7YlfSmy3(RybmSoY1~o2&2%u}I^nunHQOsTN%;)JButIf$SkZ|!@aia=>F!g*tVS% zfT>goHA9=_k=f}J5Zzo$ALs#Pp;8z?8fouRILV!O771DbCKx%v$rx)V#LLA1=PlP` z>8*v6!p;gX?TKB-79%7)v?`c?*hxd%Ff&HYESUDHb?;pR;N^0RR3iKidxZ;cKy$X) zbCtZT5YLQh37rCB6zGXhqdgZS+^-Akwt%2(D^&24+G}4=Jy`!vNR$!;r-B{LDu>7r zc&NosXfxO>9ph{I6Q>5J$x$}LV`5te0HA`h#!?>Dnk<@MYj<4C6EmS@8tl?m{renT zC_gzsbkPx=qv)lpqI&|fxEA+z;S$2PBf&TT5FlC-SMY1N((YRWe$%JiAY(M|SGL%_ zl+`XOFT+Y59p}@g*$t!@eC=Vg^WZs-^!DW^@fl;oiA`P^gm^G;T7|ibe zSOcC>UUk6^3u-!7f@03i{=HR@@y6m9N9bP7_pW$qn@C-{8A9fD<-bEUmIe38gnUEN zMr)RRd5|BD?z(@c!*@@BuiW3C!s-J`ID>C1tAaR9d)av*cL!>b^}N)UT?@P&@2|1| z)CNf9*esUrsUAjE7ooq^u=yHKH+e%E)d2ZBwjqFY=qx6TZAN9vQq#*vW;f97O>bLl zLdURu$J76u_zVOAJf2a1HctE*M)pf3fTEf$E02>GlK1^3Udfe~^7p9&Ld%7qPuPm0 zWY_&BRZSm}zGyHWPkxdfEvXgpcL9P30jLNV{CUIFDpVuX^wIqsdk(V{SDGp>MZea( zU*?Lb?`)z-G}^paTnn!RJi-#FqCc*nUw<-TM9)vd?$~vk`S_~DY(xqE;k9^)xDNj8 z*}q^J#~7?|=$cq&cqKc{*!@V?$Lm0G?^LugirS0bDEB^%1LV4s&qiMp)0I9SjBE5; z1JTg$yI4C_(PyOB*${euD$T}o9#7fk#b8=U_;)xU?t^;NF+UK7rFyarPrXa)juqmQ z3F7GL`0B*+>~uui&+gnm4>=9qn z**F=>x|?6%<+nC%j{jw_Ap~a#XV*BaHBg7DAVFN>t-tdRPijj6m?%uxa38GI>I zzYNCm4;{1Rxy!JFGBF!oP4x@Oj)MF*RF}!iM~-9W)j#k@Xk3-1xc$Ro*Z{q9 zhynx~PyuqWMORP5Jnj6NrTW|4n^kS;R{TJGllO|<<+k0gnvBi{eWO%JroZAirU>`U z4W(suVE%a|q6s99#OKs2gPC88Lo2j?Ri!RW7j9zwQDI_w%RzRXD_=|hm-6pYhId9Gv}g?KCT0jMa<3%!;Tc&NC&7SPyu8N_McmOu|yV1bk7 zdp`fPur;gfdl7&OkEmmfRZHo)H`I~GVisTYPdP!&fwap7O=vw6IPVu^k14a=T<6U9 zkK#y3xV=c|DnEtDeUOk+@7uFAr}DD_nP~wEjp<&SE~N2SyzxQ}eZvM2K!huQD75Vp zF2gVJ`dID)p*7n8T{doX9UJ8i;Oh4h)@sR~At`1+qirZA$_Kla|H(!_2!CW@;TG9R z9^yD8jINR?^)~EQPAAz~WbGT6AYE*7OuqThL+vU?T(S4xtopnKU$_|oEjKP&0!hbl z;KBi|2x>ebF3CMETQe{NsSmgPuF7C>wG5B{C%rpJfdld+y{Y@(|D}*C%m5Sg{IFy6 zf%C%vbNu`#^-muuQNFr=8KOjhRJS#oJXAOh?6sys_mL%RiD(z-mQ8FEcd5=$qDb@A zd9lq~3YY=3X22n6E!D{^e2e!!?i>OK7~5EBuGm00TdbmX`3;>jwf_0pzXO~~GF)qL zB}H+en{I_5^~qb|#|47ng|Ks0AHjiE5T^hKk@^Gya6%~54;`WUBZR%0Hh!47g^nO! ztym&ulG%Ad`}jj{V_K5 zeu;(nsEL2c_-(7+M$Agd1N=DuuryJ2=h8q5Qn=vS_FAFbeUSCU;3g9<1>{=JaEzLv z{v4Azf&6V@0&t_8HOwedsMA!0)`?7*QT|2vjiq{>JByFx&bPfbH>;%O@3jx+R=clm zW9wDLJ!LjH#AV=kc2ZwOTuwe>jjoR3f2matMi88eJM-&A{CINY|8crj;&{B~ya}cD z#MQ7RJ{X6LgP+=xNVlkbnrmAPcuTHv%jJfi>fj5I2dW(?UqPGQElGftG6IPfm8h9x zfxk9t&%R`}Dsg_UTq+tfs{6>v{($g!S7qvbv%DGU?beu;fx~ld07U#kNSLm`d~t{E zAFIR?;vScL^Mc(u9Ee{2#G6WzF6sSv=WHl$_R{-MduPR9LRD7~(~fFvBz)UY#*mV* zaJpM5;P%7g`577I4ggnxI)TNrs~;c)`}x(cMF!{wSKmASI`k7~8S+5B(fAJ6wsLBJ zQ=mq%+^S-SDaZft_y$lubA7~4 z=zYX@qwfv%=SeKY{4<-WWI^2z#9ub{z3ghi3sem49`byVP`L zE;KLGTl2cvF?N5Zd?OKvwBgTc+O58dpga0RLIRYvI<(XmI<`hy?||vwTJt|BeO?IG z)C}{ZbyudOqfBCZ#QrY(0xpaX5#V?J!rtmOC+4t0DATBFtL4WIVgs;4?x1H2Zp?)I zZx71DQC~y|j_oFf9dzWu<5fm~%w$+sx2$$U+=B z;G)D#ly&!Sg5Hu#Jy@709s;AYf(Cu~on+AoP5VUtFInQMy&&=f$fjI!WZT#GI9$iY ziyYi11_O@@6SJC9{{=o`pt0rRvdLh+3O`Y|ymnx9nncYj#$^AV*7?qE0t-n_;P4lR zeNWZFbq`fL5NHsQqP376WGO)q=CM4bsiP5zWFI~X4wiFD_v@+bs#E{*h9Sv;x`|>| z8JOmmY%FF^wEMO!zv!u5OBlr;*6Biw;ul;kFO<{8xxVbew`i{IaavCX{Di)BDw zPiTE5v}_6#1P|VIH5aXt09k+F73NwX9^Car-R51^QT9!{XSp zlYeEtL$MOC`Nu4}Qaa6JJXfol_DS6D7&15IMN-ihSn-LyIV#KlCld#dKzZ3D+zwBj z5c{c*KDoo^{LRoyGy26{^)`L7o;CipZ#-RU)FEac*9yOW8c0lJ)9#j*voszB_L6&< zBdlSAdFt+}9O^{~heh^rCN^phH8wUUqr~_Pfp@%4*IkvF5cdW+!5<3m9{Bf)FZl)A zHuJa^5LSHnw>;7S9DR7oIj`$#S3oHuVq+-ByAKDyr4ob|FdCRn>O{e5Y zpDk1Sx+gUC{lp&hA57>B^@9~Ai759p&$`E`A9-|kmCnSzO;md{zk}})7u&U)u4*Vq ze$CL37cjS_3OnOmatfoytjiE)c~8b@!eA8X_AX*uk%>}cvIxv2C;Ud9}fRCGAJk*wB%1S*lbLD*ppH~B1XdQ_%0?l zw(gpMse8;9?39U-i3RxSXrOjcHBkEvc;2~!9MaXR_7ReP&%pQ#!lgz(5Ib*nAfd!?84^7`uj>^Co>vzrh zc_K$#RNQSoJk?uK65|8K{ZBvQSucBNPp*oXM33Ve<9bnE+Zx)%v2HAj?lH# znAuhYsSi_&oxFF&Erx_e?K+Kfsb zOdZc>E3vQ$t5*C)3tkvs?EQ_9Ce%~dz#3Ze`&=%OvHanp0jo*#7tNSA zLAW|KmsM^`<218mvPpWr3+ZuziXhTTfi-D(iF?ZJe;dQsirB@Z`#(+EblE6dyml;p zuPC6GO?{Ab)XLt8saSfh!v;4|wL0&+%XcBd9QPhJkEV+LkjV^TfU1ycbPdI!t)|aM zh|2-bN@QFV7Tx)BVH!kM-skpb>xzZ{svVD_{HFYbmkSM97r#A$N2nQE^DeVG;(ExT z&G$J37)WAPw;vope(c3lmI5uCdAf4B@5Gok5L@Oc#XZTKyUa_`S8e<<)_(O*bXFy#(xB zxzM$f0&3Y{VDWa?SfSLbc; zN)|~&M|uLB0A$2#oNVEFeYHpzzSwJMpC+$@8&O|w>^8qO-DG+4 zjNv!6g^&TVWY-$UBMw&k*i-#~Bu5J;Rr*!)@UalB4f?3vT0C??>SU>UY5&Qwr8OqXplZ55ELR<~k zMok5MpZ%~FY_^W*leTj7nUf}o*T=ApmgIn{UwklSB%sbOKa)hFLnLTJgHBO3NwL=r zYR4X1^l==(%YjvpV5YGm3UYlnW533M_R5@qxLz!tWKx4*DZ!r)Mth^g$kd>MBelmx zt~%RD28CNucS)5*%XL70l`F9k<$HJqI;}{z%W|zfVUkfARC$Op68ejKcy8JP-@k4C;SCNd(swQ*yD;I#wzq${$ zb@^a#U15WcrTJLrJwt$r^mr!78Fq8^tx;EEV@R$XgcRikD65?V@iNjTLg4cx*&5}Q z?~)FgnnVHYB`En`!zS)MTs7PB(ZEJF z7wc8D$D_+%pAx1O zbUDl^{OOqTAYe=|{7YK>%i4x5Xx5@zy7<6T-z3?hH&q1O^VY^mbpz+CG<~Kh#eOUhxi-ae&Jl)&rLCP&Fd$br~m1p z9l2<4VBb>Ky-BK zF`JEzf_#@rE^PDit?SQ>#vXFXwTJ6NbvB(dj3Fj8Iodmd_t&T}*R1a&9{3#3lGT8| z@SEP9`?ok{ugJc}6ZIQ;ACM&hS(426P=UGn1dB65NU|8V1uX({j>0rlgqs;P8 zg3+OJk$rY|Zlok-!VHe4xX1@oXUw>~tqn%+ug{a)`LNRAZmjGxlXYVpo)F6Ob$(gk za(gBJNS|u2*;`C|9tvFcblaq`|32h~m~s4?-WN}|8A`EN3L64Mb;HGGOvAd*ERrXj zET2k31%;WI+$wjD{=n=m_kn=t7GkCBJDk(EYEd?(3Xt?=D-r>rx|R<24~Txkja zTc< zwfxQ6EnM#<-&4#z=8AM7Qe&_*wh8U--fj>`s`hIPUCvWeg%4^8oI2%%*fc{sc08L7 zBt!bj5*jS~i9DJ-JLa{O8y1BA8A?K~;}&-Bs3uAneL2;%H()E|vSF=#dC}8V1mjCj zNKTP6qI+sE{{}ZbN&(CBIu}E47`sx^vWb*yb?kGAD~*3gQ$yv?H-vF2itrTvMT1Z z2^%c02HJV7s+9hrUuL`W%dT0gFQuI|a?&xm{^=R(I#!-+6s}33Je)I#v*>0ddqAHV6OyL4*fB=S8j7o5TT(juPJ`0HciLIr>_3}?OgX4 zEUzDs2TtDW7#A+=&WbiL#kz~N&JNYuW;X9%#~UlVoUfjdsfli*CASfMcLUfQYQjyW z+#Iv?hJuCrwxqVOpr2qzbaq=egiD>@w=7Ul`UySQN&amG3qxB7Pbxqb47=+U5?jKry? z7*BTfXvvYXjSWaxZVa-mD1q~aYKJK=0d;{evMJd%lSc0K((8?H_3Q{?ehn2RmKI7` zJ{w$e4F3UV@2}-Umw&pY0a9S|y$4iQi_SIoGaHaIQXnaN!-9go3hi>|-~p$7aaL)m3| zTFR|;1M6(&k+R9Hq_hfVnv_Qrc1ZD>EDo2q)#I6ewp-(}$D%B8LjAFE7VGUP=;skT#%&W#Eoz z$Yi;-*S#06b){2(%9!A9enM@jx~%t8NV>9=^Y~QrdVYQ$P$Pm<#AW1YO#c~6Z`+r& zml((Gs@No+)}^t0gt$IFSf9@tRdlND8PYG5NYITFv7@%>lGn#p`~sR~Z>NC} zS|B4?kqJXcX`}GQ;e@XU6b?~Xpc14?hAOjeEB-bNJrZ(ru4V5d{`C2Vz@YoBdQFCq2P_{c)g3lILfn`kjk$w@0M*A6z zm&!oBoisl0qnW5NHx;EdyNwO8qa0NEb&yZTA-7#3e{8fk>#NEi7qbsDt`C$anh?BM z?{(-5Yl*o0)4}G-W6gdJz3JE*guQe;9XdkM2RqCa`0BTy*o(yJ5jK4XAFR}VSmxaE zFi0Vo=)0JYUCCFMM>JFteZEVzwk0G8(IKjY+;y-3sp^fT*R(|zgBUu_%xBAJt0Jsp zcPMa!B@Z(yL2+4KXx~K(krwEw*t=Kva|V9)6W0{v=RfXr1JVWGiiU|<(G_$U&$SuJOcx>hVt6Q5?;CdMzk7Qi}V<%M-n_rLRX1(N%~I! z*(pItUF&Y_-Be%^3uxs3f;L)gBa2X%O{VR`=yJfhqbRrV^e#z_4S8|(@ zi$-6DTk9jVWBneaZI2Zh&|RYxPzT+4u!;^FY{(082Bsph{H+GWKjJ8)5^k+K86S4@ zoQ+Swhj&sP?soG2a7%LI=i0sD-K3V}kHM9G#$Pq`GvmbFyKM<8sX)q+AR~FncDRXd zmBUi>3WGxu;r%;MCdJooAK*gmX~k89dp3Wv%kp;$_i|#>f5v+r@f*c{8QkvL78J7a zuemv#92O}tvBBW~ffdo{+V0!iM^*%dCskunbSdAIg4dhPPcw|%qR#A;ynyni{3j@~ zgM)`(w&0crXL`=WSgLS0q#2JT(&!HrL_#RB{;-#^q1^ln{_W>-TYyBQA1u zK-J`aQeu7hvfeA&>wkDb8Pz;M?mCA2^$n+mTC$*D#ULFy9WE186~TBKx0?}*jk|F| z)Wt|4W#jlC(QEB@8c!%-5iPUsNF-I&@HT|cmLQD8x%;b7FU{%5K#2un=b4j#mx+v+ z1y&oSffcv}xugA2`jJduv=eApK@n)Qgb#d0d2;Z^i_qQQVnyXO+|4JqqK|KE%hret z$n(dfD%*phDHps4r=JQlRtQt#e<~852W#ff-R;M(yBDTA#A)S~+HD4a1kl+seob5>&{J3wGZ5F-8L-DcSZO`vKh^R;^49Y|`$5>(d|tFTG$XU3 zpJ*Pr1yaBZiR$-SapRMcVrkg~eP1p!V&}@w1PM}9Wk{JvX2Tx+ux%o-D=_M=ZnYS@ zz!Od6&qhg*ui*AHX5!uq!0Jn(ES4Wq21p^qMwfiOTQUxD0g=WpX)(9-o@4R#8*F(8Kb#q96&x}NO%KFc~)Q) z!O6=vnQX8CB_aJ1wSG!|XmMN^`T{!*DQ-DmhEMQ#yoVN+sXf|vL#jB7XoNzPG}Pu4 z62-8)OW64;EJ)G^D~-W;;2b>i`uB!(8m?Ce2+$!)q)K^XY{CsnjoCm}iawb2`l& zt3rGuNqGba0~UM@V2ZMIy4M|>lO?kQYw$$i_@pY+*o9xzC=`5Bfn*Az0uuhd!#b@E zZk9=IS%hw~MtGW^xnE)-gn@Up^QU(^DVR=4+HU;W#A9lK*rX?{^<7N!<@+`LdYAMX zz)S;_mO?^sZHx&c=iFEliDT!KhD8W27xMD1Z zs=dyVShO6JM@s9KO?LF;=*?cc;pZFkf8K3#Q%s;?rm)uGUADrd?T+-FKGgBw#14y$ z^?~wpUB#?aYwlwf&ihtuCJc}wI~bq5)@)4nQtx*;=OWZ)(3qT%$MlImgEOF+7F(Wvxr+VwO-|R`JTOS+JhC*QK4 z<&URsl(#@H)=eBefJ zmYi8VgMIISzvGq|2`9$ly>_bX#diSqabFZ6`&JD6WG##i*r{I`pa4|N^J#M%y4?`w zVK`giZ*4T5C4(yDnNX=}Ps^muoAh`q;?}_%OM_6D7nB_%@i`eN>!VDjn@NC&bbFtQ zay`{I1`tq}>)F>UZbN~l1f#%mz%F10Qypre<*Ei(vt7m!#p)}lU2tPx*=wP%%aAjj z`rS^Mc-p7B4~|gVGL8^&ht*r#EgbCXWuwI~G|IrQ3np@FsaL9~zG6N0@}1b`Dzgo$ zorBx4#-dwN?T#_$;qR_AL0-Nf`}}dKc2m>E=uCBorx5>>Za@WpS9kf-moNSN?spHZ z-9$xU{hNsc+{m8|)VVg4t-x}5&PDNtR@-9iHOB`&eiqOD;!Q&=6$ywMC~lNr;2DNp zMAA@LcBk&{kgh==tpF-vO- zVo2c9*3@3p@51HytG#41s&67JG~wR+pKTGp3Ho(_3Dpmp3m#He^(fUr-HH;~a@lI{ zncy!X{PNnB!AoNlg(TN659*1$RkQ&Zf7j<1+#lLj?hV!4n=W&c8IrL^)suASBxw3C z;#36L9*QNs1!eiOMqo)1%o{7fXn`fzo>ui^b-4#pT+jc!W{G`cQgR?FP%>Dy8!Ptk zAM$z*%JXfHU5Db6k!Huh3o$oewN?jO7(BmKy}olYt4FZ~y7*OA;NPDyE?U~9IzQB? z+VkU<@^}*e*-r}Ax~z&;4%=N-HeT>?5RPz>3(Gd$XkQx@?I2PPFDG;hKjTz%UFkRF zeoKWK$gogdRw*+TuPhDDHxVW)NUXeAO|d2B65ut_krkq;+pf}RI$|BSAtF&B2~&F| zbeAxL=6yN4+&)&<+UBuADXlIEeRrxxu%qya?;dGz@i19h`CqzB^wBWVvhh5))2yL` zUi~rD6c(U?l{Jm=0a+z)djmN4*ng`KMURa02=b&E6SJo|pmA)8f|UXu>R$|fdrxLN zqeYs=Q*LtaJ;S6%|HdPP`96JSyG5Hz6SW8BKo=+FMRoT$t(v|dXU4U8KtobKnC?kS zdDU12HR&0~@b2IRy=-!h$0@7tpQTA_Jb;7b=qeCW*(4l!!2_dIjKl8G;?1B$GEix|blj>bwN`|V#GtT?JFaxBLXpoq6Z|cohdjFwvkFhV_6_u=XprXzD84EChXO|8ro?h;_BuIFDq<_KVm(3)G!H*#%=#4f+^6JH z>wmidD|8v3O&Vi3!YXHPhpE}-OZYO*D^cZCUinx9BBcobW#PL^)CxLVQD-m*!8FIE+C92a^ zg$~K*oCKQ@qIhb~JQzZwO89&OG-ZoT7^UCD*Jc-E9U*L^!IXwjUqs$m9~dxs80M$g zIAxbyHk;J>Q`o`2nP=`}U|9{Q+H%yrfjF0A*yLaz#uMdNV7K}bCLlo9ozSw>nE&;K!>ES7zH_NYg^=j*XIQig zR*+Vh@DEcYG*@9F*Y&6BYz>@J=93h|E?wS2G)7(LxG?N=3?##ir0~j2NpxHWqe+;t z`Sq6S`d})5gy43VEQvgekSs}9IQPow1&jA62M075UKt?+05#L`6r$Us;3sR@4wR1u%gcE7+`Wi6MAHgnKiN&+y zip@~FySAKpLDa_DG+5Q}4zmvG1xFXN`q*PuGb?&mH&t8Rd4JDUpVm&(izwI8x9-4s z6_Uzg$9(k5x;gr>(BK!Mk)2i@u>xG-B{#RVVSubx&BVS*T+{W4W_hl&_I+F;J04px zkQ&Bes^M&MP(2r)k?yt`r*J}^*S*DOBTb%Jc&cv{!zNjv)o!2F*U`LyFtpmM6fY_n zZhkze-@RU_f9$I_&nIX;b#uE$aw|?abxi-F*_I)3#QjCNM|W49B~4v&bkSkh3gIqY zZDF^w;Oh%>pEFqXe~CDyQZ{_+@$;ilP!k=&ve9ux!(TB@yr0>ygbvJqA`*T znb@%EY6*S%#MYT#MU*1LMCGL`FJ2d}99s3xL0j`y786465eT|0Z50sGcC)N&>7{zy z7K@I}mF5~x5+A|@RjU{S)B?GBOxX!=xK)8p*p*(QtbVhLNPkS4RQgTAL?rto3>_wF z$(_&QqLskt-UW}TE}3tVc*jzuJfws@?p--Wbyk#|aXDJ_VuH?|)aPt)DkZeLg^Q0R z)_^`Twmd(1CVvg#tx|t$kE648t&ObUv+M9w z*|mE2U}dpF#cB5tSL^|L(X{JjL*Y0rI_&kgPg>M;`D!~BrnaWi=06QYzG{kpw}_`2 zI~4YBs8?dPuUK|yIv5!8du!eC5~Nr$e6C^yg~(M8_A?*3qpR@Ib-P(+?=pTYIr#vl<^iXGLHHI7@^`HFG0DGv=Vj86nyPG2z{09X;)Tg_X!BAbvp2)^sF=9=ni@}ejW>eqc^ zG8QvbHT&A?=N{#Ja25Zg$>iIGntb-zr|*9Jc-dt1T1MDuOI<3uvOl7S=$r9VNqg0U zk4Ps6e$=P%RV|>k97VSIb>r@cQo~q(BStqYUb=SXi zc7Zd!(D^o3ufpvaFwlI=;b0_j^P9c!J6E)OE>Jg#ar?1Y&!25i-aL=0bPIs*6#*8WZn9w<8l?3UsYO~ zl`ZC3d`cWOYDHIUrExV$=O$i0VcBRIeU6>b?|qO}qxJR64!S=b6L?dD72zTUeztFv zsEGV@+Gh24B%dzo)Xp> zJj=b*1By4bds~K9uo!)!thgXe#42^pgC`NKThF)Yg@9}XXxA1y0AQZ30uwE z0t!Tqe{B2E^9spnih`ql+9OL&-WgwOt5O?#N#X#~@A+iT^b_~+0CUZhuJ)2kMlSg@ zxa>tXA)G{lI=MWQN?*sFT7q?-)f^OWVOB4Sc~yda?oxI@>01@4s7cGI=>8u~SK$@q z^Yx_!1OygbT3`vOrKCY}iKRihQ&>6#L<#BcTDrSax*McBq(M5R~4BL01wRF-=gYg=?a`F4h&tMW+40r|?9 zrx8(HyPFK(x_=EsL^524Sc!C(k4OCjNk1;osTWbYmhF#=b8^ZTRX%vz^*^*m-awDP zofrEhL$SD6mD=}BROJNcBKQSNFfP_02(D*wGV`Nn;Uj~;MYw4((4^&S+FY50j6ViR z4c_6@$sfclPdC^n^=7K2*;-->xdY!_N+sV?cZ!zGbKg_?v&dQ>Rr}4k4H-TLTDweG z=|jh-z(oZKBCuO7x)*qBG`UjPBOsNi&bC!*ogdRwd_6Vtb zlaOmQR#mG&(Bv6bUb>Udz6!fXf*Q`1x3!`ed59TOE>_fV&;3d+U6j+d|3S!Q%oOx` z>f-M6^6R-*HrVlezt84ur8-wXjc)1**ihRozf7G3ze%3Jucyj0KbH9Z3%5fWwK^yL zTt&0kLZ@4HGxnc?>*03=9Wx?|Crx?$fU3VQi_ZONw!XWatMUw-_a-{F^t(tzN@n)< z;!4iBRU4}krshLyO`$w510J;!(6WLknp+SE#~>$PD` z{JRZ-2b?b7#*aMgG=ZfL_uSXUkizxJmbuA~7 zEE5XN1AN)hJ(BGIB8}im1FRVUlX`3m4?HYtP3;`&d^$T?R#V*HQmdV_E52~-OZCo* zCT5R7Q#5yh*098PysXpP)~=pR7s;qJ`YYCbuNBF7Hdh&k%RoYF(fLK9y#{f6VcFmoE%?9pD!?3vq$kN7ZnC{V|(I{q_wBse*fWV33 z#*d4;e_%R{TU+f0qZSK^=MZ^0;cx&aAZ-p);q=}Z@8_c#^&9Pq1j(24xE9rpo3fM- zA+KmrmckUrWeFpHj?!FYY}}k9V4(by7Ir!UBq5y@(LbNG*Gt&(o%g0xNYP0SO->jW zmZ#z@ZQ0kH+-R!D@>Z$Hb}qNZ*tCE5Z62UMQjs_Nl6Axl&McyT6;7;qLM$+=w}-c# z0#Nml(!kme zSGK=8vet}}uv!dUs0mI6aV_J9|0`d))KP{6q}^W&@*Hu&LZ@aVz9LFKMANP!%XdM7 zNl@Y|=r7@buJUQUff&IR>;0#+V3&VB2!ZLwt0fU_iLkK&&JumLr^)PUng4pJhy%Oj z^<;Yexp&Oo{|G#BN40%hQgm~#j}Q_JDsQ5?(_5-enJM&C?#9^)cpSfx`C*4c=r!Y5>0IguEY=ONATG*18L9G1dd#8>ZIS$hi(thsJ-Jy4ow!gP! zkZg?Ld%d(H{Vu|7y7C_Wdw$o+K1aiTrS$E-2#`iW2wbr63x|r!0IB@)8_!7+JD~KN z&9t-C>wZtldKj%U$^DyqXo{89?}po2aviOnN0#x}tZ=|~>AlFK7T%EJuIQB#-O9LD&<(+5s8Djdbh6#x z$Gorp#XBl|XD`3SJGrxEv2oI<1%>Y4g1+2|8t8$G=IGVAJKApl&=l_>bG*d6(}umy zDat5(=shTIhe8adki(^8cr7A*NMv(dS!XiJ&n*5MYIiNDVa4%;*fdlV_g&=JTa6V( zu3Hd1KX+o`57!kDsLpkLLD9#qJ%8Y}0t^YGNvzsF;ix&AcKz@8Z)j4OG4KzvhQe^Q z-qq6+ey2RP(HZd7+a6(iFaQM?g46rH7ImKNTKk;ET=Q#JPG6tFdMK@JYOONJ$*q== zSii2HeZfawnjsWamEaW_qrk1f*l>wUYL@T4`c^xz7l{tzRW#B$0JJk57yQ$s7w@bZ zG-h79*Ya?PH$G0PngDtHw2`7vqwoj5CdxVNRuH_eRUY9Chp63&%(GyVCJ6vCeCr-> zb6W;D@qb(>q)HeIy#X66-`npwnJPAt^?KIe&5oxy7`OXs&rj5|7-z@|X7=f$`)1>=E(MY&sqCPK$Jc$eY_Xx@E$#Wf8O^y^meQD^dZ1aKQly) zf5VQz@o#^ywI=hwF@fFKI(oHSd2cR~AmtJTpR=@*(f*a6D6nPCemx%9!z_*;_dMFK za?$R(*})K%Hr1^J`V~iDn$$m%(NvQXVxkDvv9CCSi-?~XtT+~q{R4zi}f6yX$Bt8zEvw7uA&8)qf=U5rgM6%6p>C(Gru3u@d@qxdh zilbI8h~UXx*%Jn8NNIjFhPC8&>WvW6eGALF=#a>kWc53 zP8p4ZN)^}8xBod8Rb_QNi;kRF>0LCDo7a^XO}eD+jtw!Dz_O~g{^*z~f;abPr3P!~ zyo*eEkjDRId>Kx^fT&%CMC$zkA{|J>RiDQKiZyseU~gFT+pHX83P2=TW)(N<@Ej>G zZf-Y4hk^3gTsl*>uD$R|LYY>d$fnikt#kd(bqXqullEoP{LQpZ%gq?8 z|Hx^Z_IbRVM81D>(13(zx|3<_5p+r(7mT*_9N7~>IY7%j(IYh{u-J!aXNKe`Q2RU! zQXw@bUUvu6^HH|%Ur#w56yTtUyC;Z=ce}tk$3{H- z`C{64>r)2){rq$Gad5wZ=iT& zZIdesjgj=?acA?#s2}9~+iW(2gwI+X(=5>Qy!HjLXe2mfUEt$iof+k15vql#+^&(F z-{A!PBkW|;_=Q!S+uWryriDdYDku@H_?xZpwBLp-wv+SV-O~Ju(~vs^Rl4b6}w|UQAZ*Pe)EG$Z+LiL z%`jg#i0mElO^!aJ)%!6g%G;6PK(q@y9&M?i?;n2QMvJ6`K?f%B-(4IfesOnH=-?@p zxUZ5Vyd4M-4vfwYhy+D($UxBe$HpAzf=w(@=yC3%8wdYdyY8o`L@+N{(6LP=<$9?b zWf1+c4W2g4XB10m;vA6*h48AiL-O83uIjY2fI5%;tLt}OV8_%3$#O4^W$RHGc*NwWv}x?V zBA)*PPHJtH-Cu`+iK=iA3+QI373{2|0_VOL-cZ!$nk@WnB6LJof9E}wJe8O$kPg>C zAc#S4`u@RA)AyPkDK2(aztA~UuxGo8k$w6pVBqf9ok&p*nmN%P#FFvS-j;lH&9gO! z;Ua{*OdsLl#_f$y!x58ul&t=yXhRcACLG-FKddw34XF^5-*uz!o7^C&_FgXV}*&n3&`V0-j80?~A%Aw>kc z`DOffQnp@hs;G{~)1J#JUj@a|*QCwuB544TZU26zu#r1VB4c&LHU1!UQ5==*=h(x4 ztep$-tMr_fD*}h#+v8{3<$FiS8T7k|x()0$rqIGPNp={h4WiU-s2aO)HbM3g2j$rW z&p8+vn=gLks0n4%G>>{*Vm-UjbwRxWy^>gm>XhDlAt-YY{H+4Jy&tfL4$qDLb&Iec zSTx{yJCd(Fly0nv)rX0W^v0Z&n8w9stfZUs$da!Pe7_qj%9ff>I`f!+xcO~}FpQY= zMEqrA4`47gwjKF2;3}iJSgNl;u5Ldnm#3@@YGI2y$?U+{I-FtJI~@4;4zGr&h8bqGls&!H&R2E`)Fy& z5xN(@Hyrynl40`U_3l*k9NG%b>eXQXw!z2B=7wrCW&bSXTYWGA6zo|l=!9a_#n)ke zMs0{_4YJNPs4EWgO19-2JBVf#rsH&6>|2AcUQp0+ahj(FhqSq=^j`fBUl&Do$N$K^ zd<`I2=XRpvshWy)vs}R&gzW{Wh?n4*#a9<74~q$6+9idHF;p%~34n#2^fhdYY#11P zP%xQ@%nQFRuv(Zuvv4c{>)hhUH-54y-9Fd%2a^)T5`#yH+#eB8doCmv!!i6+@{h=A zo7Y`aZkJMKYXROgdo+-5Gf7g19X()++o=`7#8YBo?dIh`vF1J|H4;i<#LbDFjt7OQ z*3l^U+O@J|*|OBWIWmc)hDa_4hJ_FdSg%bs+T)*L;(jHb+)S(jxqf}aEv{F)) z8L=Rr1WLy!-u2vD0xv*Kp=L(`{``}BbT)DNRQZ`aE;JuXrMMDXuei@+I$u4~P7C=5 za&p`B-2Z&&Fm|q~!*U@)_XH5dCbWusr>u#MApZ^>6`W|Cq1eT^XMOWnAwM7msm4dr zPpU8svJUi}=)J$siOL?rr%cW9uPPI4wv4mRP3})@m)oj>*(SJ4&`&Wb1iYbQlgm-S zTR9&4gAhkA`PDESn}MAf9`#RJsHayzUrb3+_!AX?9zhf9aX>3#sPl53tAihR8zb2) z)L$TxmTT)8$x@xRw;DHu*5!xSbBHz8bWLzUKK2yA0!V*}_vl=zXI(S88eN%}AqfwH zX-_23n6Dn#F!B?>`i8H#tZndhy5cthRPOY)DI^9loiQ_6gn4Q9prUsntKmE(d$;`+J)aJxlIf5(NPcMe|$JE8B%;uP2b}?)0jHGxV z@Z_8PMQkDNZJD{)Och9>M^qFI%MzkOr=YKJSm3hP-57=6 zH6GD?>lI=KeZwL4b!e#QG_f_0aFhc`%NJl^c`Uxq=V^k(}R z(ykeFduCl(hwL~0Eu2%o?*S-nVEJbCIaOMD$6m%E4_D~?IzVP*Qw{_z)}2)8@vr-j zJPh{c%Z!xN5&yW#pabW@^4neByF+)0+m4NPhU_xdkVn((Q`*pkN**;#7T|M0axHzR z>>(W5TnU|o@wT8qF&e=_$rH8hNr7xu0QoE8;BxmLkUP72NF9N7LE+J+`d&_~B>Vi^ z2S-zYCqZ7H=4b!bOKo6y6{C;)Tg6lNOwEDQK-&UrzW;1)td3&hqWD)@vC(u+l+Xr$$q))GA_0J1;|ZtT3BAzs_aCFbM)@4}I_jiu%>5BD~}f;~Q)+uSj8sldLc zfKlVid|iOFymfwD)>Yr4s@t2)$XY2v-HmyCD?P+kAi$BH)TtBNR)}L*UQ>m*h3=OK z?I0na;ZHbAsUwcwt`CbZzel^-jV$?D{wFH1WbC7{OU0u%Vo2%PWrKT*`~3@Z{taUB z-D1B~=Sad?t6e2Ybc+P=^9%!N-CtbsL7DX|lJxPY+A-tBsXV>0zl&! zZ`#ow7udPEFjO}u11?d;uc~3D#mfl>85Z<4Cd>!mSLBRo#b3HECH<*83_L#QD@N;6 zlLHQw$oJ;uCK+GE(siX9`67Xdv*JD8l#{E0?FoIE{<`glW#L}L=G%9}bWAlK_oB+8tFhmP?I4N z=Sg1qAo=m;qktLEN7zVHJLYE2HTFRv(ED>siqQWm6*{RWDk%*U7nBAk3m) zMu)BArTxk|=+;{~1CO~Hgdq06-r@t; zp_|AX=omlnC=%JEYDvmdmAps`#@dyDOZT`DYsGJbBT%XVju-Ku-iX_R=ApTiD$!Z> z0ikvZ#h_B!Ni%JFlg>{{4#?P|#c>#|=WQ_-+{jdJ7w6_dbYHW=f#ilChdZu1V>aH# z7FOUI+)Z$F$Y}bC8q9ml_wK#LdSOZB*Z6x7cRGlFTmH+gpm?G784%b!lr~pGHN$}+ zzv?nIjvzC`EG^#)yTiEVN0w}9a(B7Z-zf9|cMQb+*BAcODL9{7`-B%lrAP)gOKlu; zO`qwK`~{U%_G~sy)Ya23F^)~-I@;%>I+1=$kWlEJXcvSKJG`eSmZ8{`?V?VrA|6)y zNa#X-0+Gc0W#)@JVs2J4!0m!hG9*UJpXs5o?+5QqcJ-a%nvKdWX{sn5C;7sXzfHsZ7>5J%}SD{u!qPVW*N-aE!Yz^xxroJ;;!$) zc<_4!DZhl`OKV&;3BUDRUT{5mr4A0xn32c<*fR8bAqaQ%)Rlc9NW|dX*ONvU zz91-kwu)=!ytUNXZr8OZjh9-e0pk@X0SUCV-yI5W8k`TDHf$3u1(zq6OSDk&+f(1*Zd;#VUmFLv)&H*rz=##Pm3)8jo$|~*R(LZt5e*){ z@e_CIqJ5c1@;LE2sKEHc1uH`Zf$#}dR|l-Xm~%~3QHpGMcz$zCTOa~8R(Q`7xBNa` zQ5d?(>A~S9I8$^g5Q`HjtuHe65Cwh5AneCx?S<%StE9tDhC? z591)4>=h;6XWPRP_1g`QbNlEjF}!$#9|Q5wfJoePztgP;#3S|`$_aCEW9xA=MZ*t- z(8I)pDQTl29cPnCJ4u;>I-w}-k)#Fl36AnTRV;z{495j&mIB6$TeTtQRqpTH-%j!x z-B*~WaYj||Qv@7akcPC^Np8lr-tNCm8<3Ypy{1Vj2iM0*#L*zzk|54l*X~d-M}XW% zqjY`6`(F$>TT+@x8o6A~%1Vb>xZks@eGyiMF#4d1&+WKPx>vW`j?M06%zTS?OkHO3 zyRr&84x@u6tMshx*4o2PoiC5Yw$&;<{Tw)-^ZMT0r_{|PmwijSQwkF94>TcNv{JME~BfZIj_7ErarjGlKXSlkDCp=>({L!g8kSMAS%9t^$#_g?giFCRpqSE~Z z0qW5$!II8C)@UKT#iu$zzI8SPYP(?^X-o=CQg+gtW-lWZn4g!2e{I=M)t?|VK9-1% zLPY&8tsVZO7-#)1ak2Skt%?SrsD8cWOWr|+TbrtQ;9palboBxn!=jNEI_Grbj77B6 z^kGg0u=|uZ&&z*Hz%i(?FdCn?!#-=568>4o6RI}_ZeVM7gBS!8+hU`(aoSR6{}vtScOI6{ z;$`Xm%dok5M9h*j7en^VRN=L#le97a!OH>^SSw_~>+YV07y5NriB~$hkqheseAZ5X zIVyuTFYWF9KfK_QkW|FVt)F)n;u0z%YzXcMppIsmeg6iLS$xzuQZ!dr7f@$OP_6Q1 zC3o&G%S)2N{$C1`I6=|w#?#rBwYbJSU7K2gf-rvZWa?--UfIQznzG56t98`sP=8B- zM9Hl+I+O`{*h)V?aE(d+Tg+!lE)Ie(j8P@pyBGpHg{jOkMh88ZIuwZOUaI$l; zJW15dm|)Qu_H|2!{j4>w&CQm?{4(|d#IJn%1&f0@k_H@=xib5W`8@|dN(`}u{@A^) z#dOjalb9=1-CihxIWDX2E4vHdjQVM4O>OJ0NN&c58@6Q|3-Usb6XL%yJl|OGn?(fRBR-khTY1EG1>~#>dy)pe$9Gr5Nd!p z$PCp}WpJ=y*0YvKnkwwRT-e*_rM~MoFU7CSA8Ri54!fi1zP!)(WXJViC+yiA&p)wY z)v;hWKW;^xum(HX-K@LKy>JyV zXO|L!#{HG4WPI>c9hTacXIz`l(Bx7`5x2v-s1fLlk2~(ax?UGq3XKdCq*JHOzLN zG?q_%^J-BGOKy2eA_76KN^EKGl6<{1d0J6DK+BpN zBA3w{RWmQsai}#($Gl$5Svox_r>Hy zrscNHVpO}ehkM!+EH!sy;YUZiU4-|zC0p(pqBh})>}=c*UnR0$5Q%LPs{>>E2z{>@ zyk|}Q-exIl0)@V*Emt_4lO_d3<9m3$;0hbOp@xE(PVUw`F720^61-TqV$ci+H3;fh z#$TS>RqI$n-rgdjwi!my6?A%qY~cSy<8}oQC%`9IS8hZoC_-uwz2>C*pi4hh@I~LB z;8j4uUj)JL-pxYv`A4gj zBIYxnA%cGj)`sLCkumHC7fohg01_GP;Nz4ne|Xl5agS_{UB06e(SYk@eDMApgGekB zR3^jX=}F`{j*c}5I2YY0v@BmPhgeUMm)`n(OYQZa9WcA4Yhw#VHrk}5GD|A(;7txM z+wfa}T0?8@=}Srg4zLHb!wV*h5CHPy-XdFcrRR%_5)hq~R1hj&KxP5X6(EYsfB?<> zcOzSJ)F`UKx?I)o;9dGIt5WZqpTUyJhC3XTejV|1;rzR%F(u6>7YK%Yy zdH?`lc|SzH5uyuvdm{M|w@nRHe|#;>4gclg{Kqb|A-CeY6&UMJX4K@yFt)SDK|y;J zMR`*;Pa6ZeN85sIC(s=zgSDH=av4`SZ$g89T+#!MyZnGEMB{FkOGX*ON5Yd2bGiMym3 z6hdsqpy|pqObHF4I_8_9i0L(_b9Bedw)_Yn`uZqNajB$F`M9}qZW6FU-M!4W1jPPU zC!(rO6dm!4f~I`!P5B1P2-jr-F2%@BCMpiLt4G%AL+3_0kB^~x*z3$%rxPT4$L;O) zhTBzjdFo%*lQW;d4ZwUn8Y0C$N~P0cY{E9zhQ zKjyH!_JF@oznC1EK8-M4zH@B5m8yLE8F&3t1N54)4lJ&Rw^4LPu}o}(TOQVjeEqXB{ zM9RhYwTk_Z(|GZJv)*NeR2#%fjWvw1tPi zqx97N51Y%Ui<>cy+$&I18^o+t3rs{SO?K^1!Z0bcUz$t#heRfA0t$?snb*vc%A$X|N0S-g0%Z120^*A5|@Z%-I6h}uO5hqi51+OO| zqCPx3ZW1@PA9Xy6{-Z!R`zUT8Oo{d@`)G47env{%HujBrG$W!kX)U9-HwgGpu+*Fc zf45N$-EN+w!zPm*#BZt2Pg#D25-;X~XMR7?rFThO4o*pa+ zA8$U~2gDomD`tHX^Oe9O^v5k2hqfWYeUWmS;3C`km^z8x58V-B+C#K2XBprGe}30Q z=7-r;8^13(H8{WC$oH`aNf`WR2oUk1r7;qo)yzGg+0UA63s9XS=NX(@70L`C4UQmlYUYr+9M z(={z!@I4hW2^T^^)B-}p`rdRaf`DK6hN3RSsKsFP|GQn?IfzU-hreTlBTTS}6t2!+ zMwsSS+R^e(1N|f|1y+WMHQnR`;Nn;_1b|3{m^SOdeJ+-`b0&kV6+(#eCmR?@9>rH$ z7!Jv|xg^zCpz1CmX3hK|Ux5;eu6Aj)#cEX~VYm(nxE|TtLKjbsH<8qzMuH8JO4`1E zAjWdV7Z)WviK)MQ<2=^$ry9k;e9-jCt42!E*%5!Bh5_}$Ut50mT;OX=>7vbN2yiti z#VMSA{lkT}FpO2;-)BHDsXJ)rr&*}^b1 z(OeC_RgyS5!NmaJI;_KEFjeS!jCw{RC~tOZ5RC*Ra{&G_Pc&%VL?e<@>1)}pknr$r zj*R_qiFe(3tBg87Rh>v3yJAlIYm*my8UuzC-u_}QbVR#SS)+4Tp7S}IdN{=TBxqLy zH3A`F<`IamYR#f9$L~!f+3XOHeB9r;i{Sa!z1;$HF=U8+4pjww?1sp6V_8!t4y;nM zTvSZrPViPOug&T)syC34zB2-EO(7~D7jd-R_=2!V3nZJ>VJ;B;HIk}rDXAZe@WSj! zY!$c4xN8nwGola`VWz#eRuoBMst5C+kNa!SXi##FZOU3oXZ3czW}X|b`GZ+Teuiyn zs~n{+TZg*Y^ChQ7%_CEM-UGkZX70q7+Xd4z4X|Aqt z1Ch6|hsCi)k&A+<#AvZ^Bsu`Ma-RJ-;BMq5kGf-$UawqKiO~Rg8H% zp1<_5&xP16@)z9|YhRf+?&gGOtzovr$a4mfKdR=3|=maX%qDzEbIG zsbIeKg`e<0iSV=3rQ+Ay9tq%b1Unl1LGd){``FbS`AM=KJ^IF*XN(woUFp zl};+_GIkV#K7X%Y?28xBKS8e2f1+jxQroK+8cVtwIF5u+*c4?bT@?XJproOy!{~MV zu?hDBwQc(gcz4rrP6M4QUth+Imzk|DcIZ)3&k0(KPINqNCSfdLEI>gZl&>Jpi{Bqj zCm#{Lo6e550=HoL|^R12UU(+)1 zM6U$Z7@6o$g3pB)o9eR3lSKqSbqXi`txA8)JIl0#sS<46wHm-bG^kGefc6=Xor^N@>E4sC71IXS0DB+lkAW%DUdK@ zEKz{sB`sJIFQM@Vl`r|6G^ND(J^kOpa$L?oVWS(liD<{)Hm1>KIj|()TU&yf(#J&P zN2Dq~&aP7>bg9;xhSLo=HZ~qe)_mI%TN}e4)jbCSE8~9AB{lQ`eiMJLnjKa`J?@jr zJT>b}z|6u_-2#cWCoOm}rvr(4j=I}alG^u%V7`&TN;Z2zi93D+tzCq70w#wfrpQTO z)BU{?p>Ll)G?<=m)457RB!73(PUWA+7nJLy0``QW{u*rZjc-TLGCHE>mN}pKbvI)m z36>j;9d2@SBdEY$uCpuck&EdMm5c!h=tkaYLG|d~s9};xl&A9o;;9hpcai4C>rtD6 z8VD7O!FEz^)LfgKD~NfgsTlq0&D59{IB$VRydL#k_K!GLPwx%=c?-Z%(A}@w#2L0I z9!5I{Syb+QwJTew0kAACEk$iOd}`9)k*S@rr;c%nEuY0F{)P@ob{QR5@i!ID&gCRT z4V9RwOWc|BJZ!r(XVe{WGC3U<0wOMwrLzxgi~-LdR4amOD zFgE2?{L;!Z8}hLf``wc-?>O*z_us?isvM}SpnC0u zfnS2QJQCkx*VZJ7IGD!wV)&y)o;rzS%Fcl0mSZa<2Q8AfmFWc(%m13M3L!nn zcVRKt25(7pv#W>t-=>#$?PH7Ug`{6 zNZNAuO3QW3P^ZCtGPYe8@#U@Gee}Pbk>$y3$>TaF_Y51^(jB4>MU`oNa>xDAjz?q% zV_(;h;pC2C@~LH>^i$1Kt_8IRuPji*>3+CEnUk7NDv5-A{j53Gv{&_8zY;n>4advy@Poem9*|6D4f?K&wo$4 zDbDaYSfC@m^)hp)l*7t0k*Ga9b|uFv6XSz#ZKcx?4G}*y34aJ#(J+{XWyu>Y)z>!c zg8pK^3NVahMBgjmn!EDPd7o4{=Tpe>C>0ALL+Mr$&UG;x6opQO+9P_i>5X<*|B3+f zuI%+%NXZ8yTi18968!B^d~WZb>D8^wR{FAGf;v=8aP(O6{ryaFC)L+tXa6#0nrR7$ zXd@xp+{u0$;f8c1(91GIuM^9&+P<~`f<$kxqY^4a)n-4n&`S)Mr9Ub0yg(Ri!an~R z)L)>582YaOn5QF_@>u`A#M-F#qakw+N?EZObEv4w9uh5aC5xT0<)At`KKgF@kuW%v zSE=^)q&h_{un)d97SlC&8axl~y#3RV^QhBHZAjlF&`oJQm2-d(j&D94cARp7*OgLn zRG7Aphh*llndXp7s^NhWwf{nOJm`qc=j1@)9F-$fA^RnXX#cx=X>Dq*7pCB~pu9*B zK>b++yO^vz{R}1Mt^usu>pTb=;_TcDlJw52U-)@HI2%; zRBuK~y4-l3y416G)D$D^>9{GCOhU&bL@QDs^@zCfI$2#>!jYQ@9zBt*|BhW85sBVK zG+=z7vX}G>&R=!s+=Y-}h12V{M;00v80M%@t58E3cR*|&SnA*6BA2eCz!jG@d)(?j0dQAsrXs%0Gpi3zKsL? zKX+_mS@6CEvbE^YGSN~ifZhnt{YxZ^T8N0Y7}GmW&-#Ow6WniP6^%d=txrsVax(t zibkD7Xbi@jAYPa=<|gNiMLq)SmC?dS`798z^sZ6)U*6I6(AtuB@;_c=vH|p62UuL7 z?^&mc-GV>h`o-K*u78rHDVzQ z`pva%TLlw1%qCCLTFNKYpc+{R>4W)49G#y$R=Vp!;Va)I2Sv74dS(gzJ!zGfh^(}} zW^}<#2VFsaZ@faDO-xt=X?g!H*wV8UFU?U+na~8o;(l?|fgR~hLedL*@MnG@H78=%()Viz_qHA(gSyRJ4uZbC@l)s~(WuY$-TO%^)>8|cEvxHF zOetbNAPNbM%FAMO5x)`&x2H^>SeBKUsw<@jkEqU)eTN?IR9)J=DnOKK@cm_S35xY- z#m@T|5~I`OtI%7r?W4wNhYdU1aI5+$tE3msPkr>-d_E_viR~z%_BT6jkD>&r&!=Nn zae2aOoo@a&NYTRk?U{&ZOJS##-AZU&D!Lzv;@7kEm_5WJZjJ{Cxn@i{c%^c*H%{PU zeoVnS=YFecG*{ediV(DJ`srmO)0=#7@6_wc8~tS~$eYNs{W)n3o2NqCb|5t>;!9BFoc_2$0}YEZ%Ze0unpW~6 z#m9p~tdK8O481=@kI#49t@*OO^v$b!|9LvOSvZ%Le(J(7RVT>dtY^W^Y~@0uLC#iiIXh+lU%JmemGJF$ZK)l{p9XT+FD(ZjKFDP>3? z?e#Z(HEWlII++~ks`iv6v6(MRGvTK1z0rWU+lkNs42td0N&D64v$v-DR*VC7O;j*7 zj|oM5NqZgCFg(ihKbtcqT&4m&Vj4@0LxFA})9hpJ=va}s5KY8e{ zvGficov9N9mt<--B<|dfJ+D;qoKc1I$c}&x9f{1Eo9n^RBy);N=$i!CmYl9xu-n;aYg{lr$SSGt*D2R2QeRB`I)H45u`)zaC8Fp;xRR%@aHl%e zF9$jjY6gv?7yb;JOLh)F9HC`xHq;(?Mvp|zb+z^H?M#z@==~cY7Fo-J7yKN6gKU|w zeWRM;(KEFEU(3Pu0#af0;I7VSJWY4&z(tRCUL-;`iQj34j2+Ci8n% zkch=Sdq~TDdnaE+fbc(^J=O;nD$%8+J`Nn~yhO>5bhhn!U$WpThgPr`5w}+0l&{%* ztPau_WBuqDxgSA_4#JFIpM_g8G9IUmcdSG$eDpshN3u2;=Gq#aJ)?NIXh=Ic&VtJkuajR=ql0NMl*@ z_BtN-iTqxUht|S`9R`B}C540Dl4GAWeHowaO8N8kRa`Y<{QDWgmhe0YD)Nc)Xs>3GSm6J)0HHoiAMaalWbpQg=yVcj3-3Q#2#a1; z63M7t#Duvn#l_7o>FwPujS-1HK#1Od_|{BKc>G)} z8{PD6KuW)oVufx>W7z{*R{*gV7rq!gg^)&rWCpwYXXoaz4=IGtKE>hs+8TwA z46Mp@(W3zu5l?j_>m+duV%4*3v9(oHIsr3B1k9>i#|dje;AKM3BP;D@U=`D8%h07@ z8$T|A2Q@hOZ<3W76?VmaOq{}QjJq2$m5{Gw z)&`7`$nq-G2rx4vY$D!!5tr|s)R%8u$^*OKd%xI}K@%;-uaBB#*-VNfaqaYpV(>8R zps2KmjY`e$FOghLI4nN7+%v@{k#FZCE{-_+CGs6}SSs5{sLM3RjeXn^{gzVdJM#qZ zDFgd@mfEN7nKOLSoem}!)8uh~c>)z_DFsTF1Cxf?_PKm;JF5aUm|ck}t}d7d$7XI` zIclj1PZ;E)gaJTe8Pg2Vc?W>EqDsm)EfpYiQ03^9LIg0Wi&%UGh`)apAxVM*to91HQOwX54^BPj+qyD6&c~58m3MCJVw32t6(OHDV`J-d0F%+d#_pMiM#7XRSimL=?BKBRIW+#nfrU&NE{?KPTW$is(fv2gTZ1w(te!?*E{2~4 z=4(mUqz3D8Xc?*-1@6ABC=aevSEqX}Kqs3=6%^1)3+|s;%`u~^Bbe5ATJNj&CVLQ8 ztFMp%$Jf~py1xjtKt`jB#-}SuFHK^g&>GXA=!9)8w#9>F&HU6}RR1f(=ek=XI4Whdof0mj}b?0MtMM9|GY&L)fWr0x*fm4`06R z!vdL4wCvvT^|g3?Xisr$>fQeLGICgssYz;!c`vkU!?6kwi7l`d-Jy7Jd3r03C$Gmb z-+Hs1sQA!=@QhjIlfAsyO%3Jn+#isN^kk4pi`QgK7Z|rof+^;%8=l6wAEr;ykW!qY z!ZSE5Bqww!tn^c&<7@EqB)MeK;*xlxfnT*IZ?j*zLU>2A`g$oWkY5jchyV0ECi8|8 zRK1OUhu|=NY+5rI!=0X9j@usM=muQg{}mDQ7IXW3B|q*5NB1Y<=v+5O#zTjbaJ^Pg zrXHln0AK@hXrtM2#(k**y8@8#kU3s0ikD?Y)6@0?zWPh_V1R80>;`EjBctVIk?G_L zdH}OJuzCY}HcbjPo02NDu#^1#lFD z=qoHSx4niHEycC506g;w36O` zj!X+xdx+KF3lGPR1SZf2I_fN#IxsdZd;<*<)!=Q@r%5x;;Ck}T70HA}CuxeT)0tyu zH!RSAK|jP(VhXepYQx?s;#qC4FY9;0k$IkG*r4a)x*K?yp%mOP-EP{1OadSsUlIMk zil%}&)#~>Gyq$EXu#1bss17%t?En>EwMh^8ziT7~qPv4ZN14n?NZwG>3F+(f;h8GO ze?{r^fz>lYxy~PP1hGTHf&}>gcryB4RRJV!u0I~@J}ut)^pohwvSQ~7JiUbKXW288 zLXrGftOk@+I~z@(xG=GD82+x)aWiDs0f9~zP2-C|252mUiT?8M&3Mw6rc zk>f?F92yD8Y!QB%w9m+3@iC6?>!2uW&Pkt&`Z~clzDRE-DO~EA%Q*ki%XQ)!ht@*h zqHf&&Nt>1s6J15MUp$p6$+QT@NDT&k4GiH~8Ie>PT?vVT*NHwKT}R6J_z?U|Q)@8@ z(C^$8Q|+&ih-v6j#fTb&zT=M(AfVtjj)3xVQZ;iX=F+oSoQlM^-~A(qqhSlLSumJp zuRKO;FFpiP)3`*{5D0Pxh+k@(P8znK5~P+bZ{76$s4&#}dCmC)sM`O3G`&?oTV2;R zT2dT}yA#}{xLa^{cM24DhvM$;#Y=IDyA#}tLveT49G>?(|4lBFmATiRI_4Ply14LS zw8`f5AD|~_LY2Q~dE}p&v+oB8tl8MZxWIBFgE_thC2y7CX2`7M-IK z-!&4>ODO2fl2U3q-|w*8Xsk#X2QR}>cs#ba*y-BalXJ$S6C%2*UFLW9l4)vWAs37G zN-P08>bKC}Drx1u;WB5YY*XskEChn1Jvh!m<|_E*uNrg}A&I$U(4Q6cxBEnN`8(n&*Rc)oSY9ubX)P)dj8Kk~7 z9rL{;G%P>;_0IK7sSUHvQ4SzOy*0&m)s4p;NJd=hIgB%KCi(Y#bvEk7)Lff6X|gho zJ-wSutTao_jPOy=4dwa}N!yNb)E7+1`{KF$)+;0cn|~tw5mHdZsA|b@Z#;}1Z5ZfK zeL6ut#tcoP;slrxs(I4_`C$Q~KUAF#da4=1T{uVMXhPE7PcWn2Ye=hbH3s zqE>k+66wrRL1r zI>>v?MRZUPGQLHxEF;e0ZB%6w?#g@l1Gh@{$*Oy(dnY3%b#uCl)tMehUtVjWa}_Di z>hGLe*Y%74o$&FrU45jW+x(hB=X~>0vPRi;CG{$yT3jMO58Ny;8J0JY-DJYPMykof z$3Qy}(X(X4mbP_Wch&XRoY?pLB16!<))w=f69z8Uv>B$9>mvbsRuC2ou_zsoc2-vV zo?G+{tBA(`OUfZLiN`kLH6RFi-NW3?7~TDT*$YWr?%9U|(JQ?QLh-J6?eOygG%$%{rSc8`t_%F}GRm0D-!i z5oiNRYmhzHdn08u(j3#$=p0L6NOl1-vH#Nv!L_XJ5Ed3$msT{1TPF8~uRqVlL_jBo&l|lc_yHk)06%u#%^&w|i$B&+$?^GOs zujVN*Gm2VJH43J?oDs-5q~vUTh?wFyeb^Ptr{j}9GW&Df8fQX@4q-uNjKxz>FV$1p z9AyqdE!>O2@G%_OFkgi1a%T6;kz}b zwPc~fbv5#8)yf5gJt*r#UT=)do%})u@3MZ~8c48E!h~KQZfjtHL<0p>KmWUB4Vvwq zJDXMUt>HbNKB-rsin>>M09*s1VTr**isb!*WVs6GV+ZIVUsPz8po^!EV3f(J-QuMS z+&LOpDxK0+1L)N`K^Y$0D zi%?}kQfZcMj0Zy|I~Y&|5|{Zn9N&+$;iyVHVD{#!bJEJ--7ErfBZ~azXqvvDd6vju zw+iaY@v7J|qFG-wE7)inisszyuY61hJIdox&GB;MfATxBw8MBb(^RB;_q@`F1<% zoW$LjsU{eH&R8mgWGC9=?Q15uutV*#lvnX9@Z7Es9C}8wtt`^%)|A0!jULLp3rY@V zyY?IdkG>1ipEldR7+4=5`8Oe~o4HVI}c{73C(kc{_FlyOMo9l@u zn+}glrHWbmo>F)($AhJELbfiL*Gy)?&vAWZqricYhpWBt8s?aKtF?hvYZojcr_&Ck zW*&h+;=8YH5&1*TvInX( z^uRGlMt8d&wHgO()lgv3^1#8CJ$|S!r-CCiGG_kp-MZEE2*_fU@sEF8vK|BVDZxr+ zeFyT+4Y7RAe;>=g`^^NynHKKZd5T%tr9CJiVpZ7S~%#@my$QG%78h zE-eqQg44U9F-BHgJc-^S^LB<68LhZ5{Abd-;2@oZ&z{IoQg^!+AM^$L)ZVn~O)I;M z$H?Xy=!Pfs9F^SA5{emxh^h7UZ|a`;m{eUCIpf$!+~2{rZ!kWUWM5(uQ+dF##Q`S$ z!$LrMGWw+Zz2j@Q*Gr*f#Qrv{2Vb18~%M=VMggnI=M}F?XoUALb7+^d1n!R5fVQ=koap#qNUXUdrU(u6DNdQ%X?jlF%oeJ4KSM%Xfh;T&en-cP9RC4+ZH7G2sGjx$*eO)-iNvGo zt-SnSkRHwtNi@8`Kb-~6<<}}~R+1LSfDf(8r4?(GP=+xFmjO~k%p+SdQOznSF(JwK- z)iF{}Q1&H4t&J9L(%NG2I1`~79{(PhS>?t|>We9geUL=rY2KZJDjM9t(XLq-dR(%I zvcWK$T%-oe8`H}?0SVFc0yI~U*t_}DEvZ?I2~XS!vvL(xm)qf$@y|7zPKoK%oGj^6 z$qgJT9Nrp=U?4PLtGTV*Os6j2+M2N#=B3hjq04?9TA16Hqjc&QV$@MxKor9rlDQ6$ zo|4|RFZ+#xU-o0MXd6M^|A$P9rsO?G;E~2H+mV`ngtG3(P?_@se0?Vh3A(k%LJ`Cd! z7~-CH-y4=W;pb%GJHv|W#>H;?du1o=xs&;iS=_x#0}Dm($)t_EK6L4?&&y~tc*GW> z28!wT9Uv4XgY80>gJ9>VEIt*g{KWwnBx$vL$aqhrd^h&#Y7npO#d=WIF@_51yHQpL zd^>$~M?!-uu3|Bk^h{SOpL-pnn>>SZhKQDyCOQ$AO9 zq9^H4$Cr>DYKy32ekz7xmeHJUWSB3&7HvY5wYI}Q)%IWq<)ovnt*yufm&HEeFS)1? zK@rP=xr%RkUMSoTXG=w@TNYPvqODR;NGnQ|2-Mt_xOp>yk1hW3cHZ2*!&$F7|AcfF zS?9$_R8So!g$Xk}^_=}M;M}Ecx*=Od;mXsNP!Q{!@5UN&Xa!u)E4m$&(Nrp zWBX=Vv#(g!1w)e2w3tlIOn7n>uN^~*?ew)g&r*r^pw=LT)V0ESy-g_@*V@uj8VNcD zq-DrPD@`oESa;)o3t|w%NH+mTQU@La?r{U|7B|#-&@`Ub4)PT1j*3Xka)Q_T4o3ub zU$bj=#_IBimMFI4Dq#f|MVZk;=~j^>3u)Zq$w1eHLWPXL0FXh%aZ)B+wU3g&5ko=s zK9}B$o4;=Du+!*3DALN+QAx9q(Hi=6R(|ylvne{m15;hfn11HODhI?+&%nfTwJ& zd6fZnb9|9`VkE7?7f`pb>u@htxq%%8Tc?E1e2_&tM;$S!TgKU6KEG^gGwo^s-JH{0 z$JWQ8(k4=A*mOJ!2cI%lZB$ZOcJ_ei%H7VjrH?)$&sFN7I>VG$;BurL3<`1){ducsuBd(~^JnR|koU!P@Xw1x zMfdx6@o#2hd@~tcg^WP~i}t4Ogu89ToSnQppADr~Pv|gk&$n>P44JvJEB%aJp6WH9yN1>2Tc{Rw<^`ec93-&M7jv zSs3CQ5^Es~IC3FsN{GhkNKxMxcX13hp&G?l8qP$05TcJB9wlYUuYoVJD6;e!H?i&v zkmYf^+!AbUdMC&u$dwFaUNXOC7;lSI*i6CRZ7*bMlP~C+hr5om4FF>Rq;m45gJagHl4^}uUp_zjBH^AF`@{XoB$q=w*Rb3MK(#hV>fMBQJ+Ei zuV_33jbxO?SwQcfv8>4filXrBdaYjw81CvX#*VAcz_^iee%ssGm1HpEje2ia{V(!) z&nYqLdYO9<+Z^^p+;Mt|$gB#LnJ!$Q?LOpeo;&e{wMHAg!nmeG>!>!|Q%8nml?;5Y z-=dqA6aS=*Uy-;q&)kqEvK<7{-Zz@N=WG(&>|uyjTOI1J$GWd8N(~3k0Yg}ncKR;vmV7DW$vj#7z$=H_ z#E&e}ObUgF^`&h*vFDMJfwnr}y!Bv0GT~!*FrG~z-HGT7)~VSxN_$aV>R=v@V*e)v z`&(7P*(bjwcD3eURsp#^<^;wdr(f}5`GXz=UyALWq0;1>*m5d*t!1;G*)*dFoE8)l zg(@7mf|Ff)xTtfy1mlymeHV9#WMk}Yay;wDNW%(;TH1YoVwCH9Tf$ObXra^07^h;Q zvHlh4ac{KX@faYi_%Fq=!2-Uf?AF&e%`TIL;j~dE>dhM^TX97NryLY&ciwI-w#n}O zB*0B_LAWDSZPYYVJU;WE59<;^f>^RQGG$e^Oll>W+gb3AJm8`4@5gjm0dCl7f>}(~ zRUpp&Uy>!(I9{9pKUJ!++BSR*Sx*r*2Y)YjlJhA;`&Iw32ck)mXUg(qhUB66pt zm7`oS_pHL|>*~5PU=7hU&@I72ie7Uve-3gfMgay3f!&a{7APBLjmt#gqRNk%WN^( z)$LV>R<~ZKe0H70h_$*3P24=4sj0%7ejhFQ_JvY*@RZc|L)lp@TB$k0IqDlMt`%wu z1w%b+Q$01OkXpXZ`W)LRlw8#mm}?WROvV``1@nk}V$E=MkTg0B$c!n`fGC+#rj(tV zP*6(c7Uq-RPOlzf>Snf5p6sUA7BbS4ARPi4UZ>9T1Uh;~_glcb(A&*KxBLs`hfoM4 zZ^QvM@)rU)TvOg|61^bP6gn!bA&JY+8H=1A@x@@r41NHbJ#?nQT5sh1X&XOQ^SE$j(qo99Y2nnN>b&QgV;w%iLLm#0H@)sZK?8D;oc>? z+EY=3hg)G0JI&2t$euZ-e>-FuV+;F?^?1YFXGa20q*gHG_bAqeUVeAFwK*q|MwM;U zq^L2DD!96Pa=s^KITYD^kLCTi=6`9^nY`xo7u!j5&=7HA;%|a9W)#{HIenBeFldfq z_pnraVh-tK6laf?nfLc-D$Adpn(Fly5q(p)gnGr_>H6$(E23y`r8kW8Q!dFPE&F9) zbnv&_*6+Bx6l(NhQ8SxN;xfx;P@#L|zA@WB*-OG9nH=>WZUbxRb7Y;3aU78Vk0+tU z5&Zo{z{3L{I?j5VHeqtBgrN7c{5)%H$w`i)Wsao6Zt*cZubBA&=e^Ko5z!!p zlm=JZDWt6n#wYvs!0y(?B3U$v@}TkbVzsgJ1F$n+`WF;WJg}Xaj@0IBw*EM3;erlH z4uy`yQtWzj!fpIfNcyRmraPzT1-N>br~AgEHIW4vg-qBP1hV%HQ#Kc z&_>r04SY){5y0`Nc|{q(s_>K2eDUD2n4CSj{fdft(w66Vl+qr^$!XJnB35vU|`zwcN!T!u*pclwZg%)+hI*Z{z#R z>aM;kY+N1Ir&a6oXMAO0o)JEok8oTi*dOGd%WGX05tzY!wP^`iX7pn;8kxRhiC=eb z==ibuy#l#3(!ejEp*Ppy1dn{t@?V27_S@qYgV)(?WTgyo%zRbH)i9(^jR0>JETSoN z8!w!4Onh2LM|mB2#9`!A`y-sIQarZ^7T~;-MAPMA;cMD!%E$}QDI^MIVyUfhBcfQe z#JN@Z*LsU@;`A-7hf9%(N&bYoG$K?G?oRvYrny@=zCk)F$|(g*v0mx`Ie#B^0MRW3sL{=bSo(FJnsRX?d1|-N=o>$mi|k=)bfju#!k;-HZTf zpzFnvl>L)<+dn);n&7UmKdZwoS6Tf8Dx)*Cp2M`8U`I#v%y;$U}GMnBlOeE&?% z4w#)O<=0kuG2KO_u&_HG>I*mhtjJDcMzVGL>`7v1Ml{Tc#S1_vLyn>Ntl%W2DQ*C` zna^EZ^8RjPJ3O*y1Fn9UF45-5HYbkGjSRZ=#@Uf7^I(Pi2IgJ|m^16VL#Yk$Ib$Cz z8psYJ1&Z1s%gC3>L}{peB-x>@2LtOzE>nW}K{4uyc)pX&^>E~3w0Y(wnRCvkzZveN z(@b@h)oGM+qyvrvQM0=1r`(=Mb2dvSQH3{cJ6`%K%85kt+Q72Q9&}PBUPO~+Ux+ACu~W*<56$gd>b)cN=(q=|HA^* zuEX!IO4Xht{`uAPp&t@HxB2n_v4_)fLdWw( z$#;-djS`w#xIW+EYnY_{ZP$8Nn*DtYJ%V+QwyW+!?r*IRH{z24<$m;oes_On{p|>q zgo$q@Vs`F%vyJ7=dC4PF=Ob@+3M&KXbp7UGZn%#1$VTX~%^q`hTbCBTkuA*muFUHg z$f}F5?(0~yjExnn2~z$2?x4#k>|=yTbt-qc?@0VnD=UHsi^P50%kp_SlXJG9Qpi8L za9WdzeK-Bxkapc-Pz18j1;br<#hw#yjnLj<4dj}IR_gQk+mdZW0$#CI^hBs%wW#q2 zC}9_<+Meb4Ri|t=d>UHhw$N_?u&R%Mv>To+KU7l2_D$hqQ8KgB2#G7efj$0lnFLLl zfld()Wvh;>Dg{(`&cj|($>x=;&YnH+GtWYicfgN_Ai{om2#;aIkt8&MgLm7fOLKe~ zhMA-Y{(woHH%o6tk<=U5U{gqu>D0rH@fi>-kFb6vZK+DTyrwI?l}pj3Ki6M1#=?tB8Df0#YkayPt(QjI=LqBbf1^`3 zNu{YKNjXM3ia27ux=(>#{vTNo!Iutrkr|ajwsG{TgW|wnKOYU-lGsXqk=TPq$2H{N z{zJ9tCE}3tJa*aLu6DqxFts^!ZQ8B*?Ra=zjK;B{g5pGe`wFwv;6}XKRc3eobyX3* zzMAnv*1;_FXdq61P)7ap5mhI)Es`qn#mtri?sQz3u-05eEqqQhfjD5IU+B4@hI%^d zL{nc*k0w^F{Sh=kz;WR=Kc}Z8IZ)uOQ)y!_b$|@o{<~S8rLGR~@DIVAb~;5ZZ`5P- zkRPtR+k8zk4maL^8L~K$Rdc;&A1rC5*7r>|cfO{d6y?p^+Lo)1Ogq9=47Js2mxbUS zQNH0vRgPB$#JFPB;5o0y)*Eamsb`Z_nMYT-}KG!0x zgLl&e3dwJ{cb2(MvG-pi`u>}AS$=WXRW*`$_=S~m+jDNvI+?VBEve-``UMx+!xZCK z-|OJ%g@C2Hoi8G`Q){*(BfP9kgMS;RG|>bEw`GmohK9N+g^|gC7~I;ZRclqjk$6uA zs~-oli_excnzoRyUfzrFv3>Dm;lh+0vj6ojWIwT-wbJwT*089|yNdBdO8~w$65p zAu!#hZ%rXNPFVg@5eAgT5!vv4m0d>Z22 zv8~M9x+XpU1TS5-HBPpGEmvH>VCE+lThKP49bWvzf#r|N;)>QpeMFOvL02bE^*{fQOG%P-p%7t3Ake(hrrCe-t?Rn<&xVFtyj1E0fX-Mbo1pwq68Q zS{FctX|}GZL$AY1DJd3}n1EmX{OH1`53|7fxFmq#27|$C?E?+voS4vb5xUQF<=6YcMNK8h#M3+4?^0qqtp> zB&oS8)$)u*p*>XG_bZaaRVjZWiFz5|V4vDd&+<+VMBH2EH>9hMD~6Hmp$d2woX2U1 zZF-&mOj_B|^}dxg;QlOcnD{7J#)_&QUB4si7Op1xzQs0 z&}$Bwuc1b%e6Lhc)m9?Nm`tV3S~1p$H(GaD)>l2TPfBB35((+f#J;O3kZR(`aIyHx z2){G|_mJgh8peEH!--#i7-J2X{#;Op{hhF6@`91xq^d#xz%EyDlg`xH0jza^#~fz@ z?+JdyB6jFJD#b!?DW%MC#@f(EWpbE*xikL+E$)F-Vey9;DYb1Qlz8CSM0h?&1gi>( z4mzSYucZ8HVp_bMP%S=-E9ymjvrm#zx zk)^;xg{5egVi)?w6|yh8z#6dq3^6DUV-VgOs)!9%X?O^e`+G^E+8wpBYRdVh74XEV zHg9z6M|zRm@|YgXRbSM-qZg-)`8jDv+%T$IlowGcu0j5)!`t_njB!P%`)=O)j~`&T z(3{b_7oN^!YV&}qFUToWDMDeU0P}%bi`{D)%7cL9Yd(+%dfL>>BT1l1RT+1OeHv}H z?7V{-q`OQ;XvEhysf*4fGzH}Rc6Tsl8*r+}%jL$?7?UgD)Q5(jfL{!C1iAD2*qT`& z)9mq$2aPXAtHdqo`cPU{nyi1(yF4c<>q3aOE32!G*o@JU8g#v|GKh73DUHQGb(*W$ z`n&>qEaf1VAwis6-ut3}3ze*zMtNZ1Gl<0-mjxccNbKG1r;L-i8gQ;LAesc?6K=n0 zU*?Yasm8hUrMuw2bmb=>^=g@q6(b3{c{ijuVq`Fw&4jNNl>>0BIkzsI;s$NP4K^es zr(6Zig>)|A$q3BaQkZDc8-B>5>}(0P&$Xjmtde&m>EgJ?jxQg_);%i&U0SG#C;h+7 z5ShbIN%IyG-BF$VF*z4_i3%6B? zqm>fc1-r$z{^hz)WG;UDwD>`TaxC%O9KYH5kEKN@z1!nHWhAVpvu!BuqV)~{=BbzV zb%h%n?vtGxHf^BbWV8?*6L=KCQnHSEOve=0Ej~n)Z&xg1^Mw_QJ2#K$Su%&c*a{%~ zN+u}&cl59+Mm@-$Y6%9HFXx@@jGd&;Kiye^_gK!|AXfMJxeBe?=2**H8kgSLanFzA z_9-~N`9l6X!6_Ny*J*!y@)(VEpHFq-A*02CZficEp^C!@j88f#X))R3WveU6l+B>6 z>1*n;_#ew~-*}fp91*RdW~}5`$sQw`Qa~2A>~mV3$o(^Kd=hS}ODpE?r;Bc1V1*ZJ zUg5iz8hvdut!`eNAp$5AG*ymy;`|%vbG@~AbVm}0(z?57++L(8e6|nz6l`Rxhp@0j z&oRH3%mDcl<#4USW4Dnawn`Dx`8b+Jqf$zU5*D?-CTrK5$_!L>t94DVy zA=4NB=?3{kmu$?PG|unZHH5sTWk1Aww| z1f5&xNbB~)qc(^6eljM+&;zxYA-tsPKKz@Q2)mu#H|20iu`o3C+uUY} zq^(ogD0Nlt$ydZXf09pWxlCPQ?unYTxX1?dr{k&nrp>u0wao*w00e>8J5;me%C*zEY-6bO$ z*r&w*OhhN83)k%<1RQP(T6$PG{@e5bsu_cq*Z& zOW|$mTXN9+Mp^8=wb3S&e{pd^G{%rElND+!sblJTaNsgU+k_z=Q)H7`tg#E!GwAWS z%cm9=3pyqVc(J^Y@4la|I$~LaY}^(#lgJ2r?r4O@P0raYCby{Li5ZO&q2#0$Oivv- zh8;Po#`O{58hM+>oHW!Md_J1s^Xx%CuUN1%u=FfT_OU4VI(L^oy#^QHla|aa0gQ@%y5avLF<7vi6>5*fMz6zOkdc**xsbsYAwCYKB+Dcyk3Q z@|)!wfJttOOCmn`D(ra_vkbSLo!>_BHXA%VP8#Jm+?L<^#>|RqhTVU4mAt6R1#q4; z@5*V1g#Frue_(i+G|MPv_?R(~GYB@fRqfCgF*nju$-_slTa0o%|JRN_{Z!4VPT-w; zkUeK<+XpOWCoL=41=Kqfx|w_J+=!8#(5CStOV8BPa73^5bBq@7$khmY`!EZ)0@aVg z3p(Knu@T)n;u%~{8p9n5h+w2?&C+JZY6j|rrD@G!nn;O7-PVIU>AUN5^y?9#&o~Qy z`cSPJLOmN*2R1kqyC(PKtZi8?`=`e*3JuMY=yg#8M9F^kx6>wgDBxkG4BGa1UT6_X zY-$hu9mPrH-Nd`}Uf*MA>eP4aCVDvE5z;~y2QrXhkYTln#$l~ggqF$n6%rF@-G)M! z2J{6TBI5kS9d@?#Ho|_IHyd#Qq7ebwlXuxla$=@0$XG0uCYs8g^}pC8AN;+>eCEB+ zv%a={J$0P4KCCaghJpL`b%6^&vpW&L1SbogIzbi%m zbbEKZJz{ouQEkRk!gx9aZqhydAmhbDRA5G1rn$_0D^}6OV2GXPAeEcL2)f?2u7d(T zB~mW&Cwx^b)Rspd*QL-9O+SXG1STq5?V~1HqiFKxh1(Z>NQjmoog4 zN_wF}@$}X}c+r?m$e9QialZC3=8_7dZEJMNOh1JOQ3(lh2Z?26oT)U_*Y-TPd04I4 zaN|X6UZ-4#KHMJDZ$>Dx*nv<5fI2akPZ;T-?Vxj8>7zMLX*?ykD|rP3ktBin1MPb% ziVkM@^fgeRXxJ z6fD-IGgxyfkgLTZR$)vJj~X~ceWER!i}aDApb)s_b_g)^kC@0AVQl1L*)9bd)VKSB{|sZ;ga z&ixx!pfGdq_PQ`YW0j?3$iut_fVNaWQL=0iUUVThpR^TR^h1R&xo!HgF!G`p#EDGP zeKi@0u@UgaIS9{qhslo7@S>|-I|vEC-6>3VCQXF(zV{=Z!MU37cmUhoniU*RDRJPUSf>uch*q`MoR6hTDc~**1zV zN~Q}zn}+IU12J|yCCz+(Ab>wfL;2zu!#FPZl*f*b^fFtB&jL1r1gYgsP@zf zqu6Lt@7dp&_yVUxI%@Yrw1HHr^U~dRg=Z#6w`A0Rgn{a0Lm%wX52XL;7G)zLf3xFDysa zgNyF_Pjfzi3qZ-<^_`Rhs-$~^z#j$!BJcC-r|oAPOa-rZp}9hyVpDH4l4A7UA)E!? z2;#r&nYl%KXCOu%ML!UUIkKYPe!eYVt>bYV#%WG|jgP^T{@968p9|R;(gb>`kbAO6 z*3%CYvVTAp?JCDA`p&y2pE**ykmjRw_Xy98?j3Z~L?I%RI6sKl1*X}kivG@?%W1oz z9A8xCN($~|h61U0bg8ihDJ9H3R>l4HCvF~7nwRB8-k#;~xf(DsUut9AI7d(Sc}Adq zraB8qIf+$3U;_p{gBBo3t_|~|jqaR?`$vXqWUMlGce;?7u{3TD3We!dj5kUuE}`qR zUdgOOs~N9-D5{g3Uc8wpZn+9dbD0!Ka8Mtrjg~E^aAL_Q_Sw|D+AI+RlegC)Z0QsE ze%)>-X5bJ!we^4ASZhSBN@r&oUninKH*VocMm@P^++IqI6L#=6GD zhA^Di#-hu0o41hbX_#Efgf8Ms9Bg2RV#iCR-{Ont-jwNR3`cT98y8+iN{hz3@ zRisFh%%0d4VTxc(aCArQj`r)M*=p(>o7#|}&m&Ihy~M6#+Q`!SA~B;Y2?NpK&%zl{I*81KbJnnfZ7 zC7|1Jztigj2IhDOACp5tJl^>4TX#5Ztlh=+qKtFi?m)Un@5zUaM4-LJKJ8MSRGMJ5 z?cU-v%1I8_jTwDwH(#AreB&fSC|=!_6gp8}Ts)CF=aHRdC!Yo0m|5`xpK@V!`i7FY z5I!IhbTd>{(dZUiVS$2AgdL3NWB`dCLO^w=Vbls$(TAetaOoCWgt*QDeAx~7J7gDT zl3MfNWNj`-_@?U5h0-1>of}U)6e@UT3rd<*W~{*#3ujzDdiPIRM*^NXj`Sj{PVE@3 zX<8CeOqZt%izoTVnIlAm;Tg~G>erbwKfc}KwVkht3~Q-Zi2E{+g5}`6n;h|=oguFJ z;+6A$6yo9iBj3k!47ZQs(KX7)wnr3~ z9Uw+-E)gL;Hv|Wlw0^X;?lZpm#pk|k_?`$1sWwY;Nu_KY?olahe{H$I#~5T;H;P9Y}o?*|gTHGz!hRCQ`j3_Ln-*MqV5L6`Xc z#oJ4K@dNSem2SWT5l_~(cMo@(qHd2%oHGZF0o(i^VCYls!G+way`hc<1h9IzxqTh= ze?L2o9~jeN8xd{vGnBpTV2A;EL?~ z>61C%tv!?OYSI3(6z|4W-|^|c8}tMI5!AE5j6X``)yp6A3Gsf}j*(uf4%HHIlBN(H zBXU{_Ej44-)3H0t7Xe>XHcdiuqxs@@PE3xFqnm?yF`ZX$=k^90iC>J%=Hh@Kg`4tN ze9kbnl%Lt)$sz^FPFh~(ZH&4-{^^d}lk>kD^#d1Le;1~T(gQ7>Mlnmkj9|uFGKD8J z784$aD4?0xdxnhBj5H_P=ZI0eu?er!^ny#=H=gG%4|j zSD;N)40EJuC*6;?>2Z1Z>mt#VlS34bJ-Wz zb}k_n;ES(vYi{TO{GX{k#f?8eEzx&&+d+BHYTWj|U0yues;fc22Jt8d?y||KS=Rrg z`QHWR%R<(Ng%aO;X#%+3+Nb=v{2p%dJ>K6YhlNdY80JHj?GMPCT?j21S&1gN20CBb zg1xkaU4JEe(-NsLgr>;6rgD8=)~@N~MlNL0y7ynU7qtf?uTuNmaR2|WH3f974Qmu? z9n|8esx#D7-1?y~NNdnil=ItofRh}1T_(Nk{px%b2C+oTn+ zkRHRNS4GMwq>@Zb&EnuPz_tt7Ip~(v6;HevCLpT^Jd}###Ty?g%=n4x5BQbi|DMY( zJiN?EAuX=ku~s4moDAJF=?1Pi*$`C4Lb{O?VM;5Wx{&3XB8BdiSW_KbG=tsPqEAF6 z*&C9zksE@{`cz`e08{Wh`NEFJQ>q({lXX<~^p}V+sW@VG zKC{3M{qlfb2(t+Uml3T#*7-c?GUPwkQycLK%N$3h9HbfrkBG&?g&d-qE{HYi96D28 zQhxRCpyCTB8vzQ%ILxWYMRf7u30YeZM{PYpY8!OPp$jE*{|Ms2-laBbD-Sg~(XtkN zxrKIZ@^cojUFWV#rrS`KxkvI3!*bz8pVr6QGX4o_uAWEcZ|!fj)bo#}j2UPcSpo(X zyO`^}nnxW3o14|w$xFonEVghnz_2)GT=fANBV@my^p3>f0}pNF{SV3oqHUG2{dk$V zFF3`5TO1injw>yNsTvH^^;=p2rl8fF9hhYPh071N8i_O!9xbqy?8ll87E~MJ0RvW% zlhsfCI%i6l$S&*;tbPYE(&jbC?Og(MkeBO+AI%*>Qtd-CaYB#j+VF9*n^+Uh`CU-A zbK!LUK>ztav@KWPFdkAs)-;iDM<=&B@e5-C80*0#`3ml6x`?>zu`c1;$k6ySS|Dz$}>ku&|YOMJc9>{_bI5wb136L;GhW-tXBDUx~M$l9# z)s&Yr6CW;DDq4SNte71n;dl3W)`Hwd8cQQZjLms$+C_{};s^n$I{e1=h-#GdRPE#_ zG0a|Y|Jt}xx7#6iM|8`4LXMj4Aq|yfZd`po*rvq?9%Z!A)WuV( z&bl-69e}@DV(G$gQKBFN zKYR?TQPkppvCa}&-qnJx)mTdFz9R4NFf~pRl-2XZNsxd3}2?@}oG47SVA9&GtA- zXrbInTSm{gN2Ls=o;ljsTs{dxJmuzLp8uf+ydQk@y1Q?#l<;N&f`fGr!uJENMDo_$ z&;16NxC!Z-&zEOND!6~!8t&+y1cB3-fQ%HY&sI`eu4P|}gG^FW88ZA+4)(Op%hVQt z6LE^kq)#`26uQwtEygt|%Blm7q@*2No%>yQ2q;pTzTnlI8mFf{x0Ps;GZ41Z7Ims1 zZ~jNIVion{2w8Jx2(Nuf$EOkCrtHwp~gctu(k5cDz7c(#|QD#p!{ zwcMZ}kZ`Dn&A=8fhi>X^n~~1!ke%yWCsLu1BNrOge#Qg;{y?*^@21y#*KPR6VhBPs zHOupBRhd%RmneWUkCNFSZWnU!^6F7f_4SjZCVqQ(`vP@?olG zOm;Uypk|7Ab`-!F4)zR*4?nV6 zVCAg*64>h@5+^T9^g%qIGCZzg#<)QbZ1$Xwo9>YWX&b4YN=YxS>6TfY6rm57opwq#%0s)1`mS!vK-xHh&PZ z^aPUY^hdnD%HzA2LlsCU3r}o7F5Z+(D55(VYVQ7bj<@ez_~P#{NjW6ZQO^Y?u*67* z%GG+G??9hUj(w?lB<+`40^VMMi<8=G}Hl2?o?BHsy@M`p@pTQsCJ)x8M| zxtW?kWeh&u+*dr^3R3)xL~`j`G4UIt19F*TOjOPVQ<3% za%CwSr@FNbkx0kkaPE7LE)vE<@Q0mx`yIg?SPZJQ3CDY?@{K93u?uWQizaQ_|0cto zJC10d2^l0zMWjW(eTY8?c*Z`oPNfJ}(lz&+MRHUy-@~ck@XUZD_9=!@%09s387R5B zVG7J;KqY3vIQVqx*qbY|r(y|XXlTu}q-gf7TFWu@6uCVYvciuG^@jkujyHpB}iLB4pQvZKly>~pEU(i0xMkiQNmZ+;oCu(%NI#HtcP7p+i z8ojL5RwsIwAge}61ks7IIuVK9C2A1Kd;8{jp3mp^?%(&GbMBeB=ggUNT{C_=PR}H5 z>32gq{K}7(KRR`{DL-^1r|>?1B9+ljqwl?LT!f=bDMvH_HkbUv2F@k}1*NXjbI_f8 zO18HPyW2I@GZ=VnREY`UDFmPI`xv5C2~>Ny?xcq=49g>@(GTuN4I;~u0KB3{yc8rD zUnPa=vQT2_11g&kMLk&grWE4Y95=EVEUhZb4TfNcFRLAAa|zH_I!mV*Qav(JaAxAh zx;i5JyMRzjnxk|`8HWwz3Cq)>9k33LLsWh%Xv-T~k1MFg$Di8h$E%JSF|}6z2r?*B z`;(mhu6o9{T`K;208__7RZ^=tU3bRXV~X%g=CGEK+*ZwM2d-Vi-KJDZ_@jPZ0zwj8 zL-z^rX87J$yFX?635-nFd9R`|v<>+zofVoocs*Izl(p9_l2y&*9oSCohi6DtlDhD| zhW0tsxE89(T05tfl)l;)y)Z16);c;6y_uF2E}zPV$o1Tr7Aq!kcxS^E%EGDh+^@&df&HP#b-j{9`*_p%qG8OTBu1Dr^7HlfN-=x?RDbJRu8|(wS=3E*R7rJyy&Tyq+-r6hXWKdJ z>-9!`kICe%G^ZIA>r!`F%+bK{#;*>TwHQGSFFTF>>T<|(x0+NB+?}Ku^g1Yssuo%T zItHaiG@(ljs@p&6pG!i(w2d^{D}p(GX+Ga74!XR8B|E*-!T~xY`Yu{X5xB$IE%VA) zNN@l9>1Y=&Rff9Rif^Bf9^_3e!LT_)Dve3FiA}Z>o~xm6Ea* z(GEQJolFlk;_j~AIOhD~r_2HRajn>_s2oxZ4;PhI-{qxgmGn^y$1yT&*@WbgP6x*! z^#a^bo|3IZ_@S;BEp1e_!x=G;)k?tW7$u%JkzJotcBl7%&?e-uHlEn*=l*{F zkmWO(+9Mm}Rl=^(@*CIA~mzKo)E&dg0>+(fb1^GIgAO7_(X`(-aRLP!4g# zhe{Xz0l9v>oAf}@P~QG^`X%HqemI85Q>TpCS^r>PSW+ccRBCEQoPNn9v38*d;5=IH zM`_vOzMxA-oAje3><0ShCOP@>}n)N``myZsdvC)sJz;>bP>=@%%H1qPlC}_ z%tueNYrxO=x?lp&>)3`a0|JtQO`(EN5Rb+XoP}SXZ(@#Uh(9~sho=TQb71tADu8Rt z?*Vok&2#S@S>-M55xH@0vUF@RVRU`LVoPr&pZtStiGGQs96FA1hZ4RZBTVTVo*gnv zDG_9JeaRKIHT31ZB;ZQ99=t5^=OH+o29)g?2pUshadE(jwNhM(GmlG6I*W_J7A}y8 zthXCRHC_j+?%i##GLTYitab8w=O%@_!muy+ zuw4PWAHmSKxgmbyqM~fAjeBL=dO$s7{F&mIqG#9;)lhRSoBmpw4644FGge@G1!3@! z+D^wFme-(_KW%^8B)IKoH2uDA?C9E?W3j=>ax$~wS=r!N3a%o(u7Gp{&p;t3=tNQe zaaUVS#eCZv%py5yC>M{pMFO}$VeZhnnPl#hI?L3+@0#~S;7ydj9$p2{ohmzp>2{=S z-4#E*jftD$q`0c)%!`=cA0>8y?7fXx?>o0qU&lMUQCk~A$1VtYEZ{Tb%!+ys^k7pu z(a}uew<}4omWX*s6!K@*BKD#(D6O}KbYt>1bDNG` z-yiM?zM6npv*GyUk9l`(qh;dcH6p`TC*I@@Y);C97l&a2ChH zDI0SLHQW-8y)l2T@=3QUIbEpw1tK7nX6P~XnsbTk(<$8mCY7LZEI(%CIC#V%_e{Le zt4U(>^VW_|6s{jEFSORR(YrB@t(oGN+Y;{U&d%7HNJ)1IMB9^04!k@CdGF)4bn_y7 zAo09m@2eZ*wc%k#!{xKtq3HPNXVL3hq? zO}w!=24+yVRi-7-z>9(MPV}|^l6~Qmr4>6+tNC|xS-M_z6SHF1$%(Y|elR=`%%^cL zZMrQliepI0RzKi2eRCT?rF}u=ziN1(XPo+!%5~Tn>=o{xv#pVuH=gmMMOW1RE^f}b zV;-BJjiilFyd-0KIGu19KLCF;+D*A)BbaETs9h;GOIv?G4cq=3ZM*{I)?JZ#G4589 zW#D3~xM{zi%BH65IrA$GNTd>gcua^UJ1sgi{r#UQPtGvX;%O!5#7Pbs;LUOH$JGq^ zkARO3Otim}JMd1@yvOru)Q6~AKb6qXoXJgW6cLx9{d__?J!|YEXt;*N^b}b&9|8rb z6&v{|Oe9|V`x>6KKF}Javw9}Yt&Nimv*yJyOkHR3vh>QEQb$7UBsOg0*15()6D<1I zzdD#V|7_aeed9e``YiS9H+7~vVliFs2ca)f42){qTzA$+y2u|bBi*~e#E*=>g%?B> zP<_bR`BDo6YsTSqqJ;xn`+Y1SH$r3O-m|V`E5bahdYM4^v@Xcj+Q=2PS>bGTg3#krPP3M;mMZ?XdY3IgP9k3&A2i9oF78JhS0#`xtOe~iAB zqOsUsP0x{5YN;t_tllpH-p=@f_s3#O>RTY610lptdr)RL=T!baJ)CdY zmudr-V(K00)HlSP?G*)0oK2>zpMsUjaWINQHwM?R{+m4k_qtb2-aD#W zUhv+Ko#B$bF!Xkh+hk~t_Lm;eV_nm1_u+#+1*6mUZP^%qtK7*^mq`kXtzk60AGcqM z>4HttBbmqQ9*_crf}||mo_)6!+N}$*D4J&*ry>n<#?64o8{JD1>xIL>D;6f&#~<)d zeZynl{DB`LqJR5jJ3RZTYT^vNWBf}V=bf{VnvW)v$}6;LujE_^yDx8v_`%w+Ua9~{ z!Q2ROnbAz)))1qEz&`I2NQpUsAhnf#52dqNwIU17XzOGle;Bs1r)9RM67kSaU@apeSVu|?A<8C{d_~OVcASu_y3DTrl@`Iv>D>Jh4 z>_G+3^4Gu=oUsU#{1Os| zUdQ+C_P5TYj3@`mYToxWH``K+`HyrTW9Ay0+79W(^J|onZ1zqb3biQsA;eZ#L2Il< zQ+=C@79_gi(2e9735jhXn8g*8KSj?j4)8gD63x2>I33Ghr+3+3cU${rXn!fWry?CU zKyTkw7IC+=@qDwOT*&ziRIV*N-RctT|p(Hfc} zAaC85WGWUDrpPX7FX0fN?u%n6-fWNHu3&7{C*A_x!QriuixG%os3DkIKNj-|FS5Oj z5aSZ-KimG17rip>e6UIuQ>Rj?Y|gMOqVxeICwF*Z)~}W@}j-BQsiXChs>IU$;nN^?<7}6G&Q}W zF%XuSsg8l4T1^VACvukGJtnivRatzdd-n}TpyoZERQt}yI3PIl9%IJa*>Okq{^yJ% zf$ue@+1E{JSV$#E;pVWiJ#t7>_H2L2Ha$*iwY|@3sqZS^2jPV+f`l6R!XmRwiOq>f zKXnMT$<=J4AGOlLA0xOF`t3zM0lvB86keNS6Fis7UMzjNw-*w$+AB436i8)uOA$k+Sg;{&N))e=PdE+%IMJg3Gwa(|Sgu zpmYq3lg1XaV-KLnzNAUpb6eXTHtJQ}DH8CxiBH;gymBC3$L2P(gE+aFatHD^^#pgj zlb-SUmhh#By;!Hj{} zlb~(F$@`e6#+Dkff@QMEue57b2hPs;tEr*<5i7HvQh&PP7zV=)RoT2RRJcw}*&>9`t z*H*6ls*lrC%xXJ8y<_F9Y^!JID&en`a&OL~h~v|PJj)4hv7xczmrLv|2lAa%xX+vP%?^kmHyIh5& zvb^|AJ7}6o+*y6}NFc>rn`F!#Gl&zqqkVpeJaN9J?GX8VeU$8+Q*s4B=`z>g?kf&o z6GC7!-##W#_bOo2@72Qpt`t@H8%H8k{kKAll^0Q{6T&p^P9MWKf6WBowHo#`bIDPhDvg zE}0N(xn4AN9*6MXfBbT9|C+P*&ag1Y!-}{VzWH1>lc`$(vOEq{!P+C0ekLt|)W#u; zRo8Lfp&XC2*)Ynkz|P@uvYBQtyduV=)`#-g6(5f~(9^Ts*1be%{s+Y*#E<(`6YE;; zihj`Jr%w{5s6BVHWIRwrj+J{7PmNm8MR-eW?a*n~5_!dTMb}jT7^I&TawF~V+1G8M zanY%fOKw`N?-~m7B~QtpK(%=L__F6K;ZxN9_O#4k<>{Y|z30v|E%vrj-W(=|`hzM* znfMAW)wX|!s8aSDwKG1oL+`L7G(&gP&*|6p{ES~Y6Ja_*Zo1|wFKosXLwBDD=0jgo zTl2Hf-GAx>Kh2PhKj;;SGQycQAjLTIiNdC(*w{qTAxwz)_!8rE^a6%HwbP2R87lz*?{rGkVL zldOF8t*>T9-RsDJ*1zl@hHJObqkAnv4>nS{Lb#^-L*#(iCw=qlbLee ze5hcZ{C*T6!|Ts@IG(#9=&gW#jG4e0S333_(elY*kz3ZQ;R!e%e7++%XYl@ft3urz z#)xc3amNlo9BbC0H@*Fpd#+0Mo#+oQNbEml(sZ8hp~u0G(+)G>dbS+g!gJ>KeFbk< zC2|*s38x|3Tg26++4Hg0{ED`RabFA=aFNo(S!`Q5vpHMOM2#aj*DW zwaechx)1Xkls0*&pZ~Z7z|^AmMU^v&3@h>!54kv^xAhOWVxr62kDtQLFcVT!8AFi2 zD!|NyAcFilUe18TZu(Mw$K*%~I|jXW2!>))(H!Pxz&EO>;qa|!N{5{G&QP!ajOa6W zS^N@LsVoQk7*tL`8g2e&bZ#3rOxOHGk!audbNnoPHo(W1r^MrRl3%S2ei5*8LskQz+k2_0~M5BSMA@-_0Lg_w2 zLYg^gXev~8K%2n~ikEXK^OOC$KNm&X{Rv*-$ltEhS~=UJIN}d$?z?9%Zjne1`Q;Bl zBJ-ZWpL#EFnNSj5ip)FV>N*D54Su10a)1=VpX`D3jq6)2PzP~aOe)eT(Y`s7dl9IY zb1yqo%hR!`581W>kCSgD7?s)8%R)B0ZCo+0{^+ANSk!#yZoxeQ}`O?FaqLkj? zUOqsm}yV92g7rTo71SP(x*j8x&lo=B!1hgZELeZmt9ipZZh;In1hg^_uuYA4US z@6KyvN#am3@5f4M+i~4cR~C|BwrE#U7jt(9k~2uki$NKm9Elea)a7A0V`=k(exT(p zUf;>x^21&Pf&Wtd_F52RfV){SX3RArjaP6JtT@}!l&;F$^%x>o}t=QdbD~Z z46j8H_w&$wr~9t_Mm6D}3fs(QgVsBEr2p%s9NhEakdQs+n`UssJ<#{b3+PrAA;ohJ z6=~!jZ`ls@p|!uue9HJ%f^+@C{fPJRs$OVBS6I&ZFs`VOo->a&BB`G_ zzd~KCT9p75HgKD}+rwdI^uIwFIPgD&E zxGRNY%dR&7%x^yIwQ+tQEWnSxPZk+GB|GN&?AXOAA&kK{=c%0kBm7ov###s-%(AJ6 z{@6TSILR2CUtiVo(q}E$jpp2HqP9!4#qS}WHnU-EeISG0^%RRk^2d#&rwIsRdM63D z3AOX5jhRjZZPf?roTM)udLNwJbZOL;(h0SHTzDy;6 zCCayyq`%9B7q8rAHW5_xuWGjERCTLMxxQm17;Ht%JUIyKic{uu3;!aGu>sNr!TGB% zy9;3)(wv=RzY{M-j3pF8hzOdrX{mB*B!I%1J5~o&$y(-$@%`G-t<1hnJJRqd-4QvO zv&9h!0KHNI!%DN{3Fof)=A61J^;W~Y*9KxYV?mOZa2J96yHl<=tX?9tR2?WxDiJZL zS{$l6rm2q~R!A0^GQ+~{_e5u;6v4*NPE$Oh_T={tVcN6T=WhvJI^#m(7A#-gn9^0e z8ulIu?gFKM1aD;kAmBJW?43Ml@m#gLvqKsUCDELaVnZ@fx^=}ePdXsmLj{n~& zLhVA9W87w;K5=q(tZbMjoRzuPx`M(#$g3{pZJZePJfA$S_> zb``|THNgZ4B~LHOrOeuQmK@$~;8!4JNjC3-XfHUgf@0gMNlq^V>d985uK)aU6BWw| zehqM<5(}lhW#v-We|uuOte_wmvjmQPY3)o6(jc8^Qp79#1e+Z(qicpZzcJnVDNN7U z?^6#I4sbVqrfOh{*prcXOOX?wNVDP=QN;-}E!PDS6ZE(Y(rcExeB#K!~qjK6)Mg0djbVVK&Q|L-49 zgLxzi5X6)mXvin$@8UE5o+m>;D+iMv4Z7O5FyvvXNc#0?Eo53M9F+t^C_PWz z3=~LRjBe7>(vC0Tbg--0Y)H7_P#aF-rkcLrkR4lp{}W#N=MH0p#(fNkexFk4Sqk0s zlkcC&!+(-b@M!AH3eo&o@a4PmU4A0Or76K}4CfPphWsY`$eqNmN8O)B^ym3KoRqP& znIUs$Oxx)9dAhd9Pf^?V>8V&mYl?r~gJUc77MA&_P20z&JprqEC*}u^!*q+xSZFja zo(jL}zu`q7SMG8y`H)AtQ>A8(SC_A^wx)JlaRNez;P?cs-`=A-Cri&L-n82*L79Bz zj@GP$w7JSFu_}LIy1^U|m1ty+}=xnTJj3*(5qkiSazTctENmpIUQ4-^y_g zvQbYBkrQ#|(=yNq=A;e|$=vO@PjVdJQMuqY8hUm*C=QNF05!dgiV8I~iCb_Z!6JJj znT{dvc%7wXC0vJQsu%Zp6G?BmT1;d=6|w&#YPwW=J}wqOEkFtZ4^)D_ zaEiXVy;oWvL>i_zY}qn@gJpz_ZT(#r@AD#3df*QrX2WJ+hza|$&5}_SRBU}xE|$x& z&PA|l>Ldkg%0ON=vwBRuHR?BcD$`ew?EsZk48MELXG=a?4Tnj=!jXL_^;fp<4B&7b zazXgO)3X=x+(3`RH%W@n7&d-ms_~4o=MA*+<^Ty#g&B#-R@IuV^IcjnJTVwb$}E*E z^ssA6H^*_rd&g#SCo>^CL13!*tHNOK2Gg>q!4Gg2e;IBNgF>>}Fh@zgCYac_;zwtk zJTpNy$j%^LsyrPaV_3drkU=Ff$C`VY5hOQ~3#HJ42i#SAl~vpufEvxE7Q2fNt$A(J zE1$Yo&TOE-EUfXLpg<7~B%*VAVCw zeNJ{2IvCAkM2)o%YX3@TgR+b3Ehl#2u5u0FvKIPY3kLK;0g`giY)=Te_#!ho*U`kn zFG%UE`{f0y6XxG5=vB^LM%x_(0OYOt37l6W0*PsgA*(VEt5OoR3|{al*($Q~Tc{GF zrzhF$?qsDBd!xiKbzipMy7r#7}3A`awAv#~&f;aM^q_M&WM83jxq7HbUb?yU_^yw|-j54}?e;H=`j43niRD+E1 zeh9c8=q+Hw>01`xR95AUd~EAK0Oa{`qOm9uhS|=j-;r5k33U%&rBVm~$#*((rR1I* zKaf({Q_fO+c2!^zJVaGlRDtCY14PWUEMXzRO`S)qJ*4+pqROk1902%Vks0;~6ng0H zwu+g~ToeN%eUHCL&YGiNP4cdlQiKH~j#APTC;7L{>UJ^XJaQ5vbQspuSzT^|N5+_ z(Yt;ucJP=Ekk~xIHuX@0;_2td%R=vu-Hhi-^oMh~EQUT(;l8=6o~e#z|C{DaNVNY` zM(Bxw3fIr*lHVKlQ~mk3S#TgTYi*44P%SK}4zuC#_Z{-E?`8arv5B^rjsEKbot3Ex z6S8gk^G*FfYQ&G?3=*aOJ_ba=iGy~@(`(`0wXj`W+4#))ct%X#p%<6g5q6l`uQY~| zHH_`r-)7eTSaCx4`Tsr$l6dLuN8pYHfjNs-t_K&Vg5>g2fpZpbE z_Ex%GS@?p|EGWdBn&9#q00REM%zAq%hmRB{d~aH~gb}aZV$PF`p>XdCDEN5&v+w+H zrP*D?M@+$LOKVTDIe%)4H0a&N>$UpAoPRR0Bu8Jopk-fau^FD<5O^z)f?xZ@VoS`6 zd+!vNl~Wmhk0vDPy=6@&UtQNm8E)UD$_Els0ma+Tl^K)|M*B()5=hDShz&APSLm!% zok^W`vNDjAZBR5&FMc&{QRP^#TzTkDcE)RKq-MI9Bmo54abe;(38kHR@3C?D-pCcx z?1?V~QxE|cTfSW)J9ZjFxLU^R=Jy*SJKmC0pM+M#&4gza{>R24i4R*&S3Ygq+m5RV zvv(`zaa!mxTE=ha$Un=dIA9e`C@Vj#I`#(4gd_$Fii0t-%sZZA`tV`JpeRO{nnBis z?zo?kZ&5-N+YoMjd_Ht#y8FHfshPI2?)1+*x&%OhDD9mC+@){+p~OLGA4FYi0kpC0 z(j#bvBoHvl>zrog9707Ma}{M%^U}c!Knf5lY>S$Y0wWyN0c5Vm{`|7TZgPb@?wuVZ76$%gT4+j!%G@6C%P0kpi3n1lPa5&wP_g z9D1s=yaq5x#tQLKsk&hsx6W1mR=I=ZKiaa0pPB^2O{|SXU35__%BEZ&fDxh)VTM&Y zrYy~B{hKDfgvj7$#Tq0p&hP4P``>avZXJ|0CZQ3o$V=X$_9{lkEjOImuYUX2LGrSV zM@2%kTxurm5C>BR0W3D`O`O-DR>LsmuSWuTE}G^OS9)S3N{7#V?)2Q}xTRl_hpVwz zS2|`b$XticQxv(>I)Qa3k0P74LtGo2iPc8R*pkVVa5&%_l4qAbH}CEs?FUnKbDKdDqHGn>(m9_ZUR)hC`&=HPkA`nG$u zD1g=KR|XvO7PV8Go+LBLDFAMxh`{-{A-7qLk>@z#M{BL;8%u!TWJXPS3|Y1VF_?a( z20kBPSqZ>K_B{27?0l!=r8Jz!?&M-Mhw?h(vd*ghfIa?4Io=aY_1$owN<#txz)ktj zaPEfJ9DQKHY8XIfohRNEFLu*kDKTSFVtJtyvGPx9*wAnI;XsAH5}1@W{lbdB%@_w% z%W@AI+^nK8ETto7H}VD$hoCUP*gd`~eMe?`f&u#C)TXFV)BlnFw=M}FT08|~yC}*o zzMM4}w|#1wX8$>$G#JQj1A=4)*`;MM70ApOSKCo(7&f6*v=UlR3jW8(1`^EUUa7i2 z(@@?GChHwE9ThnT!+)s~7c%w)LjgHDCU+UH;|6^6rEI*($!p@}1d5IX?XYA0r*JfR z%3(g*fGhQ#d-mJ8k7wVPPHs$U0yB~O^wgP}Kb`@m`6HnVvc7TPG)u?h!}V0ld|r37 zk@?5-e@dPp#J1Rae$xFR@xYv_ULn12MwFxlwRFdaPU_Y)wTj(Ga%~Gows*>+f zZ)g1FxT^y=Bv~`#OjN@LkzA9v2&)(4%Tw9!fzn__g5ZxI#NV_jWCs@wY~2b${=Z-` zAuOp~)t!Urte%^_J(8s%Vh*UtAu<8y#_FkA{>iHwp~A- z93dd%Uqc-buy=An9W#XN**flg7MVa*r6aT&0tC$et!RQ^p4-m$5phzMj@9dr{O*$Wvp86Jc9MwH)O=|aq_2; z|MzdeJO()C3G3NiC`xk+c5>WbTcPCxA4B-Etln=0PJL~C0E*0y z{u#g`o6s_9nD^`3|2_HN+(lM6ew;&g^SpXMyM>fjWQU@BNytIsb9aRo!k3ImHSRkv zYW#iwsqLTm06Dng$V)V6y`zpJ7>px_kW;Y;NxWNRYD}B2VUHsN0>uAD{-34e!K~TU zW^Zwf4DiXVDklYEhZNeIKL86j5!)k8?*A`1EbuS6a4;b_G?+Lhf3NN3;3zQwPAIN))D_7BR4d4 diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs new file mode 100644 index 00000000..6169ded5 --- /dev/null +++ b/benches/a_benchmark.rs @@ -0,0 +1,16 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +pub fn add_benchmark(c: &mut Criterion) { + let mut rvg = rs_ucan::test_utils::Rvg::deterministic(); + let int_val_1 = rvg.sample(&(0..100i32)); + let int_val_2 = rvg.sample(&(0..100i32)); + + c.bench_function("add", |b| { + b.iter(|| { + rs_ucan::add(int_val_1, int_val_2); + }) + }); +} + +criterion_group!(benches, add_benchmark); +criterion_main!(benches); diff --git a/codecov.yaml b/codecov.yml similarity index 75% rename from codecov.yaml rename to codecov.yml index c80348aa..1227d5f8 100644 --- a/codecov.yaml +++ b/codecov.yml @@ -1,6 +1,7 @@ ignore: - - "ucan/src/tests/*" - - "ucan-key-support/src/fixtures/*" + - "tests" + - "benches" + - "examples" comment: layout: "reach, diff, flags, files" diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..c7499506 --- /dev/null +++ b/deny.toml @@ -0,0 +1,200 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "deny" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +#ignore = [ +#] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "warn" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +allow = [ + "Apache-2.0", + "CC0-1.0", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib" +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # The Unicode-DFS-2016 license is necessary for unicode-ident because they + # use data from the unicode tables to generate the tables which are + # included in the application. We do not distribute those data files so + # this is not a problem for us. See https://github.com/dtolnay/unicode-ident/pull/9/files + { allow = ["Unicode-DFS-2016"], name = "unicode-ident", version = "*"}, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +#[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +#github = [""] +# 1 or more gitlab.com organizations to allow git sources for +#gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +#bitbucket = [""] diff --git a/examples/counterparts.rs b/examples/counterparts.rs new file mode 100644 index 00000000..76225502 --- /dev/null +++ b/examples/counterparts.rs @@ -0,0 +1,6 @@ +use std::error::Error; + +pub fn main() -> Result<(), Box> { + println!("Alien Shore!"); + Ok(()) +} diff --git a/flake.lock b/flake.lock index d3f4b552..04f6cbbe 100644 --- a/flake.lock +++ b/flake.lock @@ -21,11 +21,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1689321787, - "narHash": "sha256-ifk7hrfWnJaLlcjCf8YaWDR+9kQ0uT3x9eCz31D9qB0=", + "lastModified": 1695806987, + "narHash": "sha256-fX5kGs66NZIxCMcpAGIpxuftajHL8Hil1vjHmjjl118=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c11464c6625d9a71d91a3718a3567394638efc3e", + "rev": "f3dab3509afca932f3f4fd0908957709bb1c1f57", "type": "github" }, "original": { @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1689302058, - "narHash": "sha256-yD74lcHTrw4niXcE9goJLbzsgyce48rQQoy5jK5ZK40=", + "lastModified": 1695780708, + "narHash": "sha256-+0difm874E5ra98MeLxW8SfoxfL+Wzn3cLzKGGd2I4M=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "7b8dbbf4c67ed05a9bf3d9e658c12d4108bc24c8", + "rev": "e04538a3e155ebe4d15a281559119f63d33116bb", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 0120ead9..d931932b 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,7 @@ { - description = "homestar"; + description = "rs-ucan"; inputs = { - # we leverage unstable due to wasm-tools/wasm updates nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; @@ -32,7 +31,6 @@ rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { extensions = ["cargo" "clippy" "rustfmt" "rust-src" "rust-std"]; - targets = ["wasm32-unknown-unknown" "wasm32-wasi"]; }; nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; @@ -47,141 +45,15 @@ cargo-expand cargo-nextest cargo-outdated + cargo-spellcheck cargo-sort cargo-udeps cargo-watch - twiggy - wasm-tools - ]; - - ci = pkgs.writeScriptBin "ci" '' - cargo fmt --check - cargo clippy - cargo build --release - nx-test - nx-test-0 - ''; - - db = pkgs.writeScriptBin "db" '' - diesel setup - diesel migration run - ''; - - dbReset = pkgs.writeScriptBin "db-reset" '' - diesel database reset - diesel setup - diesel migration run - ''; - - doc = pkgs.writeScriptBin "doc" '' - cargo doc --no-deps --document-private-items --open - ''; - - compileWasm = pkgs.writeScriptBin "compile-wasm" '' - cargo build -p homestar-functions --target wasm32-unknown-unknown --release - ''; - - dockerBuild = arch: - pkgs.writeScriptBin "docker-${arch}" '' - docker buildx build --file docker/Dockerfile --platform=linux/${arch} -t homestar-runtime --progress=plain . - ''; - - xFunc = cmd: - pkgs.writeScriptBin "x-${cmd}" '' - cargo watch -c -x ${cmd} - ''; - - xFuncAll = cmd: - pkgs.writeScriptBin "x-${cmd}-all" '' - cargo watch -c -s "cargo ${cmd} --all-features" - ''; - - xFuncNoDefault = cmd: - pkgs.writeScriptBin "x-${cmd}-0" '' - cargo watch -c -s "cargo ${cmd} --no-default-features" - ''; - - xFuncPackage = cmd: crate: - pkgs.writeScriptBin "x-${cmd}-${crate}" '' - cargo watch -c -s "cargo ${cmd} -p homestar-${crate} --all-features" - ''; - - xFuncTest = pkgs.writeScriptBin "x-test" '' - cargo watch -c -s "cargo nextest run --nocapture && cargo test --doc" - ''; - - xFuncTestAll = pkgs.writeScriptBin "x-test-all" '' - cargo watch -c -s "cargo nextest run --all-features --nocapture \ - && cargo test --doc --all-features" - ''; - - xFuncTestNoDefault = pkgs.writeScriptBin "x-test-0" '' - cargo watch -c -s "cargo nextest run --no-default-features --nocapture \ - && cargo test --doc --no-default-features" - ''; - - xFuncTestPackage = crate: - pkgs.writeScriptBin "x-test-${crate}" '' - cargo watch -c -s "cargo nextest run -p homestar-${crate} --all-features \ - && cargo test --doc -p homestar-${crate} --all-features" - ''; - - nxTest = pkgs.writeScriptBin "nx-test" '' - cargo nextest run - cargo test --doc - ''; - - nxTestAll = pkgs.writeScriptBin "nx-test-all" '' - cargo nextest run --all-features --nocapture - cargo test --doc --all-features - ''; - - nxTestNoDefault = pkgs.writeScriptBin "nx-test-0" '' - cargo nextest run --no-default-features --nocapture - cargo test --doc --no-default-features - ''; - - wasmTest = pkgs.writeScriptBin "wasm-ex-test" '' - cargo build -p homestar-functions --features example-test --target wasm32-unknown-unknown --release - cp target/wasm32-unknown-unknown/release/homestar_functions.wasm homestar-wasm/fixtures/example_test.wasm - wasm-tools component new homestar-wasm/fixtures/example_test.wasm -o homestar-wasm/fixtures/example_test_component.wasm - ''; - - wasmAdd = pkgs.writeScriptBin "wasm-ex-add" '' - cargo build -p homestar-functions --features example-add --target wasm32-unknown-unknown --release - cp target/wasm32-unknown-unknown/release/homestar_functions.wasm homestar-wasm/fixtures/example_add.wasm - wasm-tools component new homestar-wasm/fixtures/example_add.wasm -o homestar-wasm/fixtures/example_add_component.wasm - wasm-tools print homestar-wasm/fixtures/example_add.wasm -o homestar-wasm/fixtures/example_add.wat - wasm-tools print homestar-wasm/fixtures/example_add_component.wasm -o homestar-wasm/fixtures/example_add_component.wat - ''; - - scripts = [ - ci - db - dbReset - doc - compileWasm - (builtins.map (arch: dockerBuild arch) ["amd64" "arm64"]) - (builtins.map (cmd: xFunc cmd) ["build" "check" "run" "clippy"]) - (builtins.map (cmd: xFuncAll cmd) ["build" "check" "run" "clippy"]) - (builtins.map (cmd: xFuncNoDefault cmd) ["build" "check" "run" "clippy"]) - (builtins.map (cmd: xFuncPackage cmd "core") ["build" "check" "run" "clippy"]) - (builtins.map (cmd: xFuncPackage cmd "wasm") ["build" "check" "run" "clippy"]) - (builtins.map (cmd: xFuncPackage cmd "runtime") ["build" "check" "run" "clippy"]) - xFuncTest - xFuncTestAll - xFuncTestNoDefault - (builtins.map (crate: xFuncTestPackage crate) ["core" "wasm" "guest-wasm" "runtime"]) - nxTest - nxTestAll - nxTestNoDefault - wasmTest - wasmAdd ]; in rec { devShells.default = pkgs.mkShell { - name = "homestar"; + name = "rs-ucan"; nativeBuildInputs = with pkgs; [ # The ordering of these two items is important. For nightly rustfmt to be used instead of @@ -189,23 +61,18 @@ # because native build inputs are added to $PATH in the order they're listed here. nightly-rustfmt rust-toolchain - rust-analyzer - pkg-config pre-commit - diesel-cli + protobuf direnv self.packages.${system}.irust ] ++ format-pkgs ++ cargo-installs - ++ scripts ++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Security darwin.apple_sdk.frameworks.CoreFoundation darwin.apple_sdk.frameworks.Foundation ]; - NIX_PATH = "nixpkgs=" + pkgs.path; - RUST_BACKTRACE = 1; shellHook = '' [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg diff --git a/release-please-config.json b/release-please-config.json index 3e4b9f8f..1c259ef2 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,11 +1,10 @@ { - "plugins": ["cargo-workspace"], + "plugins": [], "changelog-path": "CHANGELOG.md", "release-type": "rust", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "packages": { - "ucan": {}, - "ucan-key-support": {} + ".": {} } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..0d845439 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,30 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] +#![deny(unreachable_pub)] + +//! rs-ucan + +/// Test utilities. +#[cfg(any(test, feature = "test_utils"))] +#[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] +pub mod test_utils; + +/// Add two integers together. +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +/// Multiplies two integers together. +pub fn mult(a: i32, b: i32) -> i32 { + a * b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mult() { + assert_eq!(mult(3, 2), 6); + } +} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs new file mode 100644 index 00000000..4a30e2ad --- /dev/null +++ b/src/test_utils/mod.rs @@ -0,0 +1,5 @@ +/// Random value generator for sampling data. +#[cfg(feature = "test_utils")] +mod rvg; +#[cfg(feature = "test_utils")] +pub use rvg::*; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs new file mode 100644 index 00000000..30e5c3ef --- /dev/null +++ b/src/test_utils/rvg.rs @@ -0,0 +1,63 @@ +use proptest::{ + collection::vec, + strategy::{Strategy, ValueTree}, + test_runner::{Config, TestRunner}, +}; + +/// A random value generator (RVG), which, given proptest strategies, will +/// generate random values based on those strategies. +#[derive(Debug, Default)] +pub struct Rvg { + runner: TestRunner, +} + +impl Rvg { + /// Creates a new RVG with the default random number generator. + pub fn new() -> Self { + Rvg { + runner: TestRunner::new(Config::default()), + } + } + + /// Creates a new RVG with a deterministic random number generator, + /// using the same seed across test runs. + pub fn deterministic() -> Self { + Rvg { + runner: TestRunner::deterministic(), + } + } + + /// Samples a value for the given strategy. + /// + /// # Example + /// + /// ``` + /// use rs_ucan::test_utils::Rvg; + /// + /// let mut rvg = Rvg::new(); + /// let int = rvg.sample(&(0..100i32)); + /// ``` + pub fn sample(&mut self, strategy: &S) -> S::Value { + strategy + .new_tree(&mut self.runner) + .expect("No value can be generated") + .current() + } + + /// Samples a vec of some length with a value for the given strategy. + /// + /// # Example + /// + /// ``` + /// use rs_ucan::test_utils::Rvg; + /// + /// let mut rvg = Rvg::new(); + /// let ints = rvg.sample_vec(&(0..100i32), 10); + /// ``` + pub fn sample_vec(&mut self, strategy: &S, len: usize) -> Vec { + vec(strategy, len..=len) + .new_tree(&mut self.runner) + .expect("No value can be generated") + .current() + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 00000000..9b72054b --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,4 @@ +#[test] +fn test_add() { + assert_eq!(rs_ucan::add(3, 2), 5); +} diff --git a/ucan-key-support/CHANGELOG.md b/ucan-key-support/CHANGELOG.md deleted file mode 100644 index 43566890..00000000 --- a/ucan-key-support/CHANGELOG.md +++ /dev/null @@ -1,67 +0,0 @@ -# Changelog - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.1.2 to 0.2.0 - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.2.0 to 0.3.0 - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.3.0 to 0.3.1 - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.3.1 to 0.3.2 - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.3.2 to 0.4.0 - -## [0.1.2](https://github.com/ucan-wg/rs-ucan/compare/ucan-key-support-v0.1.1...ucan-key-support-v0.1.2) (2023-04-22) - - -### Features - -* Upgrade deps: `cid`, `libipld`, `base64`, `p256`, `rsa` ([#78](https://github.com/ucan-wg/rs-ucan/issues/78)) ([cfeed69](https://github.com/ucan-wg/rs-ucan/commit/cfeed6903d9a53d3728f35914d670e3b7920d88d)) - - -### Dependencies - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.1.1 to 0.1.2 - -## [0.1.1](https://github.com/ucan-wg/rs-ucan/compare/ucan-key-support-v0.1.0...ucan-key-support-v0.1.1) (2023-03-13) - - -### Features - -* ucan-key-support: add P-256 key support (aka ESRSA, aka secp256r1) ([#46](https://github.com/ucan-wg/rs-ucan/issues/46)) ([36fe961](https://github.com/ucan-wg/rs-ucan/commit/36fe9617513a25c7815772204a9426e0ca75ef7e)) - - -### Dependencies - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.1.0 to 0.1.1 - -## [0.1.0](https://github.com/ucan-wg/rs-ucan/compare/ucan-key-support-v0.1.0...ucan-key-support-v0.1.0) (2022-11-29) - - -### ⚠ BREAKING CHANGES - -* New version requirements include rsa@0.7 - -### Miscellaneous Chores - -* rsa dep changes ([#58](https://github.com/ucan-wg/rs-ucan/issues/58)) ([ecde6ff](https://github.com/ucan-wg/rs-ucan/commit/ecde6ffce6ad07c1ccb1c9d2257a3f7650189afc)) - - -### Dependencies - -* The following workspace dependencies were updated - * dependencies - * ucan bumped from 0.7.0-alpha.1 to 0.1.0 diff --git a/ucan-key-support/Cargo.toml b/ucan-key-support/Cargo.toml deleted file mode 100644 index e8cbe332..00000000 --- a/ucan-key-support/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "ucan-key-support" -description = "Ready to use SigningKey implementations for the ucan crate" -edition = "2021" -keywords = ["ucan", "authz", "jwt", "pki"] -categories = [ - "authorization", - "cryptography", - "encoding", - "web-programming" -] -documentation = "https://docs.rs/ucan" -repository = "https://github.com/cdata/rs-ucan/" -homepage = "https://github.com/cdata/rs-ucan" -license = "Apache-2.0" -readme = "README.md" -version = "0.1.7" - -[features] -default = [] - -[dependencies] -anyhow = "1.0" -async-trait = "0.1" -bs58 = "0.5" -ed25519-zebra = "3.1" -log = "0.4" -rsa = "0.9" -p256 = "0.13" -sha2 = { version = "0.10", features = ["oid"] } -ucan = { path = "../ucan", version = "0.4.0" } - -[build-dependencies] -npm_rs = "1.0" - -[dev-dependencies] -# NOTE: This is needed so that rand can be included in WASM builds -getrandom = { version = "0.2", features = ["js"] } -rand = "0.8" -wasm-bindgen-test = "0.3" - -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "1.21", features = ["macros", "rt"] } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = { version = "0.2" } -wasm-bindgen-futures = { version = "0.4" } -js-sys = { version = "0.3" } - -[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] -version = "0.3" -features = [ - 'Window', - 'SubtleCrypto', - 'Crypto', - 'CryptoKey', - 'CryptoKeyPair', - 'DedicatedWorkerGlobalScope' -] - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -pollster = "0.3.0" diff --git a/ucan-key-support/README.md b/ucan-key-support/README.md deleted file mode 100644 index 4f09b28b..00000000 --- a/ucan-key-support/README.md +++ /dev/null @@ -1,36 +0,0 @@ - - - -## - -This is an auxilliary crate containing ready-to-use `SigningKey` implementations -for the [Rust UCAN implementation][rs-ucan]. - -[rs-ucan]: https://docs.rs/ucan diff --git a/ucan-key-support/src/ed25519.rs b/ucan-key-support/src/ed25519.rs deleted file mode 100644 index 4de05858..00000000 --- a/ucan-key-support/src/ed25519.rs +++ /dev/null @@ -1,92 +0,0 @@ -use anyhow::{anyhow, Result}; -use async_trait::async_trait; - -use ed25519_zebra::{ - Signature, SigningKey as Ed25519PrivateKey, VerificationKey as Ed25519PublicKey, -}; - -use ucan::crypto::KeyMaterial; - -pub use ucan::crypto::{did::ED25519_MAGIC_BYTES, JwtSignatureAlgorithm}; - -pub fn bytes_to_ed25519_key(bytes: Vec) -> Result> { - let public_key = Ed25519PublicKey::try_from(bytes.as_slice())?; - Ok(Box::new(Ed25519KeyMaterial(public_key, None))) -} - -#[derive(Clone)] -pub struct Ed25519KeyMaterial(pub Ed25519PublicKey, pub Option); - -#[cfg_attr(target_arch="wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl KeyMaterial for Ed25519KeyMaterial { - fn get_jwt_algorithm_name(&self) -> String { - JwtSignatureAlgorithm::EdDSA.to_string() - } - - async fn get_did(&self) -> Result { - let bytes = [ED25519_MAGIC_BYTES, self.0.as_ref()].concat(); - Ok(format!("did:key:z{}", bs58::encode(bytes).into_string())) - } - - async fn sign(&self, payload: &[u8]) -> Result> { - match self.1 { - Some(private_key) => { - let signature = private_key.sign(payload); - let bytes: [u8; 64] = signature.into(); - Ok(bytes.to_vec()) - } - None => Err(anyhow!("No private key; cannot sign data")), - } - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - let signature = Signature::try_from(signature)?; - self.0 - .verify(&signature, payload) - .map_err(|error| anyhow!("Could not verify signature: {:?}", error)) - } -} - -#[cfg(test)] -mod tests { - use super::{bytes_to_ed25519_key, Ed25519KeyMaterial, ED25519_MAGIC_BYTES}; - use ed25519_zebra::{SigningKey as Ed25519PrivateKey, VerificationKey as Ed25519PublicKey}; - use ucan::{ - builder::UcanBuilder, - crypto::{did::DidParser, KeyMaterial}, - ucan::Ucan, - }; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_can_sign_and_verify_a_ucan() { - let rng = rand::thread_rng(); - let private_key = Ed25519PrivateKey::new(rng); - let public_key = Ed25519PublicKey::from(&private_key); - - let key_material = Ed25519KeyMaterial(public_key, Some(private_key)); - let token_string = UcanBuilder::default() - .issued_by(&key_material) - .for_audience(key_material.get_did().await.unwrap().as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut did_parser = DidParser::new(&[(ED25519_MAGIC_BYTES, bytes_to_ed25519_key)]); - - let ucan = Ucan::try_from(token_string).unwrap(); - ucan.check_signature(&mut did_parser).await.unwrap(); - } -} diff --git a/ucan-key-support/src/fixtures/rsa_key.pk8 b/ucan-key-support/src/fixtures/rsa_key.pk8 deleted file mode 100644 index 1af064cc8e04650db2956ee482fa9728c7e8d0ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1216 zcmV;x1V8&Qf&{z*0RS)!1_>&LNQUrrZ9p8q5=T`0)hbn0KTe`S*J(Xz$zO{0aNh$Hj>IvBp1{H$Tl24wl7}i}#!LlmEuC&hNT}R{m!rpra`K zXjvGQ*clvb>q!R{S3;t;Gc$$&n{^qkG=7Q+lOzGTNP)my{#5}f&w!(VD|0tGd>3J@ zru|o==Euuf8G%B@?=16@->81{*H`g&%+M$-v}e+`mb+4c2RvWQPNz>~*W8&a3C}5P|VZz&m(X`&^*5~VS5!-i|kW$y$6!b!S80O4q4?Xb$fq?+u zn7)@Mn@Z@*&pR4u*LZjQ9rhOHQ2@LP@%srLtzWupmCEg6k_Aj?v50ryGsm#=Q&r&F zHc)&vCmG(QA--Fm_$@4y*9xx25?Vn!e#XaKy)39*49dO~=5gBY9RAsV*x8~AHJ#co zjyVl!Z9&EYj%{ph2H8o$$p!x!eZ$QHfq?+pOdf7J=Bir< z{k*xP(s>@xy;;k`B#n^iNoQ-As$+PJe)JpmoEUsnsp1evNkein&LBmMryG27an;v^ zTY-J|A6Ffk|Kz=UYS{@+cu?~-X!EjqDKW)J5A zfq*TNCjJedbL0SR>j2g7Txl=lH(arDKJgTaEBwyb%@X8{asY9^ivH-cjycFK_jy10UmC{OOL?sGk**zZ+Q%0vV2FkJ)7n=;+^Sf!M%X&HGN2X#iFE1jqa93q9m^I4BU7chFCPM(r0{?>Ygz5k`bh}NE}cM;^$lJ$z? zbFFaxj^~I%5fHGaF7RaWp`oKhbph&pe1kB#+1U0>A e@wPtG) -> Result> { - let public_key = P256PublicKey::try_from(bytes.as_slice())?; - Ok(Box::new(P256KeyMaterial(public_key, None))) -} - -/// Support for NIST P-256 keys, aka secp256r1, aka ES256 -#[derive(Clone)] -pub struct P256KeyMaterial(pub P256PublicKey, pub Option); - -#[cfg_attr(target_arch="wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl KeyMaterial for P256KeyMaterial { - fn get_jwt_algorithm_name(&self) -> String { - JwtSignatureAlgorithm::ES256.to_string() - } - - async fn get_did(&self) -> Result { - let bytes = [P256_MAGIC_BYTES, &self.0.to_encoded_point(true).to_bytes()].concat(); - Ok(format!("did:key:z{}", bs58::encode(bytes).into_string())) - } - - async fn sign(&self, payload: &[u8]) -> Result> { - match self.1 { - Some(ref private_key) => { - let signature: ecdsa::Signature = private_key.sign(payload); - Ok(signature.to_vec()) - } - None => Err(anyhow!("No private key; cannot sign data")), - } - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - let signature = Signature::try_from(signature)?; - self.0 - .verify(payload, &signature) - .map_err(|error| anyhow!("Could not verify signature: {:?}", error)) - } -} - -#[cfg(test)] -mod tests { - use super::{bytes_to_p256_key, P256KeyMaterial, P256_MAGIC_BYTES}; - use p256::ecdsa::{SigningKey as P256PrivateKey, VerifyingKey as P256PublicKey}; - use ucan::{ - builder::UcanBuilder, - crypto::{did::DidParser, KeyMaterial}, - ucan::Ucan, - }; - - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_can_sign_and_verify_a_ucan() { - let private_key = P256PrivateKey::random(&mut p256::elliptic_curve::rand_core::OsRng); - let public_key = P256PublicKey::from(&private_key); - - let key_material = P256KeyMaterial(public_key, Some(private_key)); - let token_string = UcanBuilder::default() - .issued_by(&key_material) - .for_audience(key_material.get_did().await.unwrap().as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut did_parser = DidParser::new(&[(P256_MAGIC_BYTES, bytes_to_p256_key)]); - - let ucan = Ucan::try_from(token_string).unwrap(); - ucan.check_signature(&mut did_parser).await.unwrap(); - } -} diff --git a/ucan-key-support/src/rsa.rs b/ucan-key-support/src/rsa.rs deleted file mode 100644 index 29219115..00000000 --- a/ucan-key-support/src/rsa.rs +++ /dev/null @@ -1,114 +0,0 @@ -use anyhow::{anyhow, Result}; -use async_trait::async_trait; - -use rsa::{ - pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}, - Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, -}; - -use sha2::{Digest, Sha256}; -use ucan::crypto::{JwtSignatureAlgorithm, KeyMaterial}; - -pub use ucan::crypto::did::RSA_MAGIC_BYTES; - -pub fn bytes_to_rsa_key(bytes: Vec) -> Result> { - println!("Trying to parse RSA key..."); - // NOTE: DID bytes are PKCS1, but we store RSA keys as PKCS8 - let public_key = RsaPublicKey::from_pkcs1_der(&bytes)?; - - Ok(Box::new(RsaKeyMaterial(public_key, None))) -} - -#[derive(Clone)] -pub struct RsaKeyMaterial(pub RsaPublicKey, pub Option); - -#[cfg_attr(target_arch="wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl KeyMaterial for RsaKeyMaterial { - fn get_jwt_algorithm_name(&self) -> String { - JwtSignatureAlgorithm::RS256.to_string() - } - - async fn get_did(&self) -> Result { - let bytes = match self.0.to_pkcs1_der() { - Ok(document) => [RSA_MAGIC_BYTES, document.as_bytes()].concat(), - Err(error) => { - // TODO: Probably shouldn't swallow this error... - warn!("Could not get RSA public key bytes for DID: {:?}", error); - Vec::new() - } - }; - Ok(format!("did:key:z{}", bs58::encode(bytes).into_string())) - } - - async fn sign(&self, payload: &[u8]) -> Result> { - let mut hasher = Sha256::new(); - hasher.update(payload); - let hashed = hasher.finalize(); - - match &self.1 { - Some(private_key) => { - let padding = Pkcs1v15Sign::new::(); - let signature = private_key.sign(padding, hashed.as_ref())?; - info!("SIGNED!"); - Ok(signature) - } - None => Err(anyhow!("No private key; cannot sign data")), - } - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - let mut hasher = Sha256::new(); - hasher.update(payload); - let hashed = hasher.finalize(); - let padding = Pkcs1v15Sign::new::(); - - self.0 - .verify(padding, hashed.as_ref(), signature) - .map_err(|error| anyhow!(error)) - } -} - -#[cfg(test)] -mod tests { - use super::{bytes_to_rsa_key, RsaKeyMaterial, RSA_MAGIC_BYTES}; - - use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey, RsaPublicKey}; - use ucan::{ - builder::UcanBuilder, - crypto::{did::DidParser, KeyMaterial}, - ucan::Ucan, - }; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_can_sign_and_verify_a_ucan() { - let private_key = - RsaPrivateKey::from_pkcs8_der(include_bytes!("./fixtures/rsa_key.pk8")).unwrap(); - let public_key = RsaPublicKey::from(&private_key); - - let key_material = RsaKeyMaterial(public_key, Some(private_key)); - let token_string = UcanBuilder::default() - .issued_by(&key_material) - .for_audience(key_material.get_did().await.unwrap().as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut did_parser = DidParser::new(&[(RSA_MAGIC_BYTES, bytes_to_rsa_key)]); - - let ucan = Ucan::try_from(token_string).unwrap(); - ucan.check_signature(&mut did_parser).await.unwrap(); - } -} diff --git a/ucan-key-support/src/web_crypto.rs b/ucan-key-support/src/web_crypto.rs deleted file mode 100644 index 4d1ea625..00000000 --- a/ucan-key-support/src/web_crypto.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::rsa::RsaKeyMaterial; -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use js_sys::{Array, ArrayBuffer, Boolean, Object, Reflect, Uint8Array}; -use rsa::{pkcs1::DecodeRsaPublicKey, RsaPublicKey}; -use ucan::crypto::{JwtSignatureAlgorithm, KeyMaterial}; -use wasm_bindgen::{JsCast, JsValue}; -use wasm_bindgen_futures::JsFuture; -use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto}; - -pub fn convert_spki_to_rsa_public_key(spki_bytes: &[u8]) -> Result> { - // TODO: This is maybe a not-good, hacky solution; verifying the first - // 24 bytes would be more wholesome - // SEE: https://github.com/ucan-wg/ts-ucan/issues/30#issuecomment-1007333500 - Ok(Vec::from(&spki_bytes[24..])) -} - -pub const WEB_CRYPTO_RSA_ALGORITHM: &str = "RSASSA-PKCS1-v1_5"; - -#[derive(Debug)] -pub struct WebCryptoRsaKeyMaterial(pub CryptoKey, pub Option); - -impl WebCryptoRsaKeyMaterial { - fn get_subtle_crypto() -> Result { - // NOTE: Accessing either `Window` or `DedicatedWorkerGlobalScope` in - // a context where they are not defined will cause a JS error, so we - // do a sneaky workaround here: - let global = js_sys::global(); - match Reflect::get(&global, &JsValue::from("crypto")) { - Ok(value) => Ok(value.dyn_into::().expect("Unexpected API").subtle()), - _ => Err(anyhow!("Could not access WebCrypto API")), - } - } - - fn private_key(&self) -> Result<&CryptoKey> { - match &self.1 { - Some(key) => Ok(key), - None => Err(anyhow!("No private key configured")), - } - } - - pub async fn generate(key_size: Option) -> Result { - let subtle_crypto = Self::get_subtle_crypto()?; - let algorithm = Object::new(); - - Reflect::set( - &algorithm, - &JsValue::from("name"), - &JsValue::from(WEB_CRYPTO_RSA_ALGORITHM), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - Reflect::set( - &algorithm, - &JsValue::from("modulusLength"), - &JsValue::from(key_size.unwrap_or(2048)), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - let public_exponent = Uint8Array::new(&JsValue::from(3u8)); - public_exponent.copy_from(&[0x01u8, 0x00, 0x01]); - - Reflect::set( - &algorithm, - &JsValue::from("publicExponent"), - &JsValue::from(public_exponent), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - let hash = Object::new(); - - Reflect::set(&hash, &JsValue::from("name"), &JsValue::from("SHA-256")) - .map_err(|error| anyhow!("{:?}", error))?; - - Reflect::set(&algorithm, &JsValue::from("hash"), &JsValue::from(hash)) - .map_err(|error| anyhow!("{:?}", error))?; - - let uses = Array::new(); - - uses.push(&JsValue::from("sign")); - uses.push(&JsValue::from("verify")); - - let crypto_key_pair_generates = subtle_crypto - .generate_key_with_object(&algorithm, false, &uses) - .map_err(|error| anyhow!("{:?}", error))?; - let crypto_key_pair = CryptoKeyPair::from( - JsFuture::from(crypto_key_pair_generates) - .await - .map_err(|error| anyhow!("{:?}", error))?, - ); - - let public_key = CryptoKey::from( - Reflect::get(&crypto_key_pair, &JsValue::from("publicKey")) - .map_err(|error| anyhow!("{:?}", error))?, - ); - let private_key = CryptoKey::from( - Reflect::get(&crypto_key_pair, &JsValue::from("privateKey")) - .map_err(|error| anyhow!("{:?}", error))?, - ); - - Ok(WebCryptoRsaKeyMaterial(public_key, Some(private_key))) - } -} - -#[async_trait(?Send)] -impl KeyMaterial for WebCryptoRsaKeyMaterial { - fn get_jwt_algorithm_name(&self) -> String { - JwtSignatureAlgorithm::RS256.to_string() - } - - async fn get_did(&self) -> Result { - let public_key = &self.0; - let subtle_crypto = Self::get_subtle_crypto()?; - - let public_key_bytes = Uint8Array::new( - &JsFuture::from( - subtle_crypto - .export_key("spki", public_key) - .expect("Could not access key extraction API"), - ) - .await - .expect("Failed to extract public key bytes") - .dyn_into::() - .expect("Bytes were not an ArrayBuffer"), - ); - - let public_key_bytes = public_key_bytes.to_vec(); - let public_key_bytes = convert_spki_to_rsa_public_key(public_key_bytes.as_slice())?; - let public_key = RsaPublicKey::from_pkcs1_der(&public_key_bytes)?; - - Ok(RsaKeyMaterial(public_key, None).get_did().await?) - } - - async fn sign(&self, payload: &[u8]) -> Result> { - let key = self.private_key()?; - let subtle_crypto = Self::get_subtle_crypto()?; - let algorithm = Object::new(); - - Reflect::set( - &algorithm, - &JsValue::from("name"), - &JsValue::from(WEB_CRYPTO_RSA_ALGORITHM), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - Reflect::set( - &algorithm, - &JsValue::from("saltLength"), - &JsValue::from(128u8), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - let data = unsafe { Uint8Array::view(payload) }; - - let result = Uint8Array::new( - &JsFuture::from( - subtle_crypto - .sign_with_object_and_buffer_source(&algorithm, key, &data) - .map_err(|error| anyhow!("{:?}", error))?, - ) - .await - .map_err(|error| anyhow!("{:?}", error))?, - ); - - Ok(result.to_vec()) - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - let key = &self.0; - let subtle_crypto = Self::get_subtle_crypto()?; - let algorithm = Object::new(); - - Reflect::set( - &algorithm, - &JsValue::from("name"), - &JsValue::from(WEB_CRYPTO_RSA_ALGORITHM), - ) - .map_err(|error| anyhow!("{:?}", error))?; - Reflect::set( - &algorithm, - &JsValue::from("saltLength"), - &JsValue::from(128u8), - ) - .map_err(|error| anyhow!("{:?}", error))?; - - let signature = unsafe { Uint8Array::view(signature) }; - let data = unsafe { Uint8Array::view(payload) }; - - let valid = JsFuture::from( - subtle_crypto - .verify_with_object_and_buffer_source_and_buffer_source( - &algorithm, key, &signature, &data, - ) - .map_err(|error| anyhow!("{:?}", error))?, - ) - .await - .map_err(|error| anyhow!("{:?}", error))? - .dyn_into::() - .map_err(|error| anyhow!("{:?}", error))?; - - match valid.is_truthy() { - true => Ok(()), - false => Err(anyhow!("Could not verify signature")), - } - } -} - -#[cfg(test)] -mod tests { - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - use super::WebCryptoRsaKeyMaterial; - use crate::rsa::{bytes_to_rsa_key, RSA_MAGIC_BYTES}; - use ucan::{ - builder::UcanBuilder, - crypto::{did::DidParser, KeyMaterial}, - ucan::Ucan, - }; - - #[wasm_bindgen_test] - async fn it_can_sign_and_verify_data() { - let key_material = WebCryptoRsaKeyMaterial::generate(None).await.unwrap(); - let data = &[0xdeu8, 0xad, 0xbe, 0xef]; - let signature = key_material.sign(data).await.unwrap(); - - key_material.verify(data, signature.as_ref()).await.unwrap(); - } - - #[wasm_bindgen_test] - async fn it_produces_a_legible_rsa_did() { - let key_material = WebCryptoRsaKeyMaterial::generate(None).await.unwrap(); - let did = key_material.get_did().await.unwrap(); - let mut did_parser = DidParser::new(&[(RSA_MAGIC_BYTES, bytes_to_rsa_key)]); - - did_parser.parse(&did).unwrap(); - } - - #[wasm_bindgen_test] - async fn it_signs_ucans_that_can_be_verified_elsewhere() { - let key_material = WebCryptoRsaKeyMaterial::generate(None).await.unwrap(); - - let token = UcanBuilder::default() - .issued_by(&key_material) - .for_audience(key_material.get_did().await.unwrap().as_str()) - .with_lifetime(300) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut did_parser = DidParser::new(&[(RSA_MAGIC_BYTES, bytes_to_rsa_key)]); - let ucan = Ucan::try_from(token.as_str()).unwrap(); - - ucan.check_signature(&mut did_parser).await.unwrap(); - } -} diff --git a/ucan-key-support/webdriver.json b/ucan-key-support/webdriver.json deleted file mode 100644 index 26fea6ca..00000000 --- a/ucan-key-support/webdriver.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "os": "Windows", - "os_version": "11", - "browser": "chrome", - "browser_version": "latest" -} diff --git a/ucan/CHANGELOG.md b/ucan/CHANGELOG.md deleted file mode 100644 index 6385fa94..00000000 --- a/ucan/CHANGELOG.md +++ /dev/null @@ -1,84 +0,0 @@ -# Changelog - -## [0.4.0](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.3.2...ucan-v0.4.0) (2023-06-27) - - -### ⚠ BREAKING CHANGES - -* Update capabilites in line with UCAN 0.9/0.10 specs ([#105](https://github.com/ucan-wg/rs-ucan/issues/105)) -* Update `fct`/`ucv` layout for 0.10.0 spec ([#108](https://github.com/ucan-wg/rs-ucan/issues/108)) -* Support generic hashers in `UcanBuilder` and `ProofChain`. ([#89](https://github.com/ucan-wg/rs-ucan/issues/89)) - -### Features - -* Allow nullable expiry, per 0.9.0 spec. Fixes [#23](https://github.com/ucan-wg/rs-ucan/issues/23) ([#95](https://github.com/ucan-wg/rs-ucan/issues/95)) ([12d4756](https://github.com/ucan-wg/rs-ucan/commit/12d475606da940b64654f17807adf592551982d0)) -* Support generic hashers in `UcanBuilder` and `ProofChain`. ([#89](https://github.com/ucan-wg/rs-ucan/issues/89)) ([e057f87](https://github.com/ucan-wg/rs-ucan/commit/e057f87c7b278d18e77b1d3d213656d18b1a2fee)) -* Update `fct`/`ucv` layout for 0.10.0 spec ([#108](https://github.com/ucan-wg/rs-ucan/issues/108)) ([ae19741](https://github.com/ucan-wg/rs-ucan/commit/ae197415048da201f7d75bf08cdb010b4f657895)) -* Update capabilites in line with UCAN 0.9/0.10 specs ([#105](https://github.com/ucan-wg/rs-ucan/issues/105)) ([0bdf98f](https://github.com/ucan-wg/rs-ucan/commit/0bdf98f9043e753026711fb19449ab0bc6d87fc7)) - -## [0.3.2](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.3.1...ucan-v0.3.2) (2023-05-25) - - -### Features - -* `fct` and `prf` are now optional fields. Fixes [#98](https://github.com/ucan-wg/rs-ucan/issues/98) ([#99](https://github.com/ucan-wg/rs-ucan/issues/99)) ([6802b5c](https://github.com/ucan-wg/rs-ucan/commit/6802b5c85ce2b16680baa86342e6154896712041)) - -## [0.3.1](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.3.0...ucan-v0.3.1) (2023-05-23) - - -### Features - -* add PartialEq, Eq traits to Ucan. Fixes [#90](https://github.com/ucan-wg/rs-ucan/issues/90) ([#91](https://github.com/ucan-wg/rs-ucan/issues/91)) ([27c3628](https://github.com/ucan-wg/rs-ucan/commit/27c36288fc47bd53ab6e8f4c3e8a596714dcc6ff)) - -## [0.3.0](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.2.0...ucan-v0.3.0) (2023-05-22) - - -### ⚠ BREAKING CHANGES - -* Migrate default hashing from blake2b to blake3. ([#85](https://github.com/ucan-wg/rs-ucan/issues/85)) -* Remove `stdweb` feature from instant crate to circumvent downstream issues with `stdweb/wasm-bindgen` ([#86](https://github.com/ucan-wg/rs-ucan/issues/86)) - -### Features - -* Migrate default hashing from blake2b to blake3. ([#85](https://github.com/ucan-wg/rs-ucan/issues/85)) ([205cb96](https://github.com/ucan-wg/rs-ucan/commit/205cb962fcc99814caac8e1b9d4f8ffd956eb184)) - - -### Bug Fixes - -* Remove `stdweb` feature from instant crate to circumvent downstream issues with `stdweb/wasm-bindgen` ([#86](https://github.com/ucan-wg/rs-ucan/issues/86)) ([67ec64d](https://github.com/ucan-wg/rs-ucan/commit/67ec64db527b8bfadc4a219a65b580bdbc459640)) - -## [0.2.0](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.1.2...ucan-v0.2.0) (2023-05-04) - - -### ⚠ BREAKING CHANGES - -* Custom 'now' for proof chain validation ([#83](https://github.com/ucan-wg/rs-ucan/issues/83)) - -### Features - -* Custom 'now' for proof chain validation ([#83](https://github.com/ucan-wg/rs-ucan/issues/83)) ([1732a89](https://github.com/ucan-wg/rs-ucan/commit/1732a8911b67546f446126e4d469126f61769b44)) - -## [0.1.2](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.1.1...ucan-v0.1.2) (2023-04-22) - - -### Features - -* Upgrade deps: `cid`, `libipld`, `base64`, `p256`, `rsa` ([#78](https://github.com/ucan-wg/rs-ucan/issues/78)) ([cfeed69](https://github.com/ucan-wg/rs-ucan/commit/cfeed6903d9a53d3728f35914d670e3b7920d88d)) - -## [0.1.1](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.1.0...ucan-v0.1.1) (2023-03-13) - - -### Features - -* More derives for use in other libs ([#75](https://github.com/ucan-wg/rs-ucan/issues/75)) ([e60715f](https://github.com/ucan-wg/rs-ucan/commit/e60715f94f3b15b27ae7c1443cd4abae983d93ae)) - -## [0.1.0](https://github.com/ucan-wg/rs-ucan/compare/ucan-v0.1.0...ucan-v0.1.0) (2022-11-29) - - -### ⚠ BREAKING CHANGES - -* New version requirements include `cid@0.9`, `libipld-core@0.15` and `libipld-json@0.15` - -### Miscellaneous Chores - -* Update IPLD-adjacent crates ([#55](https://github.com/ucan-wg/rs-ucan/issues/55)) ([bf55a3f](https://github.com/ucan-wg/rs-ucan/commit/bf55a3ffad0095d88c6b33b0cd6504e66918064a)) diff --git a/ucan/Cargo.toml b/ucan/Cargo.toml deleted file mode 100644 index 79967795..00000000 --- a/ucan/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "ucan" -description = "Implement UCAN-based authorization with conciseness and ease!" -keywords = ["ucan", "authz", "jwt", "pki"] -categories = [ - "authentication", - "cryptography", - "encoding", - "web-programming" -] -documentation = "https://docs.rs/ucan" -repository = "https://github.com/cdata/rs-ucan/" -homepage = "https://github.com/cdata/rs-ucan" -license = "Apache-2.0" -readme = "README.md" -version = "0.4.0" -edition = "2021" - -[features] -default = [] - -[dependencies] -anyhow = "1.0" -async-recursion = "1.0" -async-trait = "0.1" -base64 = "0.21" -bs58 = "0.5" -cid = "0.10" -futures = "0.3" -instant = { version = "0.1", features = ["wasm-bindgen"] } -libipld-core = { version = "0.16", features = ["serde-codec", "serde"] } -libipld-json = "0.16" -log = "0.4" -rand = "0.8" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -strum = "0.24" -strum_macros = "0.25" -unsigned-varint = "0.7" -url = "2.0" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -# NOTE: This is needed so that rand can be included in WASM builds -getrandom = { version = "~0.2", features = ["js"] } - -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "^1", features = ["macros", "test-util"] } - -[dev-dependencies] -did-key = "0.2" -serde_ipld_dagcbor = "0.3" -wasm-bindgen-test = "0.3" diff --git a/ucan/LICENSE b/ucan/LICENSE deleted file mode 100644 index c61b6639..00000000 --- a/ucan/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ucan/README.md b/ucan/README.md deleted file mode 100644 index 11fefd37..00000000 --- a/ucan/README.md +++ /dev/null @@ -1,39 +0,0 @@ -
- - rs-ucan Logo - - -

ucan

- -

- - Crate Information - - - Code Coverage - - - Build Status - - - License - - - Docs - - - Discord - -

-
- - -## - -This is a Rust library to help the next generation of web applications make use -of UCANs in their authorization flows. To learn more about UCANs and how you -might use them in your application, visit [https://ucan.xyz][ucan website] or -read the [spec][spec]. - -[spec]: https://github.com/ucan-wg/spec -[ucan website]: https://ucan.xyz diff --git a/ucan/src/builder.rs b/ucan/src/builder.rs deleted file mode 100644 index 44fb9cb9..00000000 --- a/ucan/src/builder.rs +++ /dev/null @@ -1,310 +0,0 @@ -use std::collections::BTreeMap; - -use crate::{ - capability::{proof::ProofDelegationSemantics, Capability, CapabilitySemantics}, - crypto::KeyMaterial, - serde::Base64Encode, - time::now, - ucan::{FactsMap, Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, -}; -use anyhow::{anyhow, Result}; -use base64::Engine; -use cid::multihash::Code; -use log::warn; -use rand::Rng; -use serde::{de::DeserializeOwned, Serialize}; - -/// A signable is a UCAN that has all the state it needs in order to be signed, -/// but has not yet been signed. -/// NOTE: This may be useful for bespoke signing flows down the road. It is -/// meant to approximate the way that ts-ucan produces an unsigned intermediate -/// artifact (e.g., ) -pub struct Signable<'a, K> -where - K: KeyMaterial, -{ - pub issuer: &'a K, - pub audience: String, - - pub capabilities: Vec, - - pub expiration: Option, - pub not_before: Option, - - pub facts: FactsMap, - pub proofs: Vec, - pub add_nonce: bool, -} - -impl<'a, K> Signable<'a, K> -where - K: KeyMaterial, -{ - /// The header field components of the UCAN JWT - pub fn ucan_header(&self) -> UcanHeader { - UcanHeader { - alg: self.issuer.get_jwt_algorithm_name(), - typ: "JWT".into(), - } - } - - /// The payload field components of the UCAN JWT - pub async fn ucan_payload(&self) -> Result { - let nonce = match self.add_nonce { - true => Some( - base64::engine::general_purpose::URL_SAFE_NO_PAD - .encode(rand::thread_rng().gen::<[u8; 32]>()), - ), - false => None, - }; - - let facts = if self.facts.is_empty() { - None - } else { - Some(self.facts.clone()) - }; - - let proofs = if self.proofs.is_empty() { - None - } else { - Some(self.proofs.clone()) - }; - - Ok(UcanPayload { - ucv: UCAN_VERSION.into(), - aud: self.audience.clone(), - iss: self.issuer.get_did().await?, - exp: self.expiration, - nbf: self.not_before, - nnc: nonce, - cap: self.capabilities.clone().try_into()?, - fct: facts, - prf: proofs, - }) - } - - /// Produces a Ucan, which contains finalized UCAN fields along with signed - /// data suitable for encoding as a JWT token string - pub async fn sign(&self) -> Result { - let header = self.ucan_header(); - let payload = self - .ucan_payload() - .await - .expect("Unable to generate UCAN payload"); - - let header_base64 = header.jwt_base64_encode()?; - let payload_base64 = payload.jwt_base64_encode()?; - - let data_to_sign = format!("{header_base64}.{payload_base64}") - .as_bytes() - .to_vec(); - let signature = self.issuer.sign(data_to_sign.as_slice()).await?; - - Ok(Ucan::new(header, payload, data_to_sign, signature)) - } -} - -/// A builder API for UCAN tokens -#[derive(Clone)] -pub struct UcanBuilder<'a, K> -where - K: KeyMaterial, -{ - issuer: Option<&'a K>, - audience: Option, - - capabilities: Vec, - - lifetime: Option, - expiration: Option, - not_before: Option, - - facts: FactsMap, - proofs: Vec, - add_nonce: bool, -} - -impl<'a, K> Default for UcanBuilder<'a, K> -where - K: KeyMaterial, -{ - /// Create an empty builder. - /// Before finalising the builder, you need to at least call: - /// - /// - `issued_by` - /// - `to_audience` and one of - /// - `with_lifetime` or `with_expiration`. - /// - /// To finalise the builder, call its `build` or `build_parts` method. - fn default() -> Self { - UcanBuilder { - issuer: None, - audience: None, - - capabilities: Vec::new(), - - lifetime: None, - expiration: None, - not_before: None, - - facts: BTreeMap::new(), - proofs: Vec::new(), - add_nonce: false, - } - } -} - -impl<'a, K> UcanBuilder<'a, K> -where - K: KeyMaterial, -{ - /// The UCAN must be signed with the private key of the issuer to be valid. - pub fn issued_by(mut self, issuer: &'a K) -> Self { - self.issuer = Some(issuer); - self - } - - /// This is the identity this UCAN transfers rights to. - /// - /// It could e.g. be the DID of a service you're posting this UCAN as a JWT to, - /// or it could be the DID of something that'll use this UCAN as a proof to - /// continue the UCAN chain as an issuer. - pub fn for_audience(mut self, audience: &str) -> Self { - self.audience = Some(String::from(audience)); - self - } - - /// The number of seconds into the future (relative to when build() is - /// invoked) to set the expiration. This is ignored if an explicit expiration - /// is set. - pub fn with_lifetime(mut self, seconds: u64) -> Self { - self.lifetime = Some(seconds); - self - } - - /// Set the POSIX timestamp (in seconds) for when the UCAN should expire. - /// Setting this value overrides a configured lifetime value. - pub fn with_expiration(mut self, timestamp: u64) -> Self { - self.expiration = Some(timestamp); - self - } - - /// Set the POSIX timestamp (in seconds) of when the UCAN becomes active. - pub fn not_before(mut self, timestamp: u64) -> Self { - self.not_before = Some(timestamp); - self - } - - /// Add a fact or proof of knowledge to this UCAN. - pub fn with_fact(mut self, key: &str, fact: T) -> Self { - match serde_json::to_value(fact) { - Ok(value) => { - self.facts.insert(key.to_owned(), value); - } - Err(error) => warn!("Could not add fact to UCAN: {}", error), - } - self - } - - /// Will ensure that the built UCAN includes a number used once. - pub fn with_nonce(mut self) -> Self { - self.add_nonce = true; - self - } - - /// Includes a UCAN in the list of proofs for the UCAN to be built. - /// Note that the proof's audience must match this UCAN's issuer - /// or else the proof chain will be invalidated! - /// The proof is encoded into a [Cid], hashed via the [UcanBuilder::default_hasher()] - /// algorithm, unless one is provided. - pub fn witnessed_by(mut self, authority: &Ucan, hasher: Option) -> Self { - match authority.to_cid(hasher.unwrap_or_else(|| UcanBuilder::::default_hasher())) { - Ok(proof) => self.proofs.push(proof.to_string()), - Err(error) => warn!("Failed to add authority to proofs: {}", error), - } - - self - } - - /// Claim a capability by inheritance (from an authorizing proof) or - /// implicitly by ownership of the resource by this UCAN's issuer - pub fn claiming_capability(mut self, capability: C) -> Self - where - C: Into, - { - self.capabilities.push(capability.into()); - self - } - - /// Claim capabilities by inheritance (from an authorizing proof) or - /// implicitly by ownership of the resource by this UCAN's issuer - pub fn claiming_capabilities(mut self, capabilities: &[C]) -> Self - where - C: Into + Clone, - { - let caps: Vec = capabilities - .iter() - .map(|c| >::into(c.to_owned())) - .collect(); - self.capabilities.extend(caps); - self - } - - /// Delegate all capabilities from a given proof to the audience of the UCAN - /// you're building. - /// The proof is encoded into a [Cid], hashed via the [UcanBuilder::default_hasher()] - /// algorithm, unless one is provided. - pub fn delegating_from(mut self, authority: &Ucan, hasher: Option) -> Self { - match authority.to_cid(hasher.unwrap_or_else(|| UcanBuilder::::default_hasher())) { - Ok(proof) => { - self.proofs.push(proof.to_string()); - let proof_index = self.proofs.len() - 1; - let proof_delegation = ProofDelegationSemantics {}; - let capability = - proof_delegation.parse(&format!("prf:{proof_index}"), "ucan/DELEGATE", None); - - match capability { - Some(capability) => { - self.capabilities.push(Capability::from(&capability)); - } - None => warn!("Could not produce delegation capability"), - } - } - Err(error) => warn!("Could not encode authoritative UCAN: {:?}", error), - }; - - self - } - - /// Returns the default hasher ([Code::Blake3_256]) used for [Cid] encodings. - pub fn default_hasher() -> Code { - Code::Blake3_256 - } - - fn implied_expiration(&self) -> Option { - if self.expiration.is_some() { - self.expiration - } else { - self.lifetime.map(|lifetime| now() + lifetime) - } - } - - pub fn build(self) -> Result> { - match &self.issuer { - Some(issuer) => match &self.audience { - Some(audience) => Ok(Signable { - issuer, - audience: audience.clone(), - not_before: self.not_before, - expiration: self.implied_expiration(), - facts: self.facts.clone(), - capabilities: self.capabilities.clone(), - proofs: self.proofs.clone(), - add_nonce: self.add_nonce, - }), - None => Err(anyhow!("Missing audience")), - }, - None => Err(anyhow!("Missing issuer")), - } - } -} diff --git a/ucan/src/capability/caveats.rs b/ucan/src/capability/caveats.rs deleted file mode 100644 index e7d508a3..00000000 --- a/ucan/src/capability/caveats.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::ops::Deref; - -use anyhow::{anyhow, Error, Result}; -use serde_json::{Map, Value}; - -#[derive(Clone)] -pub struct Caveat(Map); - -impl Caveat { - /// Determines if this [Caveat] enables/allows the provided caveat. - /// - /// ``` - /// use ucan::capability::{Caveat}; - /// use serde_json::json; - /// - /// let no_caveat = Caveat::try_from(json!({})).unwrap(); - /// let x_caveat = Caveat::try_from(json!({ "x": true })).unwrap(); - /// let x_diff_caveat = Caveat::try_from(json!({ "x": false })).unwrap(); - /// let y_caveat = Caveat::try_from(json!({ "y": true })).unwrap(); - /// let xz_caveat = Caveat::try_from(json!({ "x": true, "z": true })).unwrap(); - /// - /// assert!(no_caveat.enables(&no_caveat)); - /// assert!(x_caveat.enables(&x_caveat)); - /// assert!(no_caveat.enables(&x_caveat)); - /// assert!(x_caveat.enables(&xz_caveat)); - /// - /// assert!(!x_caveat.enables(&x_diff_caveat)); - /// assert!(!x_caveat.enables(&no_caveat)); - /// assert!(!x_caveat.enables(&y_caveat)); - /// ``` - pub fn enables(&self, other: &Caveat) -> bool { - if self.is_empty() { - return true; - } - - if other.is_empty() { - return false; - } - - if self == other { - return true; - } - - for (key, value) in self.iter() { - if let Some(other_value) = other.get(key) { - if value != other_value { - return false; - } - } else { - return false; - } - } - - true - } -} - -impl Deref for Caveat { - type Target = Map; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq for Caveat { - fn eq(&self, other: &Caveat) -> bool { - self.0 == other.0 - } -} - -impl TryFrom for Caveat { - type Error = Error; - fn try_from(value: Value) -> Result { - Ok(Caveat(match value { - Value::Object(obj) => obj, - _ => return Err(anyhow!("Caveat must be an object")), - })) - } -} - -impl TryFrom<&Value> for Caveat { - type Error = Error; - fn try_from(value: &Value) -> Result { - Caveat::try_from(value.to_owned()) - } -} diff --git a/ucan/src/capability/data.rs b/ucan/src/capability/data.rs deleted file mode 100644 index 307fe718..00000000 --- a/ucan/src/capability/data.rs +++ /dev/null @@ -1,229 +0,0 @@ -use anyhow::anyhow; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::{ - collections::{btree_map::Iter as BTreeMapIter, BTreeMap}, - fmt::Debug, - iter::FlatMap, - ops::Deref, -}; - -#[derive(Debug, Clone, PartialEq, Eq)] -/// Represents a single, flattened capability containing a resource, ability, and caveat. -pub struct Capability { - pub resource: String, - pub ability: String, - pub caveat: Value, -} - -impl Capability { - pub fn new(resource: String, ability: String, caveat: Value) -> Self { - Capability { - resource, - ability, - caveat, - } - } -} - -impl From<&Capability> for Capability { - fn from(value: &Capability) -> Self { - value.to_owned() - } -} - -impl From<(String, String, Value)> for Capability { - fn from(value: (String, String, Value)) -> Self { - Capability::new(value.0, value.1, value.2) - } -} - -impl From<(&str, &str, &Value)> for Capability { - fn from(value: (&str, &str, &Value)) -> Self { - Capability::new(value.0.to_owned(), value.1.to_owned(), value.2.to_owned()) - } -} - -impl From for (String, String, Value) { - fn from(value: Capability) -> Self { - (value.resource, value.ability, value.caveat) - } -} - -type MapImpl = BTreeMap; -type MapIter<'a, K, V> = BTreeMapIter<'a, K, V>; -type AbilitiesImpl = MapImpl>; -type CapabilitiesImpl = MapImpl; -type AbilitiesMapClosure<'a> = Box)) -> Vec + 'a>; -type AbilitiesMap<'a> = - FlatMap>, Vec, AbilitiesMapClosure<'a>>; -type CapabilitiesIterator<'a> = FlatMap< - MapIter<'a, String, AbilitiesImpl>, - AbilitiesMap<'a>, - fn((&'a String, &'a AbilitiesImpl)) -> AbilitiesMap<'a>, ->; - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -/// The [Capabilities] struct contains capability data as a map-of-maps, matching the -/// [spec](https://github.com/ucan-wg/spec#326-capabilities--attenuation). -/// See `iter()` to deconstruct this map into a sequence of [Capability] datas. -/// -/// ``` -/// use ucan::capability::Capabilities; -/// use serde_json::json; -/// -/// let capabilities = Capabilities::try_from(&json!({ -/// "mailto:username@example.com": { -/// "msg/receive": [{}], -/// "msg/send": [{ "draft": true }, { "publish": true, "topic": ["foo"]}] -/// } -/// })).unwrap(); -/// -/// let resource = capabilities.get("mailto:username@example.com").unwrap(); -/// assert_eq!(resource.get("msg/receive").unwrap(), &vec![json!({})]); -/// assert_eq!(resource.get("msg/send").unwrap(), &vec![json!({ "draft": true }), json!({ "publish": true, "topic": ["foo"] })]) -/// ``` -pub struct Capabilities(CapabilitiesImpl); - -impl Capabilities { - /// Using a [FlatMap] implementation, iterate over a [Capabilities] map-of-map - /// as a sequence of [Capability] datas. - /// - /// ``` - /// use ucan::capability::{Capabilities, Capability}; - /// use serde_json::json; - /// - /// let capabilities = Capabilities::try_from(&json!({ - /// "example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr": { - /// "wnfs/append": [{}] - /// }, - /// "mailto:username@example.com": { - /// "msg/receive": [{}], - /// "msg/send": [{ "draft": true }, { "publish": true, "topic": ["foo"]}] - /// } - /// })).unwrap(); - /// - /// assert_eq!(capabilities.iter().collect::>(), vec![ - /// Capability::from(("example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr", "wnfs/append", &json!({}))), - /// Capability::from(("mailto:username@example.com", "msg/receive", &json!({}))), - /// Capability::from(("mailto:username@example.com", "msg/send", &json!({ "draft": true }))), - /// Capability::from(("mailto:username@example.com", "msg/send", &json!({ "publish": true, "topic": ["foo"] }))), - /// ]); - /// ``` - pub fn iter(&self) -> CapabilitiesIterator { - self.0 - .iter() - .flat_map(|(resource, abilities): (&String, &AbilitiesImpl)| { - abilities - .iter() - .flat_map(Box::new( - |(ability, caveats): (&String, &Vec)| match caveats.len() { - 0 => vec![], // An empty caveats list is the same as no capability at all - _ => caveats - .iter() - .map(|caveat| { - Capability::from(( - resource.to_owned(), - ability.to_owned(), - caveat.to_owned(), - )) - }) - .collect(), - }, - )) - }) - } -} - -impl Deref for Capabilities { - type Target = CapabilitiesImpl; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl TryFrom> for Capabilities { - type Error = anyhow::Error; - fn try_from(value: Vec) -> Result { - let mut resources: CapabilitiesImpl = BTreeMap::new(); - for capability in value.into_iter() { - let (resource_name, ability, caveat) = <(String, String, Value)>::from(capability); - - let resource = if let Some(resource) = resources.get_mut(&resource_name) { - resource - } else { - let resource: AbilitiesImpl = BTreeMap::new(); - resources.insert(resource_name.clone(), resource); - resources.get_mut(&resource_name).unwrap() - }; - - if !caveat.is_object() { - return Err(anyhow!("Caveat must be an object: {}", caveat)); - } - - if let Some(ability_vec) = resource.get_mut(&ability) { - ability_vec.push(caveat); - } else { - resource.insert(ability, vec![caveat]); - } - } - Capabilities::try_from(resources) - } -} - -impl TryFrom for Capabilities { - type Error = anyhow::Error; - - fn try_from(value: CapabilitiesImpl) -> Result { - for (resource, abilities) in value.iter() { - if abilities.is_empty() { - // [0.10.0/3.2.6.2](https://github.com/ucan-wg/spec#3262-abilities): - // One or more abilities MUST be given for each resource. - return Err(anyhow!("No abilities given for resource: {}", resource)); - } - } - Ok(Capabilities(value)) - } -} - -impl TryFrom<&Value> for Capabilities { - type Error = anyhow::Error; - - fn try_from(value: &Value) -> Result { - let map = value - .as_object() - .ok_or_else(|| anyhow!("Capabilities must be an object."))?; - let mut resources: CapabilitiesImpl = BTreeMap::new(); - - for (key, value) in map.iter() { - let resource = key.to_owned(); - let abilities_object = value - .as_object() - .ok_or_else(|| anyhow!("Abilities must be an object."))?; - - let abilities = { - let mut abilities: AbilitiesImpl = BTreeMap::new(); - for (key, value) in abilities_object.iter() { - let ability = key.to_owned(); - let mut caveats: Vec = vec![]; - - let array = value - .as_array() - .ok_or_else(|| anyhow!("Caveats must be defined as an array."))?; - for value in array.iter() { - if !value.is_object() { - return Err(anyhow!("Caveat must be an object: {}", value)); - } - caveats.push(value.to_owned()); - } - abilities.insert(ability, caveats); - } - abilities - }; - - resources.insert(resource, abilities); - } - - Capabilities::try_from(resources) - } -} diff --git a/ucan/src/capability/mod.rs b/ucan/src/capability/mod.rs deleted file mode 100644 index cde4fe33..00000000 --- a/ucan/src/capability/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod proof; - -mod caveats; -mod data; -mod semantics; - -pub use caveats::*; -pub use data::*; -pub use semantics::*; diff --git a/ucan/src/capability/proof.rs b/ucan/src/capability/proof.rs deleted file mode 100644 index 986bb578..00000000 --- a/ucan/src/capability/proof.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::{Ability, CapabilitySemantics, Scope}; -use anyhow::{anyhow, Result}; -use url::Url; - -#[derive(Ord, Eq, PartialEq, PartialOrd, Clone)] -pub enum ProofAction { - Delegate, -} - -impl Ability for ProofAction {} - -impl TryFrom for ProofAction { - type Error = anyhow::Error; - - fn try_from(value: String) -> Result { - match value.as_str() { - "ucan/DELEGATE" => Ok(ProofAction::Delegate), - unsupported => Err(anyhow!( - "Unsupported action for proof resource ({})", - unsupported - )), - } - } -} - -impl ToString for ProofAction { - fn to_string(&self) -> String { - match self { - ProofAction::Delegate => "ucan/DELEGATE".into(), - } - } -} - -#[derive(Eq, PartialEq, Clone)] -pub enum ProofSelection { - Index(usize), - All, -} - -impl Scope for ProofSelection { - fn contains(&self, other: &Self) -> bool { - self == other || *self == ProofSelection::All - } -} - -impl TryFrom for ProofSelection { - type Error = anyhow::Error; - - fn try_from(value: Url) -> Result { - match value.scheme() { - "prf" => String::from(value.path()).try_into(), - _ => Err(anyhow!("Unrecognized URI scheme")), - } - } -} - -impl TryFrom for ProofSelection { - type Error = anyhow::Error; - - fn try_from(value: String) -> Result { - match value.as_str() { - "*" => Ok(ProofSelection::All), - selection => Ok(ProofSelection::Index(selection.parse::()?)), - } - } -} - -impl ToString for ProofSelection { - fn to_string(&self) -> String { - match self { - ProofSelection::Index(usize) => format!("prf:{usize}"), - ProofSelection::All => "prf:*".to_string(), - } - } -} - -pub struct ProofDelegationSemantics {} - -impl CapabilitySemantics for ProofDelegationSemantics {} diff --git a/ucan/src/capability/semantics.rs b/ucan/src/capability/semantics.rs deleted file mode 100644 index 65966fb4..00000000 --- a/ucan/src/capability/semantics.rs +++ /dev/null @@ -1,301 +0,0 @@ -use super::{Capability, Caveat}; -use serde_json::{json, Value}; -use std::fmt::Debug; -use url::Url; - -pub trait Scope: ToString + TryFrom + PartialEq + Clone { - fn contains(&self, other: &Self) -> bool; -} - -pub trait Ability: Ord + TryFrom + ToString + Clone {} - -#[derive(Clone, Eq, PartialEq)] -pub enum ResourceUri -where - S: Scope, -{ - Scoped(S), - Unscoped, -} - -impl ResourceUri -where - S: Scope, -{ - pub fn contains(&self, other: &Self) -> bool { - match self { - ResourceUri::Unscoped => true, - ResourceUri::Scoped(scope) => match other { - ResourceUri::Scoped(other_scope) => scope.contains(other_scope), - _ => false, - }, - } - } -} - -impl ToString for ResourceUri -where - S: Scope, -{ - fn to_string(&self) -> String { - match self { - ResourceUri::Unscoped => "*".into(), - ResourceUri::Scoped(value) => value.to_string(), - } - } -} - -#[derive(Clone, Eq, PartialEq)] -pub enum Resource -where - S: Scope, -{ - Resource { kind: ResourceUri }, - My { kind: ResourceUri }, - As { did: String, kind: ResourceUri }, -} - -impl Resource -where - S: Scope, -{ - pub fn contains(&self, other: &Self) -> bool { - match (self, other) { - ( - Resource::Resource { kind: resource }, - Resource::Resource { - kind: other_resource, - }, - ) => resource.contains(other_resource), - ( - Resource::My { kind: resource }, - Resource::My { - kind: other_resource, - }, - ) => resource.contains(other_resource), - ( - Resource::As { - did, - kind: resource, - }, - Resource::As { - did: other_did, - kind: other_resource, - }, - ) if did == other_did => resource.contains(other_resource), - _ => false, - } - } -} - -impl ToString for Resource -where - S: Scope, -{ - fn to_string(&self) -> String { - match self { - Resource::Resource { kind } => kind.to_string(), - Resource::My { kind } => format!("my:{}", kind.to_string()), - Resource::As { did, kind } => format!("as:{did}:{}", kind.to_string()), - } - } -} - -pub trait CapabilitySemantics -where - S: Scope, - A: Ability, -{ - fn parse_scope(&self, scope: &Url) -> Option { - S::try_from(scope.clone()).ok() - } - fn parse_action(&self, ability: &str) -> Option { - A::try_from(String::from(ability)).ok() - } - - fn extract_did(&self, path: &str) -> Option<(String, String)> { - let mut path_parts = path.split(':'); - - match path_parts.next() { - Some("did") => (), - _ => return None, - }; - - match path_parts.next() { - Some("key") => (), - _ => return None, - }; - - let value = match path_parts.next() { - Some(value) => value, - _ => return None, - }; - - Some((format!("did:key:{value}"), path_parts.collect())) - } - - fn parse_resource(&self, resource: &Url) -> Option> { - Some(match resource.path() { - "*" => ResourceUri::Unscoped, - _ => ResourceUri::Scoped(self.parse_scope(resource)?), - }) - } - - fn parse_caveat(&self, caveat: Option<&Value>) -> Value { - if let Some(caveat) = caveat { - caveat.to_owned() - } else { - json!({}) - } - } - - /// Parse a resource and abilities string and a caveats object. - /// The default "no caveats" (`[{}]`) is implied if `None` caveats given. - fn parse( - &self, - resource: &str, - ability: &str, - caveat: Option<&Value>, - ) -> Option> { - let uri = Url::parse(resource).ok()?; - - let cap_resource = match uri.scheme() { - "my" => Resource::My { - kind: self.parse_resource(&uri)?, - }, - "as" => { - let (did, resource) = self.extract_did(uri.path())?; - Resource::As { - did, - kind: self.parse_resource(&Url::parse(resource.as_str()).ok()?)?, - } - } - _ => Resource::Resource { - kind: self.parse_resource(&uri)?, - }, - }; - - let cap_ability = match self.parse_action(ability) { - Some(ability) => ability, - None => return None, - }; - - let cap_caveat = self.parse_caveat(caveat); - - Some(CapabilityView::new_with_caveat( - cap_resource, - cap_ability, - cap_caveat, - )) - } - - fn parse_capability(&self, value: &Capability) -> Option> { - self.parse(&value.resource, &value.ability, Some(&value.caveat)) - } -} - -#[derive(Clone, Eq, PartialEq)] -pub struct CapabilityView -where - S: Scope, - A: Ability, -{ - pub resource: Resource, - pub ability: A, - pub caveat: Value, -} - -impl Debug for CapabilityView -where - S: Scope, - A: Ability, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Capability") - .field("resource", &self.resource.to_string()) - .field("ability", &self.ability.to_string()) - .field("caveats", &serde_json::to_string(&self.caveat)) - .finish() - } -} - -impl CapabilityView -where - S: Scope, - A: Ability, -{ - /// Creates a new [CapabilityView] semantics view over a capability - /// without caveats. - pub fn new(resource: Resource, ability: A) -> Self { - CapabilityView { - resource, - ability, - caveat: json!({}), - } - } - - /// Creates a new [CapabilityView] semantics view over a capability - /// with caveats. Note that an empty caveats array will imply NO - /// capabilities, rendering this capability meaningless. - pub fn new_with_caveat(resource: Resource, ability: A, caveat: Value) -> Self { - CapabilityView { - resource, - ability, - caveat, - } - } - - pub fn enables(&self, other: &CapabilityView) -> bool { - match ( - Caveat::try_from(self.caveat()), - Caveat::try_from(other.caveat()), - ) { - (Ok(self_caveat), Ok(other_caveat)) => { - self.resource.contains(&other.resource) - && self.ability >= other.ability - && self_caveat.enables(&other_caveat) - } - _ => false, - } - } - - pub fn resource(&self) -> &Resource { - &self.resource - } - - pub fn ability(&self) -> &A { - &self.ability - } - - pub fn caveat(&self) -> &Value { - &self.caveat - } -} - -impl From<&CapabilityView> for Capability -where - S: Scope, - A: Ability, -{ - fn from(value: &CapabilityView) -> Self { - Capability::new( - value.resource.to_string(), - value.ability.to_string(), - value.caveat.to_owned(), - ) - } -} - -impl From> for Capability -where - S: Scope, - A: Ability, -{ - fn from(value: CapabilityView) -> Self { - Capability::new( - value.resource.to_string(), - value.ability.to_string(), - value.caveat, - ) - } -} diff --git a/ucan/src/chain.rs b/ucan/src/chain.rs deleted file mode 100644 index 285efbba..00000000 --- a/ucan/src/chain.rs +++ /dev/null @@ -1,289 +0,0 @@ -use crate::{ - capability::{ - proof::{ProofDelegationSemantics, ProofSelection}, - Ability, CapabilitySemantics, CapabilityView, Resource, ResourceUri, Scope, - }, - crypto::did::DidParser, - store::UcanJwtStore, - ucan::Ucan, -}; -use anyhow::{anyhow, Result}; -use async_recursion::async_recursion; -use cid::Cid; -use std::{collections::BTreeSet, fmt::Debug}; - -const PROOF_DELEGATION_SEMANTICS: ProofDelegationSemantics = ProofDelegationSemantics {}; - -#[derive(Eq, PartialEq)] -pub struct CapabilityInfo { - pub originators: BTreeSet, - pub not_before: Option, - pub expires_at: Option, - pub capability: CapabilityView, -} - -impl Debug for CapabilityInfo -where - S: Scope, - A: Ability, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CapabilityInfo") - .field("originators", &self.originators) - .field("not_before", &self.not_before) - .field("expires_at", &self.expires_at) - .field("capability", &self.capability) - .finish() - } -} - -/// A deserialized chain of ancestral proofs that are linked to a UCAN -#[derive(Debug)] -pub struct ProofChain { - ucan: Ucan, - proofs: Vec, - redelegations: BTreeSet, -} - -impl ProofChain { - /// Instantiate a [ProofChain] from a [Ucan], given a [UcanJwtStore] and [DidParser] - #[cfg_attr(target_arch = "wasm32", async_recursion(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_recursion)] - pub async fn from_ucan( - ucan: Ucan, - now_time: Option, - did_parser: &mut DidParser, - store: &S, - ) -> Result - where - S: UcanJwtStore, - { - ucan.validate(now_time, did_parser).await?; - - let mut proofs: Vec = Vec::new(); - - if let Some(ucan_proofs) = ucan.proofs() { - for cid_string in ucan_proofs.iter() { - let cid = Cid::try_from(cid_string.as_str())?; - let ucan_token = store.require_token(&cid).await?; - let proof_chain = - Self::try_from_token_string(&ucan_token, now_time, did_parser, store).await?; - proof_chain.validate_link_to(&ucan)?; - proofs.push(proof_chain); - } - } - - let mut redelegations = BTreeSet::::new(); - - for capability in ucan - .capabilities() - .iter() - .filter_map(|cap| PROOF_DELEGATION_SEMANTICS.parse_capability(&cap)) - { - match capability.resource() { - Resource::Resource { - kind: ResourceUri::Scoped(ProofSelection::All), - } => { - for index in 0..proofs.len() { - redelegations.insert(index); - } - } - Resource::Resource { - kind: ResourceUri::Scoped(ProofSelection::Index(index)), - } => { - if *index < proofs.len() { - redelegations.insert(*index); - } else { - return Err(anyhow!( - "Unable to redelegate proof; no proof at zero-based index {}", - index - )); - } - } - _ => continue, - } - } - - Ok(ProofChain { - ucan, - proofs, - redelegations, - }) - } - - /// Instantiate a [ProofChain] from a [Cid], given a [UcanJwtStore] and [DidParser] - /// The [Cid] must resolve to a JWT token string - pub async fn from_cid( - cid: &Cid, - now_time: Option, - did_parser: &mut DidParser, - store: &S, - ) -> Result - where - S: UcanJwtStore, - { - Self::try_from_token_string( - &store.require_token(cid).await?, - now_time, - did_parser, - store, - ) - .await - } - - /// Instantiate a [ProofChain] from a JWT token string, given a [UcanJwtStore] and [DidParser] - pub async fn try_from_token_string<'a, S>( - ucan_token_string: &str, - now_time: Option, - did_parser: &mut DidParser, - store: &S, - ) -> Result - where - S: UcanJwtStore, - { - let ucan = Ucan::try_from(ucan_token_string)?; - Self::from_ucan(ucan, now_time, did_parser, store).await - } - - fn validate_link_to(&self, ucan: &Ucan) -> Result<()> { - let audience = self.ucan.audience(); - let issuer = ucan.issuer(); - - match audience == issuer { - true => match self.ucan.lifetime_encompasses(ucan) { - true => Ok(()), - false => Err(anyhow!("Invalid UCAN link: lifetime exceeds attenuation")), - }, - false => Err(anyhow!( - "Invalid UCAN link: audience {} does not match issuer {}", - audience, - issuer - )), - } - } - - pub fn ucan(&self) -> &Ucan { - &self.ucan - } - - pub fn proofs(&self) -> &Vec { - &self.proofs - } - - pub fn reduce_capabilities( - &self, - semantics: &Semantics, - ) -> Vec> - where - Semantics: CapabilitySemantics, - S: Scope, - A: Ability, - { - // Get the set of inherited attenuations (excluding redelegations) - // before further attenuating by own lifetime and capabilities: - let ancestral_capability_infos: Vec> = self - .proofs - .iter() - .enumerate() - .flat_map(|(index, ancestor_chain)| { - if self.redelegations.contains(&index) { - Vec::new() - } else { - ancestor_chain.reduce_capabilities(semantics) - } - }) - .collect(); - - // Get the set of capabilities that are blanket redelegated from - // ancestor proofs (via the prf: resource): - let mut redelegated_capability_infos: Vec> = self - .redelegations - .iter() - .flat_map(|index| { - self.proofs - .get(*index) - .unwrap() - .reduce_capabilities(semantics) - .into_iter() - .map(|mut info| { - // Redelegated capabilities should be attenuated by - // this UCAN's lifetime - info.not_before = *self.ucan.not_before(); - info.expires_at = *self.ucan.expires_at(); - info - }) - }) - .collect(); - - let self_capabilities_iter = self - .ucan - .capabilities() - .iter() - .map_while(|data| semantics.parse_capability(&data)); - - // Get the claimed attenuations of this ucan, cross-checking ancestral - // attenuations to discover the originating authority - let mut self_capability_infos: Vec> = match self.proofs.len() { - 0 => self_capabilities_iter - .map(|capability| CapabilityInfo { - originators: BTreeSet::from_iter(vec![self.ucan.issuer().to_string()]), - capability, - not_before: *self.ucan.not_before(), - expires_at: *self.ucan.expires_at(), - }) - .collect(), - _ => self_capabilities_iter - .map(|capability| { - let mut originators = BTreeSet::::new(); - - for ancestral_capability_info in ancestral_capability_infos.iter() { - match ancestral_capability_info.capability.enables(&capability) { - true => { - originators.extend(ancestral_capability_info.originators.clone()) - } - // true => return Some(capability), - false => continue, - } - } - - // If there are no related ancestral capability, then this - // link in the chain is considered the first originator - if originators.is_empty() { - originators.insert(self.ucan.issuer().to_string()); - } - - CapabilityInfo { - capability, - originators, - not_before: *self.ucan.not_before(), - expires_at: *self.ucan.expires_at(), - } - }) - .collect(), - }; - - self_capability_infos.append(&mut redelegated_capability_infos); - - let mut merged_capability_infos = Vec::>::new(); - - // Merge redundant capabilities (accounting for redelegation), ensuring - // that discrete originators are aggregated as we go - 'merge: while let Some(capability_info) = self_capability_infos.pop() { - for remaining_capability_info in &mut self_capability_infos { - if remaining_capability_info - .capability - .enables(&capability_info.capability) - { - remaining_capability_info - .originators - .extend(capability_info.originators); - continue 'merge; - } - } - - merged_capability_infos.push(capability_info); - } - - merged_capability_infos - } -} diff --git a/ucan/src/crypto/did.rs b/ucan/src/crypto/did.rs deleted file mode 100644 index 3322f2c9..00000000 --- a/ucan/src/crypto/did.rs +++ /dev/null @@ -1,67 +0,0 @@ -use super::KeyMaterial; -use anyhow::{anyhow, Result}; -use std::{collections::BTreeMap, sync::Arc}; - -pub type DidPrefix = &'static [u8]; -pub type BytesToKey = fn(Vec) -> Result>; -pub type KeyConstructors = BTreeMap; -pub type KeyConstructorSlice = [(DidPrefix, BytesToKey)]; -pub type KeyCache = BTreeMap>>; - -pub const DID_PREFIX: &str = "did:"; -pub const DID_KEY_PREFIX: &str = "did:key:z"; - -pub const ED25519_MAGIC_BYTES: &[u8] = &[0xed, 0x01]; -pub const RSA_MAGIC_BYTES: &[u8] = &[0x85, 0x24]; -pub const BLS12381G1_MAGIC_BYTES: &[u8] = &[0xea, 0x01]; -pub const BLS12381G2_MAGIC_BYTES: &[u8] = &[0xeb, 0x01]; -pub const P256_MAGIC_BYTES: &[u8] = &[0x80, 0x24]; -pub const SECP256K1_MAGIC_BYTES: &[u8] = &[0xe7, 0x1]; - -/// A parser that is able to convert from a DID string into a corresponding -/// [`KeyMaterial`] implementation. The parser extracts the signature -/// magic bytes from a given DID and tries to match them to a corresponding -/// constructor function that produces a `SigningKey`. -pub struct DidParser { - key_constructors: KeyConstructors, - key_cache: KeyCache, -} - -impl DidParser { - pub fn new(key_constructor_slice: &KeyConstructorSlice) -> Self { - let mut key_constructors = BTreeMap::new(); - for pair in key_constructor_slice { - key_constructors.insert(pair.0, pair.1); - } - DidParser { - key_constructors, - key_cache: BTreeMap::new(), - } - } - - pub fn parse(&mut self, did: &str) -> Result>> { - if !did.starts_with(DID_KEY_PREFIX) { - return Err(anyhow!("Expected valid did:key, got: {}", did)); - } - - let did = did.to_owned(); - if let Some(key) = self.key_cache.get(&did) { - return Ok(key.clone()); - } - - let did_bytes = bs58::decode(&did[DID_KEY_PREFIX.len()..]).into_vec()?; - let magic_bytes = &did_bytes[0..2]; - match self.key_constructors.get(magic_bytes) { - Some(ctor) => { - let key = ctor(Vec::from(&did_bytes[2..]))?; - self.key_cache.insert(did.clone(), Arc::new(key)); - - self.key_cache - .get(&did) - .ok_or_else(|| anyhow!("Couldn't find cached key")) - .map(|key| key.clone()) - } - None => Err(anyhow!("Unrecognized magic bytes: {:?}", magic_bytes)), - } - } -} diff --git a/ucan/src/crypto/key.rs b/ucan/src/crypto/key.rs deleted file mode 100644 index 022176ac..00000000 --- a/ucan/src/crypto/key.rs +++ /dev/null @@ -1,79 +0,0 @@ -use anyhow::Result; -use async_trait::async_trait; -use std::sync::Arc; - -#[cfg(not(target_arch = "wasm32"))] -pub trait KeyMaterialConditionalSendSync: Send + Sync {} - -#[cfg(not(target_arch = "wasm32"))] -impl KeyMaterialConditionalSendSync for K where K: KeyMaterial + Send + Sync {} - -#[cfg(target_arch = "wasm32")] -pub trait KeyMaterialConditionalSendSync {} - -#[cfg(target_arch = "wasm32")] -impl KeyMaterialConditionalSendSync for K where K: KeyMaterial {} - -/// This trait must be implemented by a struct that encapsulates cryptographic -/// keypair data. The trait represent the minimum required API capability for -/// producing a signed UCAN from a cryptographic keypair, and verifying such -/// signatures. -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait KeyMaterial: KeyMaterialConditionalSendSync { - /// The algorithm that will be used to produce the signature returned by the - /// sign method in this implementation - fn get_jwt_algorithm_name(&self) -> String; - - /// Provides a valid DID that can be used to solve the key - async fn get_did(&self) -> Result; - - /// Sign some data with this key - async fn sign(&self, payload: &[u8]) -> Result>; - - /// Verify the alleged signature of some data against this key - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()>; -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl KeyMaterial for Box { - fn get_jwt_algorithm_name(&self) -> String { - self.as_ref().get_jwt_algorithm_name() - } - - async fn get_did(&self) -> Result { - self.as_ref().get_did().await - } - - async fn sign(&self, payload: &[u8]) -> Result> { - self.as_ref().sign(payload).await - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - self.as_ref().verify(payload, signature).await - } -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl KeyMaterial for Arc -where - K: KeyMaterial, -{ - fn get_jwt_algorithm_name(&self) -> String { - (**self).get_jwt_algorithm_name() - } - - async fn get_did(&self) -> Result { - (**self).get_did().await - } - - async fn sign(&self, payload: &[u8]) -> Result> { - (**self).sign(payload).await - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - (**self).verify(payload, signature).await - } -} diff --git a/ucan/src/crypto/mod.rs b/ucan/src/crypto/mod.rs deleted file mode 100644 index 75ac72f0..00000000 --- a/ucan/src/crypto/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod did; -mod key; -mod signature; - -pub use key::*; -pub use signature::*; diff --git a/ucan/src/crypto/signature.rs b/ucan/src/crypto/signature.rs deleted file mode 100644 index bc319d66..00000000 --- a/ucan/src/crypto/signature.rs +++ /dev/null @@ -1,12 +0,0 @@ -use strum_macros::{Display, EnumString}; - -// See: https://www.rfc-editor.org/rfc/rfc7518 -// See: https://www.rfc-editor.org/rfc/rfc8037.html#appendix-A.4 -#[derive(Debug, Display, EnumString, Eq, PartialEq)] -pub enum JwtSignatureAlgorithm { - EdDSA, - RS256, - ES256, - ES384, - ES512, -} diff --git a/ucan/src/ipld/mod.rs b/ucan/src/ipld/mod.rs deleted file mode 100644 index d055d81f..00000000 --- a/ucan/src/ipld/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod principle; -mod signature; -mod ucan; - -pub use self::ucan::*; -pub use principle::*; -pub use signature::*; diff --git a/ucan/src/ipld/principle.rs b/ucan/src/ipld/principle.rs deleted file mode 100644 index 9980f40f..00000000 --- a/ucan/src/ipld/principle.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::crypto::did::{DID_KEY_PREFIX, DID_PREFIX}; -use anyhow::anyhow; -use serde::{Deserialize, Serialize}; -use std::{fmt::Display, str::FromStr}; - -// Note: varint encoding of 0x0d1d -pub const DID_IPLD_PREFIX: &[u8] = &[0x9d, 0x1a]; - -#[repr(transparent)] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Principle(Vec); - -impl FromStr for Principle { - type Err = anyhow::Error; - - fn from_str(input: &str) -> Result { - if let Some(stripped) = input.strip_prefix(DID_KEY_PREFIX) { - Ok(Principle(bs58::decode(stripped).into_vec()?)) - } else if let Some(stripped) = input.strip_prefix(DID_PREFIX) { - Ok(Principle([DID_IPLD_PREFIX, stripped.as_bytes()].concat())) - } else { - Err(anyhow!("This is not a DID: {}", input)) - } - } -} - -impl Display for Principle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let bytes = &self.0; - let did_content = match &bytes[0..2] { - DID_IPLD_PREFIX => [ - DID_PREFIX, - std::str::from_utf8(&bytes[2..]).map_err(|_| std::fmt::Error)?, - ] - .concat(), - _ => [DID_KEY_PREFIX, &bs58::encode(bytes).into_string()].concat(), - }; - - write!(f, "{did_content}") - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use crate::{ipld::Principle, tests::helpers::dag_cbor_roundtrip}; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn it_round_trips_a_principle_did() { - let did_string = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"; - let principle = dag_cbor_roundtrip(&Principle::from_str(&did_string).unwrap()).unwrap(); - assert_eq!(did_string, principle.to_string()); - - let did_string = "did:web:example.com"; - let principle = dag_cbor_roundtrip(&Principle::from_str(&did_string).unwrap()).unwrap(); - assert_eq!(did_string, principle.to_string()); - } -} diff --git a/ucan/src/ipld/signature.rs b/ucan/src/ipld/signature.rs deleted file mode 100644 index ed520cbd..00000000 --- a/ucan/src/ipld/signature.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::crypto::JwtSignatureAlgorithm; -use anyhow::{anyhow, Result}; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, str::FromStr}; - -// See -// See -const NONSTANDARD_VARSIG_PREFIX: u64 = 0xd000; -const ES256K_VARSIG_PREFIX: u64 = 0xd0e7; -const BLS12381G1_VARSIG_PREFIX: u64 = 0xd0ea; -const BLS12381G2_VARSIG_PREFIX: u64 = 0xd0eb; -const EDDSA_VARSIG_PREFIX: u64 = 0xd0ed; -const ES256_VARSIG_PREFIX: u64 = 0xd01200; -const ES384_VARSIG_PREFIX: u64 = 0xd01201; -const ES512_VARSIG_PREFIX: u64 = 0xd01202; -const RS256_VARSIG_PREFIX: u64 = 0xd01205; -const EIP191_VARSIG_PREFIX: u64 = 0xd191; - -/// A helper for transforming signatures used in JWTs to their UCAN-IPLD -/// counterpart representation and vice-versa -/// Note, not all valid JWT signature algorithms are represented by this -/// library, nor are all valid varsig prefixes -/// See -#[derive(Debug, Eq, PartialEq)] -pub enum VarsigPrefix { - NonStandard, - ES256K, - BLS12381G1, - BLS12381G2, - EdDSA, - ES256, - ES384, - ES512, - RS256, - EIP191, -} - -impl TryFrom for VarsigPrefix { - type Error = anyhow::Error; - - fn try_from(value: JwtSignatureAlgorithm) -> Result { - Ok(match value { - JwtSignatureAlgorithm::EdDSA => VarsigPrefix::EdDSA, - JwtSignatureAlgorithm::RS256 => VarsigPrefix::RS256, - JwtSignatureAlgorithm::ES256 => VarsigPrefix::ES256, - JwtSignatureAlgorithm::ES384 => VarsigPrefix::ES384, - JwtSignatureAlgorithm::ES512 => VarsigPrefix::ES512, - }) - } -} - -impl TryFrom for JwtSignatureAlgorithm { - type Error = anyhow::Error; - - fn try_from(value: VarsigPrefix) -> Result { - Ok(match value { - VarsigPrefix::EdDSA => JwtSignatureAlgorithm::EdDSA, - VarsigPrefix::RS256 => JwtSignatureAlgorithm::RS256, - VarsigPrefix::ES256 => JwtSignatureAlgorithm::ES256, - VarsigPrefix::ES384 => JwtSignatureAlgorithm::ES384, - VarsigPrefix::ES512 => JwtSignatureAlgorithm::ES512, - _ => { - return Err(anyhow!( - "JWT signature algorithm name for {:?} is not known", - value - )) - } - }) - } -} - -impl FromStr for VarsigPrefix { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - VarsigPrefix::try_from(JwtSignatureAlgorithm::from_str(s)?) - } -} - -impl From for u64 { - fn from(value: VarsigPrefix) -> Self { - match value { - VarsigPrefix::NonStandard { .. } => NONSTANDARD_VARSIG_PREFIX, - VarsigPrefix::ES256K => ES256K_VARSIG_PREFIX, - VarsigPrefix::BLS12381G1 => BLS12381G1_VARSIG_PREFIX, - VarsigPrefix::BLS12381G2 => BLS12381G2_VARSIG_PREFIX, - VarsigPrefix::EdDSA => EDDSA_VARSIG_PREFIX, - VarsigPrefix::ES256 => ES256_VARSIG_PREFIX, - VarsigPrefix::ES384 => ES384_VARSIG_PREFIX, - VarsigPrefix::ES512 => ES512_VARSIG_PREFIX, - VarsigPrefix::RS256 => RS256_VARSIG_PREFIX, - VarsigPrefix::EIP191 => EIP191_VARSIG_PREFIX, - } - } -} - -impl TryFrom for VarsigPrefix { - type Error = anyhow::Error; - - fn try_from(value: u64) -> Result { - Ok(match value { - EDDSA_VARSIG_PREFIX => VarsigPrefix::EdDSA, - RS256_VARSIG_PREFIX => VarsigPrefix::RS256, - ES256K_VARSIG_PREFIX => VarsigPrefix::ES256K, - BLS12381G1_VARSIG_PREFIX => VarsigPrefix::BLS12381G1, - BLS12381G2_VARSIG_PREFIX => VarsigPrefix::BLS12381G2, - EIP191_VARSIG_PREFIX => VarsigPrefix::EIP191, - ES256_VARSIG_PREFIX => VarsigPrefix::ES256, - ES384_VARSIG_PREFIX => VarsigPrefix::ES384, - ES512_VARSIG_PREFIX => VarsigPrefix::ES512, - NONSTANDARD_VARSIG_PREFIX => VarsigPrefix::NonStandard, - _ => return Err(anyhow!("Signature does not have a recognized prefix")), - }) - } -} - -/// An envelope for the UCAN-IPLD-equivalent of a UCAN's JWT signature, which -/// is a specified prefix in front of the raw signature bytes -/// See: -#[repr(transparent)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Signature(pub Vec); - -impl Signature { - pub fn decode(&self) -> Result<(JwtSignatureAlgorithm, Vec)> { - let buffer = self.0.as_slice(); - let (prefix, buffer) = unsigned_varint::decode::u64(buffer)?; - let (signature_length, buffer) = unsigned_varint::decode::usize(buffer)?; - - // TODO: Non-standard algorithm support here... - - let algorithm = JwtSignatureAlgorithm::try_from(VarsigPrefix::try_from(prefix)?)?; - let signature = buffer[..signature_length].to_vec(); - - Ok((algorithm, signature)) - } -} - -// TODO: Support non-standard signature algorithms for experimental purposes -// Note that non-standard signatures should additionally have the signature name -// appended after the signature bytes in the varsig representation -impl> TryFrom<(JwtSignatureAlgorithm, T)> for Signature { - type Error = anyhow::Error; - - fn try_from((algorithm, signature): (JwtSignatureAlgorithm, T)) -> Result { - // TODO: Non-standard JWT algorithm support here - let signature_bytes = signature.as_ref(); - let prefix = VarsigPrefix::try_from(algorithm)?; - let mut prefix_buffer = unsigned_varint::encode::u64_buffer(); - let prefix_bytes = unsigned_varint::encode::u64(prefix.into(), &mut prefix_buffer); - let mut size_buffer = unsigned_varint::encode::usize_buffer(); - - let size_bytes = unsigned_varint::encode::usize(signature_bytes.len(), &mut size_buffer); - - Ok(Signature( - [prefix_bytes, size_bytes, signature_bytes].concat(), - )) - } -} - -#[cfg(test)] -mod tests { - use crate::{crypto::JwtSignatureAlgorithm, ipld::Signature}; - - use base64::Engine; - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn it_can_convert_between_jwt_and_bytesprefix_form() { - let token_signature = "Ab-xfYRoqYEHuo-252MKXDSiOZkLD-h1gHt8gKBP0AVdJZ6Jruv49TLZOvgWy9QkCpiwKUeGVbHodKcVx-azCQ"; - let signature_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(token_signature) - .unwrap(); - - let bytesprefix_signature = - Signature::try_from((JwtSignatureAlgorithm::EdDSA, &signature_bytes)).unwrap(); - - let (decoded_algorithm, decoded_signature_bytes) = bytesprefix_signature.decode().unwrap(); - - assert_eq!(decoded_algorithm, JwtSignatureAlgorithm::EdDSA); - assert_eq!(decoded_signature_bytes, signature_bytes); - } - - #[allow(dead_code)] - // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - #[ignore = "Support non-standard signature algorithms"] - fn it_can_convert_between_jwt_and_bytesprefix_for_nonstandard_signatures() {} -} diff --git a/ucan/src/ipld/ucan.rs b/ucan/src/ipld/ucan.rs deleted file mode 100644 index c98970d5..00000000 --- a/ucan/src/ipld/ucan.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::{ - capability::Capabilities, - crypto::JwtSignatureAlgorithm, - ipld::{Principle, Signature}, - serde::Base64Encode, - ucan::{FactsMap, Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, -}; -use cid::Cid; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, str::FromStr}; - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct UcanIpld { - pub v: String, - - pub iss: Principle, - pub aud: Principle, - pub s: Signature, - - pub cap: Capabilities, - pub prf: Option>, - pub exp: Option, - pub fct: Option, - - pub nnc: Option, - pub nbf: Option, -} - -impl TryFrom<&Ucan> for UcanIpld { - type Error = anyhow::Error; - - fn try_from(ucan: &Ucan) -> Result { - let prf = if let Some(proofs) = ucan.proofs() { - let mut prf = Vec::new(); - for cid_string in proofs { - prf.push(Cid::try_from(cid_string.as_str())?); - } - if prf.is_empty() { - None - } else { - Some(prf) - } - } else { - None - }; - - Ok(UcanIpld { - v: ucan.version().to_string(), - iss: Principle::from_str(ucan.issuer())?, - aud: Principle::from_str(ucan.audience())?, - s: Signature::try_from(( - JwtSignatureAlgorithm::from_str(ucan.algorithm())?, - ucan.signature(), - ))?, - cap: ucan.capabilities().clone(), - prf, - exp: *ucan.expires_at(), - fct: ucan.facts().clone(), - nnc: ucan.nonce().as_ref().cloned(), - nbf: *ucan.not_before(), - }) - } -} - -impl TryFrom<&UcanIpld> for Ucan { - type Error = anyhow::Error; - - fn try_from(value: &UcanIpld) -> Result { - let (algorithm, signature) = value.s.decode()?; - - let header = UcanHeader { - alg: algorithm.to_string(), - typ: "JWT".into(), - }; - - let payload = UcanPayload { - ucv: UCAN_VERSION.into(), - iss: value.iss.to_string(), - aud: value.aud.to_string(), - exp: value.exp, - nbf: value.nbf, - nnc: value.nnc.clone(), - cap: value.cap.clone(), - fct: value.fct.clone(), - prf: value - .prf - .clone() - .map(|prf| prf.iter().map(|cid| cid.to_string()).collect()), - }; - - let signed_data = format!( - "{}.{}", - header.jwt_base64_encode()?, - payload.jwt_base64_encode()? - ) - .as_bytes() - .to_vec(); - - Ok(Ucan::new(header, payload, signed_data, signature)) - } -} - -#[cfg(test)] -mod tests { - use std::convert::TryFrom; - - use serde_json::json; - - use crate::{ - tests::{ - fixtures::Identities, - helpers::{dag_cbor_roundtrip, scaffold_ucan_builder}, - }, - Ucan, - }; - - use super::UcanIpld; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_produces_canonical_jwt_despite_json_ambiguity() { - let identities = Identities::new().await; - let canon_builder = scaffold_ucan_builder(&identities).await.unwrap(); - let other_builder = scaffold_ucan_builder(&identities).await.unwrap(); - - let canon_jwt = canon_builder - .with_fact( - "abc/challenge", - json!({ - "baz": true, - "foo": "bar" - }), - ) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let other_jwt = other_builder - .with_fact( - "abc/challenge", - json!({ - "foo": "bar", - "baz": true - }), - ) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - assert_eq!(canon_jwt, other_jwt); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_stays_canonical_when_converting_between_jwt_and_ipld() { - let identities = Identities::new().await; - let builder = scaffold_ucan_builder(&identities).await.unwrap(); - - let jwt = builder - .with_fact( - "abc/challenge", - json!({ - "baz": true, - "foo": "bar" - }), - ) - .with_nonce() - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let ucan = Ucan::try_from(jwt.as_str()).unwrap(); - let ucan_ipld = UcanIpld::try_from(&ucan).unwrap(); - - let decoded_ucan_ipld = dag_cbor_roundtrip(&ucan_ipld).unwrap(); - - let decoded_ucan = Ucan::try_from(&decoded_ucan_ipld).unwrap(); - - let decoded_jwt = decoded_ucan.encode().unwrap(); - - assert_eq!(jwt, decoded_jwt); - } -} diff --git a/ucan/src/lib.rs b/ucan/src/lib.rs deleted file mode 100644 index 04c5c8db..00000000 --- a/ucan/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Implement UCAN-based authorization with conciseness and ease! -//! -//! [UCANs][UCAN docs] are an emerging pattern based on -//! [JSON Web Tokens][JWT docs] (aka JWTs) that facilitate distributed and/or -//! decentralized authorization flows in web applications. Visit -//! [https://ucan.xyz][UCAN docs] for an introduction to UCANs and ideas for -//! how you can use them in your application. -//! -//! # Examples -//! -//! This crate offers the [`builder::UcanBuilder`] abstraction to generate -//! signed UCAN tokens. -//! -//! To generate a signed token, you need to provide a [`crypto::KeyMaterial`] -//! implementation. For more information on providing a signing key, see the -//! [`crypto`] module documentation. -//! -//! ```rust -//! use ucan::{ -//! builder::UcanBuilder, -//! crypto::KeyMaterial, -//! }; -//! -//! async fn generate_token<'a, K: KeyMaterial>(issuer_key: &'a K, audience_did: &'a str) -> Result { -//! UcanBuilder::default() -//! .issued_by(issuer_key) -//! .for_audience(audience_did) -//! .with_lifetime(60) -//! .build()? -//! .sign().await? -//! .encode() -//! } -//! ``` -//! -//! The crate also offers a validating parser to interpret UCAN tokens and -//! the capabilities they grant via their issuer and/or witnessing proofs: -//! the [`chain::ProofChain`]. -//! -//! Most capabilities are closely tied to a specific application domain. See the -//! [`capability`] module documentation to read more about defining your own -//! domain-specific semantics. -//! -//! ```rust -//! use ucan::{ -//! chain::{ProofChain, CapabilityInfo}, -//! capability::{CapabilitySemantics, Scope, Ability}, -//! crypto::did::{DidParser, KeyConstructorSlice}, -//! store::UcanJwtStore -//! }; -//! -//! const SUPPORTED_KEY_TYPES: &KeyConstructorSlice = &[ -//! // You must bring your own key support -//! ]; -//! -//! async fn get_capabilities<'a, Semantics, S, A, Store>(ucan_token: &'a str, semantics: &'a Semantics, store: &'a Store) -> Result>, anyhow::Error> -//! where -//! Semantics: CapabilitySemantics, -//! S: Scope, -//! A: Ability, -//! Store: UcanJwtStore -//! { -//! let mut did_parser = DidParser::new(SUPPORTED_KEY_TYPES); -//! -//! Ok(ProofChain::try_from_token_string(ucan_token, None, &mut did_parser, store).await? -//! .reduce_capabilities(semantics)) -//! } -//! ``` -//! -//! Note that you must bring your own key support in order to build a -//! `ProofChain`, via a [`crypto::did::DidParser`]. This is so that the core -//! library can remain agnostic of backing implementations for specific key -//! types. -//! -//! [JWT docs]: https://jwt.io/ -//! [UCAN docs]: https://ucan.xyz/ -//! [DID spec]: https://www.w3.org/TR/did-core/ -//! [DID Key spec]: https://w3c-ccg.github.io/did-method-key/ - -pub mod crypto; -pub mod time; - -pub mod builder; -pub mod capability; -pub mod chain; -pub mod ipld; -pub mod serde; -pub mod store; -pub mod ucan; -pub use self::ucan::Ucan; - -#[cfg(test)] -mod tests; diff --git a/ucan/src/serde.rs b/ucan/src/serde.rs deleted file mode 100644 index a2ffebc4..00000000 --- a/ucan/src/serde.rs +++ /dev/null @@ -1,46 +0,0 @@ -use anyhow::Result; -use base64::Engine; -use libipld_core::{ - codec::{Decode, Encode}, - ipld::Ipld, - serde::{from_ipld, to_ipld}, -}; -use libipld_json::DagJsonCodec; -use serde::{de::DeserializeOwned, Serialize, Serializer}; -use std::io::Cursor; - -/// Utility function to enforce lower-case string values when serializing -pub fn ser_to_lower_case(string: &str, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&string.to_lowercase()) -} - -/// Helper trait to ser/de any serde-implementing value to/from DAG-JSON -pub trait DagJson: Serialize + DeserializeOwned { - fn to_dag_json(&self) -> Result> { - let ipld = to_ipld(self)?; - let mut json_bytes = Vec::new(); - - ipld.encode(DagJsonCodec, &mut json_bytes)?; - - Ok(json_bytes) - } - - fn from_dag_json(json_bytes: &[u8]) -> Result { - let ipld = Ipld::decode(DagJsonCodec, &mut Cursor::new(json_bytes))?; - Ok(from_ipld(ipld)?) - } -} - -impl DagJson for T where T: Serialize + DeserializeOwned {} - -/// Helper trait to encode structs as base64 as part of creating a JWT -pub trait Base64Encode: DagJson { - fn jwt_base64_encode(&self) -> Result { - Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.to_dag_json()?)) - } -} - -impl Base64Encode for T where T: DagJson {} diff --git a/ucan/src/store.rs b/ucan/src/store.rs deleted file mode 100644 index 034a4c32..00000000 --- a/ucan/src/store.rs +++ /dev/null @@ -1,130 +0,0 @@ -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use cid::{ - multihash::{Code, MultihashDigest}, - Cid, -}; -use libipld_core::{ - codec::{Codec, Decode, Encode}, - ipld::Ipld, - raw::RawCodec, -}; -use std::{ - collections::HashMap, - io::Cursor, - sync::{Arc, Mutex}, -}; - -#[cfg(not(target_arch = "wasm32"))] -pub trait UcanStoreConditionalSend: Send {} - -#[cfg(not(target_arch = "wasm32"))] -impl UcanStoreConditionalSend for U where U: Send {} - -#[cfg(target_arch = "wasm32")] -pub trait UcanStoreConditionalSend {} - -#[cfg(target_arch = "wasm32")] -impl UcanStoreConditionalSend for U {} - -#[cfg(not(target_arch = "wasm32"))] -pub trait UcanStoreConditionalSendSync: Send + Sync {} - -#[cfg(not(target_arch = "wasm32"))] -impl UcanStoreConditionalSendSync for U where U: Send + Sync {} - -#[cfg(target_arch = "wasm32")] -pub trait UcanStoreConditionalSendSync {} - -#[cfg(target_arch = "wasm32")] -impl UcanStoreConditionalSendSync for U {} - -/// This trait is meant to be implemented by a storage backend suitable for -/// persisting UCAN tokens that may be referenced as proofs by other UCANs -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait UcanStore: UcanStoreConditionalSendSync { - /// Read a value from the store by CID, returning a Result> that unwraps - /// to None if no value is found, otherwise Some - async fn read>(&self, cid: &Cid) -> Result>; - - /// Write a value to the store, receiving a Result that wraps the values CID if the - /// write was successful - async fn write + UcanStoreConditionalSend + core::fmt::Debug>( - &mut self, - token: T, - ) -> Result; -} - -/// This trait is sugar over the UcanStore trait to add convenience methods -/// for the case of storing JWT-encoded UCAN strings using the 'raw' codec -/// which is the only combination strictly required by the UCAN spec -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait UcanJwtStore: UcanStore { - async fn require_token(&self, cid: &Cid) -> Result { - match self.read_token(cid).await? { - Some(token) => Ok(token), - None => Err(anyhow!("No token found for CID {}", cid.to_string())), - } - } - - async fn read_token(&self, cid: &Cid) -> Result> { - let codec = RawCodec; - - if cid.codec() != u64::from(codec) { - return Err(anyhow!( - "Only 'raw' codec supported, but CID refers to {:#x}", - cid.codec() - )); - } - - match self.read::(cid).await? { - Some(Ipld::Bytes(bytes)) => Ok(Some(std::str::from_utf8(&bytes)?.to_string())), - _ => Err(anyhow!("No UCAN was found for CID {:?}", cid)), - } - } - - async fn write_token(&mut self, token: &str) -> Result { - self.write(Ipld::Bytes(token.as_bytes().to_vec())).await - } -} - -impl UcanJwtStore for U where U: UcanStore {} - -/// A basic in-memory store that implements UcanStore for the 'raw' -/// codec. This will serve for basic use cases and tests, but it is -/// recommended that a store that persists to disk be used in most -/// practical use cases. -#[derive(Clone, Default, Debug)] -pub struct MemoryStore { - dags: Arc>>>, -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl UcanStore for MemoryStore { - async fn read>(&self, cid: &Cid) -> Result> { - let codec = RawCodec; - let dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; - - Ok(match dags.get(cid) { - Some(bytes) => Some(T::decode(codec, &mut Cursor::new(bytes))?), - None => None, - }) - } - - async fn write + UcanStoreConditionalSend + core::fmt::Debug>( - &mut self, - token: T, - ) -> Result { - let codec = RawCodec; - let block = codec.encode(&token)?; - let cid = Cid::new_v1(codec.into(), Code::Blake3_256.digest(&block)); - - let mut dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; - dags.insert(cid, block); - - Ok(cid) - } -} diff --git a/ucan/src/tests/attenuation.rs b/ucan/src/tests/attenuation.rs deleted file mode 100644 index 2ef9679f..00000000 --- a/ucan/src/tests/attenuation.rs +++ /dev/null @@ -1,447 +0,0 @@ -use super::fixtures::{EmailSemantics, Identities, SUPPORTED_KEYS}; -use crate::{ - builder::UcanBuilder, - capability::{Capability, CapabilitySemantics}, - chain::{CapabilityInfo, ProofChain}, - crypto::did::DidParser, - store::{MemoryStore, UcanJwtStore}, -}; -use std::collections::BTreeSet; - -use serde_json::json; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - -#[cfg(target_arch = "wasm32")] -wasm_bindgen_test_configure!(run_in_browser); - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_works_with_a_simple_example() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let email_semantics = EmailSemantics {}; - let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com", "email/send", None) - .unwrap(); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let attenuated_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let chain = - ProofChain::try_from_token_string(attenuated_token.as_str(), None, &mut did_parser, &store) - .await - .unwrap(); - - let capability_infos = chain.reduce_capabilities(&email_semantics); - - assert_eq!(capability_infos.len(), 1); - - let info = capability_infos.get(0).unwrap(); - - assert_eq!( - info.capability.resource().to_string().as_str(), - "mailto:alice@email.com", - ); - assert_eq!(info.capability.ability().to_string().as_str(), "email/send"); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_reports_the_first_issuer_in_the_chain_as_originator() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let email_semantics = EmailSemantics {}; - let send_email_as_bob = email_semantics - .parse("mailto:bob@email.com".into(), "email/send".into(), None) - .unwrap(); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .claiming_capability(&send_email_as_bob) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let capability_infos = - ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store) - .await - .unwrap() - .reduce_capabilities(&email_semantics); - - assert_eq!(capability_infos.len(), 1); - - let info = capability_infos.get(0).unwrap(); - - assert_eq!( - info.originators.iter().collect::>(), - vec![&identities.bob_did] - ); - assert_eq!(info.capability, send_email_as_bob); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_finds_the_right_proof_chain_for_the_originator() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let email_semantics = EmailSemantics {}; - let send_email_as_bob = email_semantics - .parse("mailto:bob@email.com".into(), "email/send".into(), None) - .unwrap(); - let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into(), None) - .unwrap(); - - let leaf_ucan_alice = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(60) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let leaf_ucan_bob = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(60) - .claiming_capability(&send_email_as_bob) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan = UcanBuilder::default() - .issued_by(&identities.mallory_key) - .for_audience(identities.alice_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan_alice, None) - .witnessed_by(&leaf_ucan_bob, None) - .claiming_capability(&send_email_as_alice) - .claiming_capability(&send_email_as_bob) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan_token = ucan.encode().unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan_alice.encode().unwrap()) - .await - .unwrap(); - store - .write_token(&leaf_ucan_bob.encode().unwrap()) - .await - .unwrap(); - - let proof_chain = ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store) - .await - .unwrap(); - let capability_infos = proof_chain.reduce_capabilities(&email_semantics); - - assert_eq!(capability_infos.len(), 2); - - let send_email_as_bob_info = capability_infos.get(0).unwrap(); - let send_email_as_alice_info = capability_infos.get(1).unwrap(); - - assert_eq!( - send_email_as_alice_info, - &CapabilityInfo { - originators: BTreeSet::from_iter(vec![identities.alice_did]), - capability: send_email_as_alice, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() - } - ); - - assert_eq!( - send_email_as_bob_info, - &CapabilityInfo { - originators: BTreeSet::from_iter(vec![identities.bob_did]), - capability: send_email_as_bob, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() - } - ); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_reports_all_chain_options() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let email_semantics = EmailSemantics {}; - let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into(), None) - .unwrap(); - - let leaf_ucan_alice = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(60) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let leaf_ucan_bob = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(60) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan = UcanBuilder::default() - .issued_by(&identities.mallory_key) - .for_audience(identities.alice_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan_alice, None) - .witnessed_by(&leaf_ucan_bob, None) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan_token = ucan.encode().unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan_alice.encode().unwrap()) - .await - .unwrap(); - store - .write_token(&leaf_ucan_bob.encode().unwrap()) - .await - .unwrap(); - - let proof_chain = ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store) - .await - .unwrap(); - let capability_infos = proof_chain.reduce_capabilities(&email_semantics); - - assert_eq!(capability_infos.len(), 1); - - let send_email_as_alice_info = capability_infos.get(0).unwrap(); - - assert_eq!( - send_email_as_alice_info, - &CapabilityInfo { - originators: BTreeSet::from_iter(vec![identities.alice_did, identities.bob_did]), - capability: send_email_as_alice, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() - } - ); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_validates_caveats() -> anyhow::Result<()> { - let resource = "mailto:alice@email.com"; - let ability = "email/send"; - - let no_caveat = Capability::from((resource, ability, &json!({}))); - let x_caveat = Capability::from((resource, ability, &json!({ "x": true }))); - let y_caveat = Capability::from((resource, ability, &json!({ "y": true }))); - let z_caveat = Capability::from((resource, ability, &json!({ "z": true }))); - let yz_caveat = Capability::from((resource, ability, &json!({ "y": true, "z": true }))); - - let valid = [ - (vec![&no_caveat], vec![&no_caveat]), - (vec![&x_caveat], vec![&x_caveat]), - (vec![&no_caveat], vec![&x_caveat]), - (vec![&x_caveat, &y_caveat], vec![&x_caveat]), - (vec![&x_caveat, &y_caveat], vec![&x_caveat, &yz_caveat]), - ]; - - let invalid = [ - (vec![&x_caveat], vec![&no_caveat]), - (vec![&x_caveat], vec![&y_caveat]), - ( - vec![&x_caveat, &y_caveat], - vec![&x_caveat, &y_caveat, &z_caveat], - ), - ]; - - for (proof_capabilities, delegated_capabilities) in valid { - let is_successful = - test_capabilities_delegation(&proof_capabilities, &delegated_capabilities).await?; - assert!( - is_successful, - "{} enables {}", - render_caveats(&proof_capabilities), - render_caveats(&delegated_capabilities) - ); - } - - for (proof_capabilities, delegated_capabilities) in invalid { - let is_successful = - test_capabilities_delegation(&proof_capabilities, &delegated_capabilities).await?; - assert!( - !is_successful, - "{} disallows {}", - render_caveats(&proof_capabilities), - render_caveats(&delegated_capabilities) - ); - } - - fn render_caveats(capabilities: &Vec<&Capability>) -> String { - format!( - "{:?}", - capabilities - .iter() - .map(|cap| cap.caveat.to_string()) - .collect::>() - ) - } - - async fn test_capabilities_delegation( - proof_capabilities: &Vec<&Capability>, - delegated_capabilities: &Vec<&Capability>, - ) -> anyhow::Result { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - let email_semantics = EmailSemantics {}; - let mut store = MemoryStore::default(); - let proof_capabilities = proof_capabilities - .to_owned() - .into_iter() - .map(|cap| cap.to_owned()) - .collect::>(); - let delegated_capabilities = delegated_capabilities - .to_owned() - .into_iter() - .map(|cap| cap.to_owned()) - .collect::>(); - - let proof_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(60) - .claiming_capabilities(&proof_capabilities) - .build()? - .sign() - .await?; - - let ucan = UcanBuilder::default() - .issued_by(&identities.mallory_key) - .for_audience(identities.alice_did.as_str()) - .with_lifetime(50) - .witnessed_by(&proof_ucan, None) - .claiming_capabilities(&delegated_capabilities) - .build()? - .sign() - .await?; - store.write_token(&proof_ucan.encode().unwrap()).await?; - store.write_token(&ucan.encode().unwrap()).await?; - - let proof_chain = ProofChain::from_ucan(ucan, None, &mut did_parser, &store).await?; - - Ok(enables_capabilities( - &proof_chain, - &email_semantics, - &identities.alice_did, - &delegated_capabilities, - )) - } - - /// Checks proof chain returning true if all desired capabilities are enabled. - fn enables_capabilities( - proof_chain: &ProofChain, - semantics: &EmailSemantics, - originator: &String, - desired_capabilities: &Vec, - ) -> bool { - let capability_infos = proof_chain.reduce_capabilities(semantics); - - for desired_capability in desired_capabilities { - let mut has_capability = false; - for info in &capability_infos { - if info.originators.contains(originator) - && info - .capability - .enables(&semantics.parse_capability(desired_capability).unwrap()) - { - has_capability = true; - break; - } - } - if !has_capability { - return false; - } - } - true - } - - Ok(()) -} diff --git a/ucan/src/tests/builder.rs b/ucan/src/tests/builder.rs deleted file mode 100644 index cb1fdeaf..00000000 --- a/ucan/src/tests/builder.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::collections::BTreeMap; - -use crate::{ - builder::UcanBuilder, - capability::{Capabilities, Capability, CapabilitySemantics}, - chain::ProofChain, - crypto::did::DidParser, - store::UcanJwtStore, - tests::fixtures::{ - Blake2bMemoryStore, EmailSemantics, Identities, WNFSSemantics, SUPPORTED_KEYS, - }, - time::now, -}; -use cid::multihash::Code; -use did_key::PatchedKeyPair; -use serde_json::json; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - -#[cfg(target_arch = "wasm32")] -wasm_bindgen_test_configure!(run_in_browser); - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -async fn it_builds_with_a_simple_example() { - let identities = Identities::new().await; - - let fact_1 = json!({ - "test": true - }); - - let fact_2 = json!({ - "preimage": "abc", - "hash": "sth" - }); - - let email_semantics = EmailSemantics {}; - let wnfs_semantics = WNFSSemantics {}; - - let cap_1 = email_semantics - .parse("mailto:alice@gmail.com", "email/send", None) - .unwrap(); - - let cap_2 = wnfs_semantics - .parse("wnfs://alice.fission.name/public", "wnfs/super_user", None) - .unwrap(); - - let expiration = now() + 30; - let not_before = now() - 30; - - let token = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_expiration(expiration) - .not_before(not_before) - .with_fact("abc/challenge", fact_1.clone()) - .with_fact("def/challenge", fact_2.clone()) - .claiming_capability(&cap_1) - .claiming_capability(&cap_2) - .with_nonce() - .build() - .unwrap(); - - let ucan = token.sign().await.unwrap(); - - assert_eq!(ucan.issuer(), identities.alice_did); - assert_eq!(ucan.audience(), identities.bob_did); - assert!(ucan.expires_at().is_some()); - assert_eq!(ucan.expires_at().unwrap(), expiration); - assert!(ucan.not_before().is_some()); - assert_eq!(ucan.not_before().unwrap(), not_before); - assert_eq!( - ucan.facts(), - &Some(BTreeMap::from([ - (String::from("abc/challenge"), fact_1), - (String::from("def/challenge"), fact_2), - ])) - ); - - let expected_attenuations = - Capabilities::try_from(vec![Capability::from(&cap_1), Capability::from(&cap_2)]).unwrap(); - - assert_eq!(ucan.capabilities(), &expected_attenuations); - assert!(ucan.nonce().is_some()); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -async fn it_builds_with_lifetime_in_seconds() { - let identities = Identities::new().await; - - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(300) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - assert!(ucan.expires_at().unwrap() > (now() + 290)); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -async fn it_prevents_duplicate_proofs() { - let wnfs_semantics = WNFSSemantics {}; - - let parent_cap = wnfs_semantics - .parse("wnfs://alice.fission.name/public", "wnfs/super_user", None) - .unwrap(); - - let identities = Identities::new().await; - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(30) - .claiming_capability(&parent_cap) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let attenuated_cap_1 = wnfs_semantics - .parse("wnfs://alice.fission.name/public/Apps", "wnfs/create", None) - .unwrap(); - - let attenuated_cap_2 = wnfs_semantics - .parse( - "wnfs://alice.fission.name/public/Domains", - "wnfs/create", - None, - ) - .unwrap(); - - let next_ucan = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(30) - .witnessed_by(&ucan, None) - .claiming_capability(&attenuated_cap_1) - .claiming_capability(&attenuated_cap_2) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - assert_eq!( - next_ucan.proofs(), - &Some(vec![ucan - .to_cid(UcanBuilder::::default_hasher()) - .unwrap() - .to_string()]) - ) -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_can_use_custom_hasher() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.alice_key) - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, Some(Code::Blake2b256)) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let mut store = Blake2bMemoryStore::default(); - - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let _ = store - .write_token(&delegated_token.encode().unwrap()) - .await - .unwrap(); - - let valid_chain = - ProofChain::from_ucan(delegated_token, Some(now()), &mut did_parser, &store).await; - - assert!(valid_chain.is_ok()); -} diff --git a/ucan/src/tests/capability.rs b/ucan/src/tests/capability.rs deleted file mode 100644 index 11e9bd2d..00000000 --- a/ucan/src/tests/capability.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::capability::{Capabilities, Capability}; -use serde_json::json; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - -#[cfg(target_arch = "wasm32")] -wasm_bindgen_test_configure!(run_in_browser); - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), test)] -fn it_can_cast_between_map_and_sequence() { - let cap_foo = Capability::from(("example://foo", "ability/foo", &json!({}))); - let cap_bar_1 = Capability::from(("example://bar", "ability/bar", &json!({ "beep": 1 }))); - let cap_bar_2 = Capability::from(("example://bar", "ability/bar", &json!({ "boop": 1 }))); - - let cap_sequence = vec![cap_bar_1.clone(), cap_bar_2.clone(), cap_foo]; - let cap_map = Capabilities::try_from(&json!({ - "example://bar": { - "ability/bar": [{ "beep": 1 }, { "boop": 1 }] - }, - "example://foo": { "ability/foo": [{}] }, - })) - .unwrap(); - - assert_eq!( - &cap_map.iter().collect::>(), - &cap_sequence, - "Capabilities map to sequence." - ); - assert_eq!( - &Capabilities::try_from(cap_sequence).unwrap(), - &cap_map, - "Capabilities sequence to map." - ); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), test)] -fn it_rejects_non_compliant_json() { - let failure_cases = [ - (json!([]), "resources must be map"), - ( - json!({ - "resource:foo": [] - }), - "abilities must be map", - ), - ( - json!({"resource:foo": {}}), - "resource must have at least one ability", - ), - ( - json!({"resource:foo": { "ability/read": {} }}), - "caveats must be array", - ), - ( - json!({"resource:foo": { "ability/read": [1] }}), - "caveat must be object", - ), - ]; - - for (json_data, message) in failure_cases { - assert!(Capabilities::try_from(&json_data).is_err(), "{message}"); - } - - assert!(Capabilities::try_from(&json!({ - "resource:foo": { "ability/read": [{}] } - })) - .is_ok()); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), test)] -fn it_filters_out_empty_caveats_when_iterating() { - let cap_map = Capabilities::try_from(&json!({ - "example://bar": { "ability/bar": [{}] }, - "example://foo": { "ability/foo": [] } - })) - .unwrap(); - - assert_eq!( - cap_map.iter().collect::>(), - vec![Capability::from(( - "example://bar", - "ability/bar", - &json!({}) - ))], - "iter() filters out capabilities with empty caveats" - ); -} diff --git a/ucan/src/tests/chain.rs b/ucan/src/tests/chain.rs deleted file mode 100644 index d671b590..00000000 --- a/ucan/src/tests/chain.rs +++ /dev/null @@ -1,254 +0,0 @@ -use super::fixtures::{Identities, SUPPORTED_KEYS}; -use crate::{ - builder::UcanBuilder, - chain::ProofChain, - crypto::did::DidParser, - store::{MemoryStore, UcanJwtStore}, - time::now, -}; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - -#[cfg(target_arch = "wasm32")] -wasm_bindgen_test_configure!(run_in_browser); - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_decodes_deep_ucan_chains() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let chain = - ProofChain::try_from_token_string(delegated_token.as_str(), None, &mut did_parser, &store) - .await - .unwrap(); - - assert_eq!(chain.ucan().audience(), &identities.mallory_did); - assert_eq!( - chain.proofs().get(0).unwrap().ucan().issuer(), - &identities.alice_did - ); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_fails_with_incorrect_chaining() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let parse_token_result = - ProofChain::try_from_token_string(delegated_token.as_str(), None, &mut did_parser, &store) - .await; - - assert!(parse_token_result.is_err()); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_can_be_instantiated_by_cid() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let cid = store.write_token(&delegated_token).await.unwrap(); - - let chain = ProofChain::from_cid(&cid, None, &mut did_parser, &store) - .await - .unwrap(); - - assert_eq!(chain.ucan().audience(), &identities.mallory_did); - assert_eq!( - chain.proofs().get(0).unwrap().ucan().issuer(), - &identities.alice_did - ); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_can_handle_multiple_leaves() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan_1 = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let leaf_ucan_2 = UcanBuilder::default() - .issued_by(&identities.mallory_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.alice_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan_1, None) - .witnessed_by(&leaf_ucan_2, None) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - store - .write_token(&leaf_ucan_1.encode().unwrap()) - .await - .unwrap(); - store - .write_token(&leaf_ucan_2.encode().unwrap()) - .await - .unwrap(); - - ProofChain::try_from_token_string(&delegated_token, None, &mut did_parser, &store) - .await - .unwrap(); -} - -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -pub async fn it_can_use_a_custom_timestamp_to_validate_a_ucan() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let leaf_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(60) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let delegated_token = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_lifetime(50) - .witnessed_by(&leaf_ucan, None) - .build() - .unwrap() - .sign() - .await - .unwrap() - .encode() - .unwrap(); - - let mut store = MemoryStore::default(); - - store - .write_token(&leaf_ucan.encode().unwrap()) - .await - .unwrap(); - - let cid = store.write_token(&delegated_token).await.unwrap(); - - let valid_chain = ProofChain::from_cid(&cid, Some(now()), &mut did_parser, &store).await; - - assert!(valid_chain.is_ok()); - - let invalid_chain = ProofChain::from_cid(&cid, Some(now() + 61), &mut did_parser, &store).await; - - assert!(invalid_chain.is_err()); -} diff --git a/ucan/src/tests/crypto.rs b/ucan/src/tests/crypto.rs deleted file mode 100644 index 0b40b155..00000000 --- a/ucan/src/tests/crypto.rs +++ /dev/null @@ -1,27 +0,0 @@ -mod did_from_keypair { - use base64::Engine; - use did_key::{from_existing_key, Ed25519KeyPair, KeyMaterial as _KeyMaterial}; - - use crate::crypto::KeyMaterial; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_handles_ed25519_keys() { - let pub_key = base64::engine::general_purpose::STANDARD - .decode("Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=") - .unwrap(); - let key = Ed25519KeyPair::from_public_key(&pub_key); - let keypair = from_existing_key::(&key.public_key_bytes(), None); - - let expected_did = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB"; - let result_did = keypair.get_did().await.unwrap(); - - assert_eq!(expected_did, result_did.as_str()); - } -} diff --git a/ucan/src/tests/fixtures/capabilities/email.rs b/ucan/src/tests/fixtures/capabilities/email.rs deleted file mode 100644 index d8e44524..00000000 --- a/ucan/src/tests/fixtures/capabilities/email.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::capability::{Ability, CapabilitySemantics, Scope}; -use anyhow::{anyhow, Result}; -use url::Url; - -#[derive(Clone, PartialEq)] -pub struct EmailAddress(String); - -impl Scope for EmailAddress { - fn contains(&self, other: &Self) -> bool { - return self.0 == other.0; - } -} - -impl ToString for EmailAddress { - fn to_string(&self) -> String { - format!("mailto:{}", self.0.clone()) - } -} - -impl TryFrom for EmailAddress { - type Error = anyhow::Error; - - fn try_from(value: Url) -> Result { - match value.scheme() { - "mailto" => Ok(EmailAddress(String::from(value.path()))), - _ => Err(anyhow!( - "Could not interpret URI as an email address: {}", - value - )), - } - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] -pub enum EmailAction { - Send, -} - -impl Ability for EmailAction {} - -impl ToString for EmailAction { - fn to_string(&self) -> String { - match self { - EmailAction::Send => "email/send", - } - .into() - } -} - -impl TryFrom for EmailAction { - type Error = anyhow::Error; - - fn try_from(value: String) -> Result { - match value.as_str() { - "email/send" => Ok(EmailAction::Send), - _ => Err(anyhow!("Unrecognized action: {}", value)), - } - } -} - -pub struct EmailSemantics {} - -impl CapabilitySemantics for EmailSemantics {} diff --git a/ucan/src/tests/fixtures/capabilities/mod.rs b/ucan/src/tests/fixtures/capabilities/mod.rs deleted file mode 100644 index ee70eec2..00000000 --- a/ucan/src/tests/fixtures/capabilities/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod email; -mod wnfs; - -pub use email::*; -pub use wnfs::*; diff --git a/ucan/src/tests/fixtures/capabilities/wnfs.rs b/ucan/src/tests/fixtures/capabilities/wnfs.rs deleted file mode 100644 index 4b18a3c6..00000000 --- a/ucan/src/tests/fixtures/capabilities/wnfs.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::capability::{Ability, CapabilitySemantics, Scope}; -use anyhow::{anyhow, Result}; -use url::Url; - -#[derive(Ord, Eq, PartialOrd, PartialEq, Clone)] -pub enum WNFSCapLevel { - Create, - Revise, - SoftDelete, - Overwrite, - SuperUser, -} - -impl Ability for WNFSCapLevel {} - -impl TryFrom for WNFSCapLevel { - type Error = anyhow::Error; - - fn try_from(value: String) -> Result { - Ok(match value.as_str() { - "wnfs/create" => WNFSCapLevel::Create, - "wnfs/revise" => WNFSCapLevel::Revise, - "wnfs/soft_delete" => WNFSCapLevel::SoftDelete, - "wnfs/overwrite" => WNFSCapLevel::Overwrite, - "wnfs/super_user" => WNFSCapLevel::SuperUser, - _ => return Err(anyhow!("No such WNFS capability level: {}", value)), - }) - } -} - -impl ToString for WNFSCapLevel { - fn to_string(&self) -> String { - match self { - WNFSCapLevel::Create => "wnfs/create", - WNFSCapLevel::Revise => "wnfs/revise", - WNFSCapLevel::SoftDelete => "wnfs/soft_delete", - WNFSCapLevel::Overwrite => "wnfs/overwrite", - WNFSCapLevel::SuperUser => "wnfs/super_user", - } - .into() - } -} - -#[derive(Clone, PartialEq)] -pub struct WNFSScope { - origin: String, - path: String, -} - -impl Scope for WNFSScope { - fn contains(&self, other: &Self) -> bool { - if self.origin != other.origin { - return false; - } - - let self_path_parts = self.path.split('/'); - let mut other_path_parts = other.path.split('/'); - - for part in self_path_parts { - match other_path_parts.nth(0) { - Some(other_part) => { - if part != other_part { - return false; - } - } - None => return false, - } - } - - true - } -} - -impl TryFrom for WNFSScope { - type Error = anyhow::Error; - - fn try_from(value: Url) -> Result { - match (value.scheme(), value.host_str(), value.path()) { - ("wnfs", Some(host), path) => Ok(WNFSScope { - origin: String::from(host), - path: String::from(path), - }), - _ => Err(anyhow!("Cannot interpret URI as WNFS scope: {}", value)), - } - } -} - -impl ToString for WNFSScope { - fn to_string(&self) -> String { - format!("wnfs://{}{}", self.origin, self.path) - } -} - -pub struct WNFSSemantics {} - -impl CapabilitySemantics for WNFSSemantics {} diff --git a/ucan/src/tests/fixtures/crypto.rs b/ucan/src/tests/fixtures/crypto.rs deleted file mode 100644 index c2fcfc98..00000000 --- a/ucan/src/tests/fixtures/crypto.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::crypto::{ - did::{KeyConstructorSlice, ED25519_MAGIC_BYTES}, - KeyMaterial, -}; -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use did_key::{from_existing_key, CoreSign, Ed25519KeyPair, Fingerprint, PatchedKeyPair}; - -pub const SUPPORTED_KEYS: &KeyConstructorSlice = &[ - // https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 - (ED25519_MAGIC_BYTES, bytes_to_ed25519_key), -]; - -pub fn bytes_to_ed25519_key(bytes: Vec) -> Result> { - Ok(Box::new(from_existing_key::( - bytes.as_slice(), - None, - ))) -} - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl KeyMaterial for PatchedKeyPair { - fn get_jwt_algorithm_name(&self) -> String { - "EdDSA".into() - } - - async fn get_did(&self) -> Result { - Ok(format!("did:key:{}", self.fingerprint())) - } - - async fn sign(&self, payload: &[u8]) -> Result> { - Ok(CoreSign::sign(self, payload)) - } - - async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> { - CoreSign::verify(self, payload, signature).map_err(|error| anyhow!("{:?}", error)) - } -} diff --git a/ucan/src/tests/fixtures/identities.rs b/ucan/src/tests/fixtures/identities.rs deleted file mode 100644 index df199d6a..00000000 --- a/ucan/src/tests/fixtures/identities.rs +++ /dev/null @@ -1,60 +0,0 @@ -use base64::Engine; -use did_key::{ - from_existing_key, Ed25519KeyPair, Generate, KeyMaterial as _KeyMaterial, PatchedKeyPair, -}; - -use crate::crypto::KeyMaterial; - -pub struct Identities { - pub alice_key: PatchedKeyPair, - pub bob_key: PatchedKeyPair, - pub mallory_key: PatchedKeyPair, - - pub alice_did: String, - pub bob_did: String, - pub mallory_did: String, -} - -/// An adaptation of the fixtures used in the canonical ts-ucan repo -/// See: https://github.com/ucan-wg/ts-ucan/blob/main/tests/fixtures.ts -impl Identities { - pub async fn new() -> Self { - // NOTE: tweetnacl secret keys concat the public keys, so we only care - // about the first 32 bytes - let alice_key = Ed25519KeyPair::from_secret_key(&base64::engine::general_purpose::STANDARD.decode("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==".as_bytes()).unwrap().as_slice()[0..32]); - let alice_keypair = from_existing_key::( - &alice_key.public_key_bytes(), - Some(&alice_key.private_key_bytes()), - ); - let bob_key = Ed25519KeyPair::from_secret_key(&base64::engine::general_purpose::STANDARD.decode("G4+QCX1b3a45IzQsQd4gFMMe0UB1UOx9bCsh8uOiKLER69eAvVXvc8P2yc4Iig42Bv7JD2zJxhyFALyTKBHipg==".as_bytes()).unwrap().as_slice()[0..32]); - let bob_keypair = from_existing_key::( - &bob_key.public_key_bytes(), - Some(&bob_key.private_key_bytes()), - ); - let mallory_key = Ed25519KeyPair::from_secret_key(&base64::engine::general_purpose::STANDARD.decode("LR9AL2MYkMARuvmV3MJV8sKvbSOdBtpggFCW8K62oZDR6UViSXdSV/dDcD8S9xVjS61vh62JITx7qmLgfQUSZQ==".as_bytes()).unwrap().as_slice()[0..32]); - let mallory_keypair = from_existing_key::( - &mallory_key.public_key_bytes(), - Some(&mallory_key.private_key_bytes()), - ); - - Identities { - alice_did: alice_keypair.get_did().await.unwrap(), - bob_did: bob_keypair.get_did().await.unwrap(), - mallory_did: mallory_keypair.get_did().await.unwrap(), - - alice_key: alice_keypair, - bob_key: bob_keypair, - mallory_key: mallory_keypair, - } - } - - #[allow(dead_code)] - pub fn name_for(&self, did: String) -> String { - match did { - _ if did == self.alice_did => "alice".into(), - _ if did == self.bob_did => "bob".into(), - _ if did == self.mallory_did => "mallory".into(), - _ => did, - } - } -} diff --git a/ucan/src/tests/fixtures/mod.rs b/ucan/src/tests/fixtures/mod.rs deleted file mode 100644 index 82669dbd..00000000 --- a/ucan/src/tests/fixtures/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod capabilities; -mod crypto; -mod identities; -mod store; - -pub use capabilities::*; -pub use crypto::*; -pub use identities::*; -pub use store::*; diff --git a/ucan/src/tests/fixtures/store.rs b/ucan/src/tests/fixtures/store.rs deleted file mode 100644 index bd158760..00000000 --- a/ucan/src/tests/fixtures/store.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::store::{UcanStore, UcanStoreConditionalSend}; -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use cid::{ - multihash::{Code, MultihashDigest}, - Cid, -}; -use libipld_core::{ - codec::{Codec, Decode, Encode}, - raw::RawCodec, -}; -use std::{ - collections::HashMap, - io::Cursor, - sync::{Arc, Mutex}, -}; - -#[derive(Clone, Default, Debug)] -pub struct Blake2bMemoryStore { - dags: Arc>>>, -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl UcanStore for Blake2bMemoryStore { - async fn read>(&self, cid: &Cid) -> Result> { - let dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; - - Ok(match dags.get(cid) { - Some(bytes) => Some(T::decode(RawCodec, &mut Cursor::new(bytes))?), - None => None, - }) - } - - async fn write + UcanStoreConditionalSend + core::fmt::Debug>( - &mut self, - token: T, - ) -> Result { - let codec = RawCodec; - let block = codec.encode(&token)?; - let cid = Cid::new_v1(codec.into(), Code::Blake2b256.digest(&block)); - - let mut dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; - dags.insert(cid, block); - - Ok(cid) - } -} diff --git a/ucan/src/tests/helpers.rs b/ucan/src/tests/helpers.rs deleted file mode 100644 index c178a4c0..00000000 --- a/ucan/src/tests/helpers.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::fixtures::{EmailSemantics, Identities}; -use crate::{builder::UcanBuilder, capability::CapabilitySemantics}; -use anyhow::Result; -use did_key::PatchedKeyPair; -use serde::{de::DeserializeOwned, Serialize}; -use serde_ipld_dagcbor::{from_slice, to_vec}; - -pub fn dag_cbor_roundtrip(data: &T) -> Result -where - T: Serialize + DeserializeOwned, -{ - Ok(from_slice(&to_vec(data)?)?) -} - -pub async fn scaffold_ucan_builder(identities: &Identities) -> Result> { - let email_semantics = EmailSemantics {}; - let send_email_as_bob = email_semantics - .parse("mailto:bob@email.com".into(), "email/send".into(), None) - .unwrap(); - let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into(), None) - .unwrap(); - - let leaf_ucan_alice = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.mallory_did.as_str()) - .with_expiration(1664232146010) - .claiming_capability(&send_email_as_alice) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let leaf_ucan_bob = UcanBuilder::default() - .issued_by(&identities.bob_key) - .for_audience(identities.mallory_did.as_str()) - .with_expiration(1664232146010) - .claiming_capability(&send_email_as_bob) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let builder = UcanBuilder::default() - .issued_by(&identities.mallory_key) - .for_audience(identities.alice_did.as_str()) - .with_expiration(1664232146010) - .witnessed_by(&leaf_ucan_alice, None) - .witnessed_by(&leaf_ucan_bob, None) - .claiming_capability(&send_email_as_alice) - .claiming_capability(&send_email_as_bob); - - Ok(builder) -} diff --git a/ucan/src/tests/mod.rs b/ucan/src/tests/mod.rs deleted file mode 100644 index 1a8c2355..00000000 --- a/ucan/src/tests/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod attenuation; -mod builder; -mod capability; -mod chain; -mod crypto; -pub mod fixtures; -pub mod helpers; -mod ucan; diff --git a/ucan/src/tests/ucan.rs b/ucan/src/tests/ucan.rs deleted file mode 100644 index 5f2b4b12..00000000 --- a/ucan/src/tests/ucan.rs +++ /dev/null @@ -1,235 +0,0 @@ -mod validate { - use crate::{ - builder::UcanBuilder, - capability::CapabilitySemantics, - crypto::did::DidParser, - tests::fixtures::{EmailSemantics, Identities, SUPPORTED_KEYS}, - time::now, - ucan::Ucan, - }; - use anyhow::Result; - - use serde_json::json; - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_round_trips_with_encode() { - let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); - - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(30) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let encoded_ucan = ucan.encode().unwrap(); - let decoded_ucan = Ucan::try_from(encoded_ucan.as_str()).unwrap(); - - decoded_ucan.validate(None, &mut did_parser).await.unwrap(); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_identifies_a_ucan_that_is_not_active_yet() { - let identities = Identities::new().await; - - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .not_before(now() + 30) - .with_lifetime(30) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - assert!(ucan.is_too_early()); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_identifies_a_ucan_that_has_become_active() { - let identities = Identities::new().await; - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .not_before(now() / 1000) - .with_lifetime(30) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - assert!(!ucan.is_too_early()); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_can_be_serialized_as_json() -> Result<()> { - let identities = Identities::new().await; - - let email_semantics = EmailSemantics {}; - let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into(), None) - .unwrap(); - - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .not_before(now() / 1000) - .with_lifetime(30) - .with_fact("abc/challenge", json!({ "foo": "bar" })) - .claiming_capability(&send_email_as_alice) - .build()? - .sign() - .await?; - - let ucan_json = serde_json::to_value(ucan.clone())?; - - assert_eq!( - ucan_json, - serde_json::json!({ - "header": { - "alg": "EdDSA", - "typ": "JWT" - }, - "payload": { - "ucv": crate::ucan::UCAN_VERSION, - "iss": ucan.issuer(), - "aud": ucan.audience(), - "exp": ucan.expires_at(), - "nbf": ucan.not_before(), - "cap": { - "mailto:alice@email.com": { - "email/send": [{}] - } - }, - "fct": { - "abc/challenge": { "foo": "bar" } - } - }, - "signed_data": ucan.signed_data(), - "signature": ucan.signature() - }) - ); - Ok(()) - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_can_be_serialized_as_json_without_optionals() -> Result<()> { - let identities = Identities::new().await; - let ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .build()? - .sign() - .await?; - - let ucan_json = serde_json::to_value(ucan.clone())?; - - assert_eq!( - ucan_json, - serde_json::json!({ - "header": { - "alg": "EdDSA", - "typ": "JWT" - }, - "payload": { - "ucv": crate::ucan::UCAN_VERSION, - "iss": ucan.issuer(), - "aud": ucan.audience(), - "exp": serde_json::Value::Null, - "cap": {} - }, - "signed_data": ucan.signed_data(), - "signature": ucan.signature() - }) - ); - - Ok(()) - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn it_implements_partial_eq() { - let identities = Identities::new().await; - let ucan_a = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_expiration(10000000) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan_b = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_expiration(10000000) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - let ucan_c = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_expiration(20000000) - .build() - .unwrap() - .sign() - .await - .unwrap(); - - assert!(ucan_a == ucan_b); - assert!(ucan_a != ucan_c); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn test_lifetime_ends_after() -> Result<()> { - let identities = Identities::new().await; - let forever_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .build()? - .sign() - .await?; - let early_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(2000) - .build()? - .sign() - .await?; - let later_ucan = UcanBuilder::default() - .issued_by(&identities.alice_key) - .for_audience(identities.bob_did.as_str()) - .with_lifetime(4000) - .build()? - .sign() - .await?; - - assert_eq!(*forever_ucan.expires_at(), None); - assert!(forever_ucan.lifetime_ends_after(&early_ucan)); - assert!(!early_ucan.lifetime_ends_after(&forever_ucan)); - assert!(later_ucan.lifetime_ends_after(&early_ucan)); - - Ok(()) - } -} diff --git a/ucan/src/time.rs b/ucan/src/time.rs deleted file mode 100644 index af8b5b0b..00000000 --- a/ucan/src/time.rs +++ /dev/null @@ -1,8 +0,0 @@ -use instant::SystemTime; - -pub fn now() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() -} diff --git a/ucan/src/ucan.rs b/ucan/src/ucan.rs deleted file mode 100644 index beaf8d23..00000000 --- a/ucan/src/ucan.rs +++ /dev/null @@ -1,270 +0,0 @@ -use crate::{ - capability::Capabilities, - crypto::did::DidParser, - serde::{Base64Encode, DagJson}, - time::now, -}; -use anyhow::{anyhow, Result}; -use base64::Engine; -use cid::{ - multihash::{Code, MultihashDigest}, - Cid, -}; -use libipld_core::{codec::Codec, raw::RawCodec}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::{collections::BTreeMap, convert::TryFrom, str::FromStr}; - -pub const UCAN_VERSION: &str = "0.10.0-canary"; - -pub type FactsMap = BTreeMap; - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct UcanHeader { - pub alg: String, - pub typ: String, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct UcanPayload { - pub ucv: String, - pub iss: String, - pub aud: String, - pub exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub nbf: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub nnc: Option, - pub cap: Capabilities, - #[serde(skip_serializing_if = "Option::is_none")] - pub fct: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub prf: Option>, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct Ucan { - header: UcanHeader, - payload: UcanPayload, - signed_data: Vec, - signature: Vec, -} - -impl Ucan { - pub fn new( - header: UcanHeader, - payload: UcanPayload, - signed_data: Vec, - signature: Vec, - ) -> Self { - Ucan { - signed_data, - header, - payload, - signature, - } - } - - /// Validate the UCAN's signature and timestamps - pub async fn validate<'a>( - &self, - now_time: Option, - did_parser: &mut DidParser, - ) -> Result<()> { - if self.is_expired(now_time) { - return Err(anyhow!("Expired")); - } - - if self.is_too_early() { - return Err(anyhow!("Not active yet (too early)")); - } - - self.check_signature(did_parser).await - } - - /// Validate that the signed data was signed by the stated issuer - pub async fn check_signature<'a>(&self, did_parser: &mut DidParser) -> Result<()> { - let key = did_parser.parse(&self.payload.iss)?; - key.verify(&self.signed_data, &self.signature).await - } - - /// Produce a base64-encoded serialization of the UCAN suitable for - /// transferring in a header field - pub fn encode(&self) -> Result { - let header = self.header.jwt_base64_encode()?; - let payload = self.payload.jwt_base64_encode()?; - let signature = - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.signature.as_slice()); - - Ok(format!("{header}.{payload}.{signature}")) - } - - /// Returns true if the UCAN has past its expiration date - pub fn is_expired(&self, now_time: Option) -> bool { - if let Some(exp) = self.payload.exp { - exp < now_time.unwrap_or_else(now) - } else { - false - } - } - - /// Raw bytes of signed data for this UCAN - pub fn signed_data(&self) -> &[u8] { - &self.signed_data - } - - pub fn signature(&self) -> &[u8] { - &self.signature - } - - /// Returns true if the not-before ("nbf") time is still in the future - pub fn is_too_early(&self) -> bool { - match self.payload.nbf { - Some(nbf) => nbf > now(), - None => false, - } - } - - /// Returns true if this UCAN's lifetime begins no later than the other - /// Note that if a UCAN specifies an NBF but the other does not, the - /// other has an unbounded start time and this function will return - /// false. - pub fn lifetime_begins_before(&self, other: &Ucan) -> bool { - match (self.payload.nbf, other.payload.nbf) { - (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, - (Some(_), None) => false, - _ => true, - } - } - - /// Returns true if this UCAN expires no earlier than the other - pub fn lifetime_ends_after(&self, other: &Ucan) -> bool { - match (self.payload.exp, other.payload.exp) { - (Some(exp), Some(other_exp)) => exp >= other_exp, - (Some(_), None) => false, - (None, _) => true, - } - } - - /// Returns true if this UCAN's lifetime fully encompasses the other - pub fn lifetime_encompasses(&self, other: &Ucan) -> bool { - self.lifetime_begins_before(other) && self.lifetime_ends_after(other) - } - - pub fn algorithm(&self) -> &str { - &self.header.alg - } - - pub fn issuer(&self) -> &str { - &self.payload.iss - } - - pub fn audience(&self) -> &str { - &self.payload.aud - } - - pub fn proofs(&self) -> &Option> { - &self.payload.prf - } - - pub fn expires_at(&self) -> &Option { - &self.payload.exp - } - - pub fn not_before(&self) -> &Option { - &self.payload.nbf - } - - pub fn nonce(&self) -> &Option { - &self.payload.nnc - } - - #[deprecated(since = "0.4.0", note = "use `capabilities()`")] - pub fn attenuation(&self) -> &Capabilities { - self.capabilities() - } - - pub fn capabilities(&self) -> &Capabilities { - &self.payload.cap - } - - pub fn facts(&self) -> &Option { - &self.payload.fct - } - - pub fn version(&self) -> &str { - &self.payload.ucv - } - - pub fn to_cid(&self, hasher: Code) -> Result { - let codec = RawCodec; - let token = self.encode()?; - let encoded = codec.encode(token.as_bytes())?; - Ok(Cid::new_v1(codec.into(), hasher.digest(&encoded))) - } -} - -/// Deserialize an encoded UCAN token string reference into a UCAN -impl<'a> TryFrom<&'a str> for Ucan { - type Error = anyhow::Error; - - fn try_from(ucan_token: &str) -> Result { - Ucan::from_str(ucan_token) - } -} - -/// Deserialize an encoded UCAN token string into a UCAN -impl TryFrom for Ucan { - type Error = anyhow::Error; - - fn try_from(ucan_token: String) -> Result { - Ucan::from_str(ucan_token.as_str()) - } -} - -/// Deserialize an encoded UCAN token string reference into a UCAN -impl FromStr for Ucan { - type Err = anyhow::Error; - - fn from_str(ucan_token: &str) -> Result { - // better to create multiple iterators than collect, or clone. - let signed_data = ucan_token - .split('.') - .take(2) - .map(String::from) - .reduce(|l, r| format!("{l}.{r}")) - .ok_or_else(|| anyhow!("Could not parse signed data from token string"))?; - - let mut parts = ucan_token.split('.').map(|str| { - base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(str) - .map_err(|error| anyhow!(error)) - }); - - let header = parts - .next() - .ok_or_else(|| anyhow!("Missing UCAN header in token part"))? - .map(|decoded| UcanHeader::from_dag_json(&decoded)) - .map_err(|e| e.context("Could not decode UCAN header base64"))? - .map_err(|e| e.context("Could not parse UCAN header JSON"))?; - - let payload = parts - .next() - .ok_or_else(|| anyhow!("Missing UCAN payload in token part"))? - .map(|decoded| UcanPayload::from_dag_json(&decoded)) - .map_err(|e| e.context("Could not decode UCAN payload base64"))? - .map_err(|e| e.context("Could not parse UCAN payload JSON"))?; - - let signature = parts - .next() - .ok_or_else(|| anyhow!("Missing UCAN signature in token part"))? - .map_err(|e| e.context("Could not parse UCAN signature base64"))?; - - Ok(Ucan::new( - header, - payload, - signed_data.as_bytes().into(), - signature, - )) - } -} From e29141a46057e4477f8ed10697bc870cfaee37fb Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 12:08:05 -0700 Subject: [PATCH 002/234] feat: add error.rs with exposed error types --- Cargo.toml | 1 + src/error.rs | 49 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 22 ++---------------- tests/integration_test.rs | 4 ---- 4 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 2a46729a..eb25915a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ name = "counterparts" path = "examples/counterparts.rs" [dependencies] +anyhow = "1.0.75" proptest = { version = "1.1", optional = true } thiserror = "1.0" tracing = "0.1" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..98d665b4 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,49 @@ +//! Error types for UCAN + +use thiserror::Error; + +/// Error types for UCAN +#[derive(Error, Debug)] +pub enum Error { + /// Parsing errors + #[error("An error occurred while parsing the token: {msg}")] + TokenParseError { + /// Error message + msg: String, + }, + /// Verification errors + #[error("An error occurred while verifying the token: {msg}")] + VerifyingError { + /// Error message + msg: String, + }, + /// Signing errors + #[error("An error occurred while signing the token: {msg}")] + SigningError { + /// Error message + msg: String, + }, + /// Plugin errors + #[error(transparent)] + PluginError(PluginError), + /// Internal errors + #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] + InternalUcanError { + /// Error message + msg: String, + }, +} + +/// Error types for plugins +#[derive(Error, Debug)] +#[error(transparent)] +pub struct PluginError { + #[from] + inner: anyhow::Error, +} + +impl From for Error { + fn from(inner: anyhow::Error) -> Self { + Self::PluginError(PluginError { inner }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0d845439..65ba42b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,27 +4,9 @@ //! rs-ucan +pub mod error; + /// Test utilities. #[cfg(any(test, feature = "test_utils"))] #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] pub mod test_utils; - -/// Add two integers together. -pub fn add(a: i32, b: i32) -> i32 { - a + b -} - -/// Multiplies two integers together. -pub fn mult(a: i32, b: i32) -> i32 { - a * b -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_mult() { - assert_eq!(mult(3, 2), 6); - } -} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 9b72054b..e69de29b 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +0,0 @@ -#[test] -fn test_add() { - assert_eq!(rs_ucan::add(3, 2), 5); -} From ccc2441384ac0849dc3a68049f5dfe2744eba64c Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 13:59:42 -0700 Subject: [PATCH 003/234] feat: add plugin capability handler + plugins for custom semantics --- Cargo.toml | 7 + src/capability.rs | 382 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/plugins.rs | 219 ++++++++++++++++++++++ src/semantics/ability.rs | 54 ++++++ src/semantics/caveat.rs | 55 ++++++ src/semantics/fact.rs | 25 +++ src/semantics/mod.rs | 6 + src/semantics/resource.rs | 27 +++ tests/integration_test.rs | 0 10 files changed, 778 insertions(+) create mode 100644 src/capability.rs create mode 100644 src/plugins.rs create mode 100644 src/semantics/ability.rs create mode 100644 src/semantics/caveat.rs create mode 100644 src/semantics/fact.rs create mode 100644 src/semantics/mod.rs create mode 100644 src/semantics/resource.rs delete mode 100644 tests/integration_test.rs diff --git a/Cargo.toml b/Cargo.toml index eb25915a..00ef7f96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,16 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +downcast-rs = "1.2.0" +dyn-clone = "1.0.14" +erased-serde = "0.3.31" +lazy_static = "1.4.0" +linkme = "0.3.15" proptest = { version = "1.1", optional = true } +serde = { version = "1.0.188", features = ["derive"] } thiserror = "1.0" tracing = "0.1" +url = "2.4.1" [dev-dependencies] criterion = "0.4" diff --git a/src/capability.rs b/src/capability.rs new file mode 100644 index 00000000..50623434 --- /dev/null +++ b/src/capability.rs @@ -0,0 +1,382 @@ +//! Capabilities, and traits for deserializing them + +use std::collections::BTreeMap; + +use serde::{ + de::{DeserializeSeed, IgnoredAny, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use url::Url; + +use crate::semantics::{ + ability::{Ability, TopAbility}, + caveat::{Caveat, EmptyCaveat}, + resource::Resource, +}; + +/// The default capability handler, when deserializing a UCAN +pub type DefaultCapabilityParser = PluginCapability; + +/// A capability +#[derive(Debug, Clone)] +pub struct Capability { + /// The resource + pub resource: Box, + /// The ability + pub ability: Box, + /// The caveat + pub caveat: Box, +} + +impl Capability { + /// Returns the resource + pub fn resource(&self) -> &dyn Resource { + &*self.resource + } + + /// Returns the ability + pub fn ability(&self) -> &dyn Ability { + &*self.ability + } + + /// Returns the caveat + pub fn caveat(&self) -> &dyn Caveat { + &*self.caveat + } + + /// Returns true if self is subsumed by other + pub fn is_subsumed_by(&self, other: &Capability) -> bool { + if !self.resource.is_valid_attenuation(&*other.resource) { + return false; + } + + if !(other.ability.is::() || self.ability.is_valid_attenuation(&*other.ability)) + { + return false; + } + + other.caveat.is::() || self.caveat.is_valid_attenuation(&*other.caveat) + } +} + +/// A collection of capabilities +#[derive(Clone, Debug)] +pub struct Capabilities { + inner: Vec, + _marker: std::marker::PhantomData C>, +} + +impl Default for Capabilities { + fn default() -> Self { + Self { + inner: Default::default(), + _marker: Default::default(), + } + } +} + +impl Capabilities { + /// Creates a new collection of capabilities from a vector + pub fn new(inner: Vec) -> Self { + Self { + inner, + _marker: Default::default(), + } + } + + /// Pushes a capability to the collection + pub fn push(&mut self, capability: Capability) { + self.inner.push(capability); + } + + /// Extends the collection with the capabilities from a slice of capabilities + pub fn extend_from_slice(&mut self, capabilities: &[Capability]) { + self.inner.extend_from_slice(capabilities); + } + + /// Returns an iterator over the capabilities + pub fn iter(&self) -> impl Iterator { + self.inner.iter() + } +} + +/// Handles deserializing capabilities +pub trait CapabilityParser { + /// Tries to deserialize a capability from a resource_uri, ability, and a deserilizer for the caveat + fn try_handle( + resource_uri: &Url, + ability: &str, + caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, anyhow::Error> + where + Self: Sized; +} + +/// A capability handler that deserializes using the registered plugins +#[derive(Clone, Debug)] +pub struct PluginCapability {} + +impl CapabilityParser for PluginCapability { + fn try_handle( + resource_uri: &Url, + ability: &str, + caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, anyhow::Error> { + let resource_scheme = resource_uri.scheme(); + + let Some(plugin) = crate::plugins::plugins().find(|p| p.scheme() == resource_scheme) else { + return Ok(None); + }; + + let Some(resource) = plugin.try_handle_resource(resource_uri)? else { + return Ok(None); + }; + + let Some(ability) = plugin.try_handle_ability(&resource, ability)? else { + return Ok(None); + }; + + let Some(caveat) = plugin.try_handle_caveat(&resource, &ability, caveat_deserializer)? + else { + return Ok(None); + }; + + Ok(Some(Capability { + resource, + ability, + caveat, + })) + } +} + +impl Serialize for Capabilities +where + Cap: CapabilityParser, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut capabilities: BTreeMap>> = + Default::default(); + + for capability in self.iter() { + let resource_uri = capability.resource().to_string(); + let ability_key = capability.ability().to_string(); + let caveat = capability.caveat(); + + capabilities + .entry(resource_uri) + .or_default() + .entry(ability_key) + .or_default() + .push(caveat); + } + + capabilities.serialize(serializer) + } +} + +impl<'de, C> Deserialize<'de> for Capabilities +where + C: CapabilityParser, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CapabilitiesVisitor { + _marker: std::marker::PhantomData C>, + } + + impl<'de, C> Visitor<'de> for CapabilitiesVisitor + where + C: CapabilityParser, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a map of capabilities") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut capabilities = Vec::new(); + + while let Some(resource_key) = map.next_key::()? { + let resource_uri = + Url::parse(&resource_key).map_err(serde::de::Error::custom)?; + + map.next_value_seed(Abilities:: { + resource_uri, + capabilities: &mut capabilities, + _marker: Default::default(), + })?; + } + + Ok(capabilities) + } + } + + let caps = deserializer.deserialize_map(CapabilitiesVisitor:: { + _marker: Default::default(), + })?; + + Ok(Self::new(caps)) + } +} + +struct Abilities<'a, C> { + resource_uri: Url, + capabilities: &'a mut Vec, + _marker: std::marker::PhantomData C>, +} + +impl<'de, 'a, C> DeserializeSeed<'de> for Abilities<'a, C> +where + C: CapabilityParser, +{ + type Value = (); + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AbilitiesVisitor<'a, C> { + resource_uri: Url, + capabilities: &'a mut Vec, + _marker: std::marker::PhantomData C>, + } + + impl<'de, 'a, C> Visitor<'de> for AbilitiesVisitor<'a, C> + where + C: CapabilityParser, + { + type Value = (); + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a map of abilities for {}", self.resource_uri) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + while let Some(ability_key) = map.next_key::()? { + map.next_value_seed(Caveats:: { + resource_uri: self.resource_uri.clone(), + ability_key: ability_key.clone(), + capabilities: self.capabilities, + _marker: Default::default(), + })?; + } + + Ok(()) + } + } + + deserializer.deserialize_map(AbilitiesVisitor:: { + resource_uri: self.resource_uri, + capabilities: self.capabilities, + _marker: Default::default(), + }) + } +} + +struct Caveats<'a, C> { + resource_uri: Url, + ability_key: String, + capabilities: &'a mut Vec, + _marker: std::marker::PhantomData C>, +} + +impl<'de, 'a, C> DeserializeSeed<'de> for Caveats<'a, C> +where + C: CapabilityParser, +{ + type Value = (); + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CaveatsVisitor<'a, C> { + resource_uri: Url, + ability_key: String, + capabilities: &'a mut Vec, + _marker: std::marker::PhantomData C>, + } + + impl<'de, 'a, C> Visitor<'de> for CaveatsVisitor<'a, C> + where + C: CapabilityParser, + { + type Value = (); + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + formatter, + "a map of caveats for {} : {}", + self.resource_uri, self.ability_key + ) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + while let Some(element) = seq.next_element_seed(CaveatSeed:: { + resource_uri: self.resource_uri.clone(), + ability_key: self.ability_key.clone(), + _marker: Default::default(), + })? { + if let Some(capability) = element { + self.capabilities.push(capability); + } + } + + Ok(()) + } + } + + deserializer.deserialize_seq(CaveatsVisitor:: { + resource_uri: self.resource_uri, + ability_key: self.ability_key, + capabilities: self.capabilities, + _marker: Default::default(), + }) + } +} + +struct CaveatSeed { + resource_uri: Url, + ability_key: String, + _marker: std::marker::PhantomData Cap>, +} + +impl<'de, Cap> DeserializeSeed<'de> for CaveatSeed +where + Cap: CapabilityParser, +{ + type Value = Option; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut deserializer = >::erase(deserializer); + + let Some(capability) = + Cap::try_handle(&self.resource_uri, &self.ability_key, &mut deserializer) + .map_err(serde::de::Error::custom)? + else { + erased_serde::deserialize::(&mut deserializer) + .map_err(serde::de::Error::custom)?; + return Ok(None); + }; + + Ok(Some(capability)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 65ba42b9..27972e64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,10 @@ //! rs-ucan +pub mod capability; pub mod error; +pub mod plugins; +pub mod semantics; /// Test utilities. #[cfg(any(test, feature = "test_utils"))] diff --git a/src/plugins.rs b/src/plugins.rs new file mode 100644 index 00000000..1f6ed6a1 --- /dev/null +++ b/src/plugins.rs @@ -0,0 +1,219 @@ +//! Plugins for definining custom semantics + +use core::fmt; +use std::sync::RwLock; + +use downcast_rs::{impl_downcast, Downcast}; +use linkme::distributed_slice; +use url::Url; + +use crate::{ + error::Error, + semantics::{ + ability::{Ability, TopAbility}, + caveat::Caveat, + resource::Resource, + }, +}; + +#[distributed_slice] +static STATIC_PLUGINS: [&dyn Plugin< + Resource = Box, + Ability = Box, + Caveat = Box, + Error = Error, +>] = [..]; + +type ErasedPlugin = dyn Plugin< + Resource = Box, + Ability = Box, + Caveat = Box, + Error = Error, +>; + +lazy_static::lazy_static! { + static ref RUNTIME_PLUGINS: RwLock> = RwLock::new(Vec::new()); +} + +/// A plugin for handling a specific scheme +pub trait Plugin: Send + Sync + Downcast + 'static { + /// The type of resource this plugin handles + type Resource; + + /// The type of ability this plugin handles + type Ability; + + /// The type of caveat this plugin handles + type Caveat; + + /// The type of error this plugin may return + type Error; + + /// The scheme this plugin handles + fn scheme(&self) -> &'static str; + + /// Handle a resource + fn try_handle_resource( + &self, + resource_uri: &Url, + ) -> Result, Self::Error>; + + /// Handle an ability + fn try_handle_ability( + &self, + resource: &Self::Resource, + ability: &str, + ) -> Result, Self::Error>; + + /// Handle a caveat + fn try_handle_caveat( + &self, + resource: &Self::Resource, + ability: &Self::Ability, + deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, Self::Error>; +} + +impl_downcast!(Plugin assoc Resource, Ability, Caveat, Error); + +/// A wrapped plugin that unifies plugin error handling, and handles common semantics, such +/// as top abilities. +pub struct WrappedPlugin +where + R: 'static, + A: 'static, + C: 'static, + E: 'static, +{ + inner: &'static dyn Plugin, +} + +impl Plugin for WrappedPlugin +where + R: Resource, + A: Ability, + C: Caveat, + E: Into, +{ + type Resource = Box; + type Ability = Box; + type Caveat = Box; + + type Error = Error; + + fn scheme(&self) -> &'static str { + self.inner.scheme() + } + + fn try_handle_resource( + &self, + resource_uri: &Url, + ) -> Result, Self::Error> { + self.inner.try_handle_resource(resource_uri).map_or_else( + |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), + |r| Ok(r.map(|r| Box::new(r) as Box)), + ) + } + + fn try_handle_ability( + &self, + resource: &Self::Resource, + ability: &str, + ) -> Result>, Self::Error> { + if ability == "*" { + return Ok(Some(Box::new(TopAbility))); + } + + let Some(resource) = resource.downcast_ref::() else { + return Ok(None); + }; + + self.inner + .try_handle_ability(resource, ability) + .map_or_else( + |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), + |a| Ok(a.map(|a| Box::new(a) as Box)), + ) + } + + fn try_handle_caveat( + &self, + resource: &Self::Resource, + ability: &Self::Ability, + deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, Self::Error> { + let Some(resource) = resource.downcast_ref::() else { + return Ok(None); + }; + + let Some(ability) = ability.downcast_ref::() else { + return Ok(None); + }; + + self.inner + .try_handle_caveat(resource, ability, deserializer) + .map_or_else( + |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), + |c| Ok(c.map(|c| Box::new(c) as Box)), + ) + } +} + +impl fmt::Debug for WrappedPlugin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WrappedPlugin") + .field("scheme", &self.inner.scheme()) + .finish() + } +} + +/// Get an iterator over all plugins +pub fn plugins() -> impl Iterator< + Item = &'static dyn Plugin< + Resource = Box, + Ability = Box, + Caveat = Box, + Error = Error, + >, +> { + let static_plugins = STATIC_PLUGINS.iter().copied(); + let runtime_plugins = RUNTIME_PLUGINS + .read() + .expect("plugin lock poisoned") + .clone() + .into_iter(); + + static_plugins.chain(runtime_plugins) +} + +/// Register a plugin +pub fn register_plugin( + plugin: &'static dyn Plugin, +) where + R: Resource, + A: Ability, + C: Caveat, + E: Into, +{ + let erased = Box::new(WrappedPlugin { inner: plugin }); + let leaked = Box::leak::<'static>(erased); + + RUNTIME_PLUGINS + .write() + .expect("plugin lock poisoned") + .push(leaked); +} + +/// Register a plugin at compile time +#[macro_export] +macro_rules! register_plugin { + ($name:ident, $plugin:expr) => { + #[linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] + static $name: &'static dyn Plugin< + Resource = Box, + Ability = Box, + Caveat = Box, + Error = Error, + > = &$crate::plugins::WrappedPlugin { inner: $plugin }; + }; +} diff --git a/src/semantics/ability.rs b/src/semantics/ability.rs new file mode 100644 index 00000000..74a8117e --- /dev/null +++ b/src/semantics/ability.rs @@ -0,0 +1,54 @@ +//! UCAN Abilities + +use std::fmt::{self, Display}; + +use downcast_rs::{impl_downcast, Downcast}; +use dyn_clone::{clone_trait_object, DynClone}; + +use super::caveat::Caveat; + +/// An ability defined as part of a semantics +pub trait Ability: Display + DynClone + Downcast + 'static { + /// Returns true if self is a valid attenuation of other + fn is_valid_attenuation(&self, other: &dyn Ability) -> bool; + + /// Returns true if caveat is a valid caveat for self + fn is_valid_caveat(&self, _caveat: &dyn Caveat) -> bool { + false + } +} + +clone_trait_object!(Ability); +impl_downcast!(Ability); + +impl Ability for Box { + fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { + (**self).is_valid_attenuation(other) + } +} + +impl fmt::Debug for dyn Ability { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, r#"Ability("{}")"#, self) + } +} + +/// The top ability +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct TopAbility; + +impl Ability for TopAbility { + fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { + if let Some(ability) = other.downcast_ref::() { + return self == ability; + }; + + false + } +} + +impl Display for TopAbility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "*") + } +} diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs new file mode 100644 index 00000000..c4f9297a --- /dev/null +++ b/src/semantics/caveat.rs @@ -0,0 +1,55 @@ +//! UCAN Caveats + +use std::fmt; + +use downcast_rs::{impl_downcast, Downcast}; +use dyn_clone::{clone_trait_object, DynClone}; +use erased_serde::serialize_trait_object; +use serde::{Deserialize, Serialize}; + +/// A caveat defined as part of a semantics +pub trait Caveat: DynClone + Downcast + erased_serde::Serialize + 'static { + /// Returns true if the caveat is valid + fn is_valid(&self) -> bool; + + /// Returns true if self is a valid attenuation of other + fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool; +} + +clone_trait_object!(Caveat); +impl_downcast!(Caveat); +serialize_trait_object!(Caveat); + +impl fmt::Debug for dyn Caveat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Caveat({})", std::any::type_name::()) + } +} + +/// A caveat that is always valid +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct EmptyCaveat {} + +impl Caveat for EmptyCaveat { + fn is_valid(&self) -> bool { + true + } + + fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { + if let Some(resource) = other.downcast_ref::() { + return self == resource; + }; + + false + } +} + +impl Caveat for Box { + fn is_valid(&self) -> bool { + (**self).is_valid() + } + + fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { + (**self).is_valid_attenuation(other) + } +} diff --git a/src/semantics/fact.rs b/src/semantics/fact.rs new file mode 100644 index 00000000..eb9841ff --- /dev/null +++ b/src/semantics/fact.rs @@ -0,0 +1,25 @@ +//! UCAN Facts + +use std::{any::Any, fmt}; + +use downcast_rs::{impl_downcast, Downcast}; +use dyn_clone::{clone_trait_object, DynClone}; +use serde::{Deserialize, Serialize}; + +/// A fact defined as part of a semantics +pub trait Fact: DynClone + Downcast + 'static {} + +clone_trait_object!(Fact); +impl_downcast!(Fact); + +impl Fact for T where T: Any + Clone {} + +impl fmt::Debug for dyn Fact { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Fact({})", std::any::type_name::()) + } +} + +/// The empty fact +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct DefaultFact {} diff --git a/src/semantics/mod.rs b/src/semantics/mod.rs new file mode 100644 index 00000000..c6c9fa33 --- /dev/null +++ b/src/semantics/mod.rs @@ -0,0 +1,6 @@ +//! Semantics for UCAN schemes + +pub mod ability; +pub mod caveat; +pub mod fact; +pub mod resource; diff --git a/src/semantics/resource.rs b/src/semantics/resource.rs new file mode 100644 index 00000000..d706b4e3 --- /dev/null +++ b/src/semantics/resource.rs @@ -0,0 +1,27 @@ +//! UCAN Resources + +use std::fmt::{self, Display}; + +use downcast_rs::{impl_downcast, Downcast}; +use dyn_clone::{clone_trait_object, DynClone}; + +/// A resource defined as part of a semantics +pub trait Resource: Display + DynClone + Downcast + 'static { + /// Returns true if self is a valid attenuation of other + fn is_valid_attenuation(&self, other: &dyn Resource) -> bool; +} + +clone_trait_object!(Resource); +impl_downcast!(Resource); + +impl Resource for Box { + fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { + (**self).is_valid_attenuation(other) + } +} + +impl fmt::Debug for dyn Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, r#"Resource("{}")"#, self) + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs deleted file mode 100644 index e69de29b..00000000 From 9a7dea6dc8c420ba762beec95621e6bc8b84a43b Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 14:10:38 -0700 Subject: [PATCH 004/234] feat: add plugin for the `ucan` scheme --- Cargo.toml | 1 + src/lib.rs | 3 + src/plugins.rs | 2 + src/plugins/ucan.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/plugins/ucan.rs diff --git a/Cargo.toml b/Cargo.toml index 00ef7f96..1a943c99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +cid = "0.10.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" erased-serde = "0.3.31" diff --git a/src/lib.rs b/src/lib.rs index 27972e64..db5f8aa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ pub mod error; pub mod plugins; pub mod semantics; +/// A decentralized identifier. +pub type Did = String; + /// Test utilities. #[cfg(any(test, feature = "test_utils"))] #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] diff --git a/src/plugins.rs b/src/plugins.rs index 1f6ed6a1..e83621b6 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -16,6 +16,8 @@ use crate::{ }, }; +pub mod ucan; + #[distributed_slice] static STATIC_PLUGINS: [&dyn Plugin< Resource = Box, diff --git a/src/plugins/ucan.rs b/src/plugins/ucan.rs new file mode 100644 index 00000000..fe3e3a2f --- /dev/null +++ b/src/plugins/ucan.rs @@ -0,0 +1,139 @@ +//! A plugin for handling the `ucan` scheme. + +use std::fmt::Display; + +use cid::Cid; +use url::Url; + +use crate::{ + error::Error, + semantics::{ability::Ability, caveat::EmptyCaveat}, + Did, +}; + +use super::{Plugin, Resource}; + +/// A plugin for handling the `ucan` scheme. +#[derive(Debug)] +pub struct UcanPlugin; + +crate::register_plugin!(UCAN, &UcanPlugin); + +impl Plugin for UcanPlugin { + type Resource = UcanResource; + type Ability = UcanAbilityDelegation; + type Caveat = EmptyCaveat; + + type Error = anyhow::Error; + + fn scheme(&self) -> &'static str { + "ucan" + } + + fn try_handle_resource( + &self, + resource_uri: &Url, + ) -> Result, Self::Error> { + match resource_uri.path() { + "*" => Ok(Some(UcanResource::AllProvable)), + "./*" => Ok(Some(UcanResource::LocallyProvable)), + path => { + if let Ok(cid) = Cid::try_from(path) { + return Ok(Some(UcanResource::ByCid(cid))); + } + + match resource_uri + .path_segments() + .map(|p| p.collect::>()) + .as_deref() + { + Some([did, "*"]) => Ok(Some(UcanResource::OwnedBy(did.to_string()))), + Some([did, scheme]) => Ok(Some(UcanResource::OwnedByWithScheme( + did.to_string(), + scheme.to_string(), + ))), + _ => Ok(None), + } + } + } + } + + fn try_handle_ability( + &self, + _resource: &Self::Resource, + ability: &str, + ) -> Result, Self::Error> { + match ability { + "ucan/*" => Ok(Some(UcanAbilityDelegation)), + _ => Ok(None), + } + } + + fn try_handle_caveat( + &self, + _resource: &Self::Resource, + _ability: &Self::Ability, + deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, Self::Error> { + erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) + } +} + +/// A resource for the `ucan` scheme. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum UcanResource { + /// ucan: + ByCid(Cid), + /// ucan:* + AllProvable, + /// ucan:./* + LocallyProvable, + /// ucan:///* + OwnedBy(Did), + /// ucan:/// + OwnedByWithScheme(Did, String), +} + +impl Display for UcanResource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hier_part = match self { + UcanResource::ByCid(cid) => cid.to_string(), + UcanResource::AllProvable => "*".to_string(), + UcanResource::LocallyProvable => "./*".to_string(), + UcanResource::OwnedBy(did) => format!("{}/*", did), + UcanResource::OwnedByWithScheme(did, scheme) => format!("{}/{}", did, scheme), + }; + + f.write_fmt(format_args!("ucan:{}", hier_part)) + } +} + +impl Resource for UcanResource { + fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { + if let Some(resource) = other.downcast_ref::() { + return self == resource; + }; + + false + } +} + +/// The UCAN delegation ability from the `ucan` scheme. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct UcanAbilityDelegation; + +impl Ability for UcanAbilityDelegation { + fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { + if let Some(ability) = other.downcast_ref::() { + return self == ability; + }; + + false + } +} + +impl Display for UcanAbilityDelegation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "*") + } +} From ec58118a9121d9d1c6765b2c7269decfad47a520 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 14:13:46 -0700 Subject: [PATCH 005/234] feat: add a plugin for the `wnfs` scheme --- src/plugins.rs | 1 + src/plugins/wnfs.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 src/plugins/wnfs.rs diff --git a/src/plugins.rs b/src/plugins.rs index e83621b6..5b130fd5 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -17,6 +17,7 @@ use crate::{ }; pub mod ucan; +pub mod wnfs; #[distributed_slice] static STATIC_PLUGINS: [&dyn Plugin< diff --git a/src/plugins/wnfs.rs b/src/plugins/wnfs.rs new file mode 100644 index 00000000..263b2ea8 --- /dev/null +++ b/src/plugins/wnfs.rs @@ -0,0 +1,168 @@ +//! A plugin for handling the `wnfs` scheme. + +use std::fmt::Display; + +use crate::{ + error::Error, + semantics::{ability::Ability, caveat::EmptyCaveat, resource::Resource}, +}; +use url::Url; + +use super::Plugin; + +/// A plugin for handling the `wnfs` scheme. +#[derive(Debug)] +pub struct WnfsPlugin; + +crate::register_plugin!(WNFS, &WnfsPlugin); + +impl Plugin for WnfsPlugin { + type Resource = WnfsResource; + type Ability = WnfsAbility; + type Caveat = EmptyCaveat; + + type Error = anyhow::Error; + + fn scheme(&self) -> &'static str { + "wnfs" + } + + fn try_handle_resource( + &self, + resource_uri: &Url, + ) -> Result, Self::Error> { + let Some(user) = resource_uri.host_str() else { + return Ok(None); + }; + + let Some(path_segments) = resource_uri.path_segments() else { + return Ok(None); + }; + + match path_segments.collect::>().as_slice() { + ["public", path @ ..] => Ok(Some(WnfsResource::PublicPath { + user: user.to_string(), + path: path.iter().map(|s| s.to_string()).collect(), + })), + ["private", ..] => todo!(), + _ => Ok(None), + } + } + + fn try_handle_ability( + &self, + _resource: &Self::Resource, + ability: &str, + ) -> Result, Self::Error> { + match ability { + "wnfs/create" => Ok(Some(WnfsAbility::Create)), + "wnfs/revise" => Ok(Some(WnfsAbility::Revise)), + "wnfs/soft_delete" => Ok(Some(WnfsAbility::SoftDelete)), + "wnfs/overwrite" => Ok(Some(WnfsAbility::Overwrite)), + "wnfs/super_user" => Ok(Some(WnfsAbility::SuperUser)), + _ => Ok(None), + } + } + + fn try_handle_caveat( + &self, + _resource: &Self::Resource, + _ability: &Self::Ability, + deserializer: &mut dyn erased_serde::Deserializer<'_>, + ) -> Result, Self::Error> { + erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) + } +} + +/// A resource for the `wnfs` scheme. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum WnfsResource { + /// wnfs:///public/ + PublicPath { + /// The user + user: String, + /// The path + path: Vec, + }, + /// wnfs:///private/ + PrivatePath { + /// The user + user: String, + }, // TODO +} + +impl Resource for WnfsResource { + fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { + let Some(other) = other.downcast_ref::() else { + return false; + }; + + match self { + WnfsResource::PublicPath { user, path } => { + let WnfsResource::PublicPath { + user: other_user, + path: other_path, + } = other + else { + return false; + }; + + if user != other_user { + return false; + } + + path.strip_prefix(other_path.as_slice()).is_some() + } + WnfsResource::PrivatePath { .. } => todo!(), + } + } +} + +impl Display for WnfsResource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WnfsResource::PublicPath { user, path } => { + f.write_fmt(format_args!("wnfs://{}/public/{}", user, path.join("/"))) + } + + WnfsResource::PrivatePath { .. } => todo!(), + } + } +} + +/// An ability for the `wnfs` scheme. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum WnfsAbility { + /// wnfs/create + Create, + /// wnfs/revise + Revise, + /// wnfs/soft_delete + SoftDelete, + /// wnfs/overwrite + Overwrite, + /// wnfs/super_user + SuperUser, +} + +impl Ability for WnfsAbility { + fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { + let Some(other) = other.downcast_ref::() else { + return false; + }; + + self <= other + } +} + +impl Display for WnfsAbility { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WnfsAbility::Create => f.write_str("wnfs/create"), + WnfsAbility::Revise => f.write_str("wnfs/revise"), + WnfsAbility::SoftDelete => f.write_str("wnfs/soft_delete"), + WnfsAbility::Overwrite => f.write_str("wnfs/overwrite"), + WnfsAbility::SuperUser => f.write_str("wnfs/super_user"), + } + } +} From b6739530a274656381ef90a91f6771b41a96e9b1 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 16:09:18 -0700 Subject: [PATCH 006/234] feat: add support for registering DID verification methods --- Cargo.toml | 9 +++++ src/did_verifier.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/did_verifier.rs diff --git a/Cargo.toml b/Cargo.toml index 1a943c99..ca7a07c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,13 +31,22 @@ anyhow = "1.0.75" cid = "0.10.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" +ecdsa = "0.16.8" +ed25519 = "2.2.2" erased-serde = "0.3.31" +k256 = "0.13.1" lazy_static = "1.4.0" linkme = "0.3.15" +multibase = "0.9.1" +p256 = "0.13.2" +p384 = "0.13.0" +p521 = "0.13.0" proptest = { version = "1.1", optional = true } +rsa = "0.9.2" serde = { version = "1.0.188", features = ["derive"] } thiserror = "1.0" tracing = "0.1" +unsigned-varint = "0.7.2" url = "2.4.1" [dev-dependencies] diff --git a/src/did_verifier.rs b/src/did_verifier.rs new file mode 100644 index 00000000..fa52aaa3 --- /dev/null +++ b/src/did_verifier.rs @@ -0,0 +1,88 @@ +//! DID verifier methods + +use core::fmt; +use std::collections::HashMap; + +use crate::error::Error; + +/// A map from did method to verifier +#[derive(Debug, Default)] +pub struct DidVerifierMap { + map: HashMap>, +} + +impl DidVerifierMap { + /// Register a verifier + pub fn register(&mut self, verifier: V) -> &mut Self + where + V: DidVerifier + 'static, + { + self.map + .insert(verifier.method().to_string(), Box::new(verifier)); + self + } + + /// Register a verifier that's already boxed + pub fn register_box(&mut self, verifier: Box) -> &mut Self { + self.map.insert(verifier.method().to_string(), verifier); + self + } + + /// Verify a signature using the registered verifier for the given method + pub fn verify( + &self, + method: &str, + identifier: &str, + payload: &[u8], + signature: &[u8], + ) -> Result<(), Error> { + self.map + .get(method) + .ok_or_else(|| Error::VerifyingError { + msg: format!("Unrecognized DID method, {}", method).to_string(), + })? + .verify(identifier, payload, signature) + .map_err(|e| Error::VerifyingError { msg: e.to_string() }) + } +} + +impl FromIterator> for DidVerifierMap { + fn from_iter>>(iter: T) -> Self { + let mut map = Self::default(); + for verifier in iter { + map.register_box(verifier); + } + + map + } +} + +impl Extend> for DidVerifierMap { + fn extend>>(&mut self, iter: T) { + for verifier in iter { + self.register_box(verifier); + } + } +} + +/// A trait for implementing DID method verification +pub trait DidVerifier { + /// The DID method for this verifier + fn method(&self) -> &'static str; + + /// Verify a signature + fn verify( + &self, + identifier: &str, + payload: &[u8], + signature: &[u8], + ) -> Result<(), anyhow::Error>; +} + +impl fmt::Debug for dyn DidVerifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DidVerifier") + .field("method", &self.method()) + .finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index db5f8aa6..653f4b0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ //! rs-ucan pub mod capability; +pub mod did_verifier; pub mod error; pub mod plugins; pub mod semantics; From 7edff84ac76bc0950050f63f46a560aee3c08921 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 16:11:11 -0700 Subject: [PATCH 007/234] feat: implement did:key verifier --- src/did_verifier.rs | 2 + src/did_verifier/did_key.rs | 196 ++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 src/did_verifier/did_key.rs diff --git a/src/did_verifier.rs b/src/did_verifier.rs index fa52aaa3..089f3f83 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -5,6 +5,8 @@ use std::collections::HashMap; use crate::error::Error; +pub mod did_key; + /// A map from did method to verifier #[derive(Debug, Default)] pub struct DidVerifierMap { diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs new file mode 100644 index 00000000..11331f23 --- /dev/null +++ b/src/did_verifier/did_key.rs @@ -0,0 +1,196 @@ +//! did:key method verifier + +use core::fmt; +use std::{any::TypeId, collections::HashMap}; + +use anyhow::anyhow; +use multibase::Base; + +use super::DidVerifier; + +/// A closure for verifying a signature +pub type SignatureVerifier = dyn Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error>; + +/// did:key method verifier +#[derive(Default)] +pub struct DidKeyVerifier { + /// map from type id of signature to verifier function + verifier_map: HashMap>, +} + +impl DidKeyVerifier { + /// set verifier function for type `T` + pub fn set(&mut self, f: F) -> &mut Self + where + T: 'static, + F: Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error> + 'static, + { + self.verifier_map.insert(TypeId::of::(), Box::new(f)); + self + } + + /// check if verifier function for type `T` is set + pub fn has(&self) -> bool + where + T: 'static, + { + self.verifier_map.contains_key(&TypeId::of::()) + } +} + +impl DidVerifier for DidKeyVerifier { + fn method(&self) -> &'static str { + "key" + } + + fn verify( + &self, + identifier: &str, + payload: &[u8], + signature: &[u8], + ) -> Result<(), anyhow::Error> { + let (base, data) = multibase::decode(identifier).map_err(|e| anyhow!(e))?; + + let Base::Base58Btc = base else { + return Err(anyhow!("expected base58btc, got {:?}", base)); + }; + + let (multicodec, public_key) = + unsigned_varint::decode::u128(&data).map_err(|e| anyhow!(e))?; + + let multicodec_pub_key = MulticodecPubKey::try_from(multicodec)?; + + multicodec_pub_key.validate_pub_key_len(public_key)?; + + let verifier = match multicodec_pub_key { + MulticodecPubKey::Secp256k1Compressed => self + .verifier_map + .get(&TypeId::of::>()), + MulticodecPubKey::X25519 => return Err(anyhow!("x25519 not supported for signing")), + MulticodecPubKey::Ed25519 => self.verifier_map.get(&TypeId::of::()), + MulticodecPubKey::P256Compressed => self + .verifier_map + .get(&TypeId::of::>()), + MulticodecPubKey::P384Compressed => self + .verifier_map + .get(&TypeId::of::>()), + MulticodecPubKey::P521Compressed => self + .verifier_map + .get(&TypeId::of::>()), + MulticodecPubKey::RSAPKCS1 => self + .verifier_map + .get(&TypeId::of::()), + } + .ok_or_else(|| anyhow!("no registered verifier for signature type"))?; + + verifier(public_key, payload, signature) + } +} + +impl fmt::Debug for DidKeyVerifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DidKeyVerifier").finish() + } +} + +/// Multicodec public key +#[derive(Debug)] +pub enum MulticodecPubKey { + /// secp256k1 compressed public key + Secp256k1Compressed, + /// x25519 public key + X25519, + /// ed25519 public key + Ed25519, + /// p256 compressed public key + P256Compressed, + /// p384 compressed public key + P384Compressed, + /// p521 compressed public key + P521Compressed, + /// rsa pkcs1 public key + RSAPKCS1, +} + +impl MulticodecPubKey { + fn validate_pub_key_len(&self, pub_key: &[u8]) -> Result<(), anyhow::Error> { + match self { + MulticodecPubKey::Secp256k1Compressed => { + if pub_key.len() != 33 { + return Err(anyhow!( + "expected 33 bytes for secp256k1 compressed public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::X25519 => { + if pub_key.len() != 32 { + return Err(anyhow!( + "expected 32 bytes for x25519 public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::Ed25519 => { + if pub_key.len() != 32 { + return Err(anyhow!( + "expected 32 bytes for ed25519 public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::P256Compressed => { + if pub_key.len() != 33 { + return Err(anyhow!( + "expected 33 bytes for p256 compressed public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::P384Compressed => { + if pub_key.len() != 49 { + return Err(anyhow!( + "expected 49 bytes for p384 compressed public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::P521Compressed => { + if pub_key.len() > 67 { + return Err(anyhow!( + "expected <= 67 bytes for p521 compressed public key, got {}", + pub_key.len() + )); + } + } + MulticodecPubKey::RSAPKCS1 => match pub_key.len() { + 94 | 126 | 162 | 226 | 294 | 422 | 546 => {} + n => { + return Err(anyhow!( + "expected 94, 126, 162, 226, 294, 422, or 546 bytes for RSA PKCS1 public key, got {}", + n + )); + } + }, + }; + + Ok(()) + } +} + +impl TryFrom for MulticodecPubKey { + type Error = anyhow::Error; + + fn try_from(value: u128) -> Result { + match value { + 0xe7 => Ok(MulticodecPubKey::Secp256k1Compressed), + 0xec => Ok(MulticodecPubKey::X25519), + 0xed => Ok(MulticodecPubKey::Ed25519), + 0x1200 => Ok(MulticodecPubKey::P256Compressed), + 0x1201 => Ok(MulticodecPubKey::P384Compressed), + 0x1202 => Ok(MulticodecPubKey::P521Compressed), + 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), + _ => Err(anyhow!("unsupported multicodec")), + } + } +} From c49f2525ac10ab2a7e62e2ee833488d155fd20ab Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:02:19 -0700 Subject: [PATCH 008/234] feat: add UCAN types --- Cargo.toml | 3 + src/lib.rs | 2 + src/time.rs | 11 ++ src/ucan.rs | 324 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 src/time.rs create mode 100644 src/ucan.rs diff --git a/Cargo.toml b/Cargo.toml index ca7a07c6..f882d7f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ dyn-clone = "1.0.14" ecdsa = "0.16.8" ed25519 = "2.2.2" erased-serde = "0.3.31" +instant = "0.1.12" +jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = "0.13.1" lazy_static = "1.4.0" linkme = "0.3.15" @@ -44,6 +46,7 @@ p521 = "0.13.0" proptest = { version = "1.1", optional = true } rsa = "0.9.2" serde = { version = "1.0.188", features = ["derive"] } +serde_json = "1.0.107" thiserror = "1.0" tracing = "0.1" unsigned-varint = "0.7.2" diff --git a/src/lib.rs b/src/lib.rs index 653f4b0c..09b1cff9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ pub mod did_verifier; pub mod error; pub mod plugins; pub mod semantics; +pub mod time; +pub mod ucan; /// A decentralized identifier. pub type Did = String; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 00000000..13d6cb00 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,11 @@ +//! Time utilities + +use instant::SystemTime; + +/// Get the current time in seconds since UNIX_EPOCH +pub fn now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() +} diff --git a/src/ucan.rs b/src/ucan.rs new file mode 100644 index 00000000..891aa116 --- /dev/null +++ b/src/ucan.rs @@ -0,0 +1,324 @@ +//! JWT embedding of a UCAN + +use std::str::FromStr; + +use crate::{ + capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, + did_verifier::DidVerifierMap, + error::Error, + semantics::{ability::Ability, fact::DefaultFact, resource::Resource}, + time, +}; +use cid::{ + multihash::{self, MultihashDigest}, + Cid, +}; +use serde::{Deserialize, Serialize}; + +/// The current UCAN version +pub const UCAN_VERSION: &str = "0.10.0"; + +/// The UCAN header +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UcanHeader { + pub(crate) alg: String, + pub(crate) typ: String, +} + +/// The UCAN payload +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UcanPayload { + pub(crate) ucv: String, + pub(crate) iss: String, + pub(crate) aud: String, + pub(crate) exp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) nbf: Option, + // TODO: nonce required in 1.0 + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) nnc: Option, + #[serde(bound = "C: CapabilityParser")] + pub(crate) cap: Capabilities, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) fct: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) prf: Option>, +} + +/// A UCAN +#[derive(Clone, Debug)] +pub struct Ucan { + pub(crate) header: jose_b64::serde::Json, + pub(crate) payload: jose_b64::serde::Json>, + pub(crate) signature: jose_b64::serde::Bytes, +} + +impl Ucan +where + F: Serialize, + C: CapabilityParser, +{ + /// Validate the UCAN's signature and timestamps + pub fn validate( + &self, + now_time: Option, + did_verifier_map: &DidVerifierMap, + ) -> Result<(), Error> { + if self.is_expired(now_time) { + return Err(Error::VerifyingError { + msg: "token is expired".to_string(), + }); + } + + if self.is_too_early() { + return Err(Error::VerifyingError { + msg: "current time is before token validity period begins".to_string(), + }); + } + + let (method, identifier) = self + .payload + .iss + .strip_prefix("did:") + .and_then(|did| did.split_once(':')) + .ok_or(Error::VerifyingError { + msg: format!( + "expected did::, got {}", + self.payload.iss + ), + })?; + + let header = serde_json::to_value(&self.header) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let payload = serde_json::to_value(&self.payload) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let signed_data = format!( + "{}.{}", + header.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of header".to_string(), + })?, + payload.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of payload".to_string(), + })?, + ); + + did_verifier_map.verify(method, identifier, signed_data.as_bytes(), &self.signature) + } + + /// Encode the UCAN as a JWT token + pub fn encode(&self) -> Result { + let header = serde_json::to_value(&self.header) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let payload = serde_json::to_value(&self.payload) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let signature = serde_json::to_value(&self.signature) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + Ok(format!( + "{}.{}.{}", + header.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of header".to_string(), + })?, + payload.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of payload".to_string(), + })?, + signature.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of signature".to_string(), + })? + )) + } + + /// Returns true if the UCAN has past its expiration date + pub fn is_expired(&self, now_time: Option) -> bool { + if let Some(exp) = self.payload.exp { + exp < now_time.unwrap_or_else(time::now) + } else { + false + } + } + + /// Returns the UCAN's signature + pub fn signature(&self) -> &jose_b64::serde::Bytes { + &self.signature + } + + /// Returns true if the not-before ("nbf") time is still in the future + pub fn is_too_early(&self) -> bool { + match self.payload.nbf { + Some(nbf) => nbf > time::now(), + None => false, + } + } + + /// Returns true if this UCAN's lifetime begins no later than the other + /// Note that if a UCAN specifies an NBF but the other does not, the + /// other has an unbounded start time and this function will return + /// false. + pub fn lifetime_begins_before(&self, other: &Ucan) -> bool { + match (self.payload.nbf, other.payload.nbf) { + (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, + (Some(_), None) => false, + _ => true, + } + } + + /// Returns true if this UCAN expires no earlier than the other + pub fn lifetime_ends_after(&self, other: &Ucan) -> bool { + match (self.payload.exp, other.payload.exp) { + (Some(exp), Some(other_exp)) => exp >= other_exp, + (Some(_), None) => false, + (None, _) => true, + } + } + + /// Returns true if this UCAN's lifetime fully encompasses the other + pub fn lifetime_encompasses(&self, other: &Ucan) -> bool { + self.lifetime_begins_before(other) && self.lifetime_ends_after(other) + } + + /// Return the `typ` field of the UCAN header + pub fn typ(&self) -> &str { + &self.header.typ + } + + /// Return the `alg` field of the UCAN header + pub fn algorithm(&self) -> &str { + &self.header.alg + } + + /// Return the `iss` field of the UCAN payload + pub fn issuer(&self) -> &str { + &self.payload.iss + } + + /// Return the `aud` field of the UCAN payload + pub fn audience(&self) -> &str { + &self.payload.aud + } + + /// Return the `prf` field of the UCAN payload + pub fn proofs(&self) -> Option<&Vec> { + self.payload.prf.as_ref() + } + + /// Return the `exp` field of the UCAN payload + pub fn expires_at(&self) -> Option { + self.payload.exp + } + + /// Return the `nbf` field of the UCAN payload + pub fn not_before(&self) -> Option { + self.payload.nbf + } + + /// Return the `nnc` field of the UCAN payload + pub fn nonce(&self) -> Option<&String> { + self.payload.nnc.as_ref() + } + + /// Return an iterator over the `cap` field of the UCAN payload + pub fn capabilities(&self) -> impl Iterator { + self.payload.cap.iter() + } + + /// Return the `fct` field of the UCAN payload + pub fn facts(&self) -> Option<&F> { + self.payload.fct.as_ref() + } + + /// Return the `ucv` field of the UCAN payload + pub fn version(&self) -> &str { + &self.payload.ucv + } + + /// Return the CID v1 of the UCAN encoded as a JWT token + pub fn to_cid(&self, hasher: multihash::Code) -> Result { + static RAW_CODEC: u64 = 0x55; + + let token = self.encode()?; + let digest = hasher.digest(token.as_bytes()); + let cid = Cid::new_v1(RAW_CODEC, digest); + + Ok(cid) + } + + /// Returns true if the UCAN authorizes the given resource and ability + // TODO: This is an old placeholder implementation that needs to take + // into account the issuer of the capabilities + pub fn is_authorized(&self, resource: &R, ability: &A) -> bool + where + R: Resource, + A: Ability, + { + for capability in self.capabilities() { + if !resource.is_valid_attenuation(capability.resource()) { + continue; + } + + if !ability.is_valid_attenuation(capability.ability()) { + continue; + } + + if capability.caveat().is_valid() { + return true; + } + } + + false + } +} + +impl<'a> TryFrom<&'a str> for Ucan { + type Error = Error; + + fn try_from(ucan_token: &str) -> Result { + Ucan::from_str(ucan_token) + } +} + +impl TryFrom for Ucan { + type Error = Error; + + fn try_from(ucan_token: String) -> Result { + Ucan::from_str(ucan_token.as_str()) + } +} + +impl FromStr for Ucan { + type Err = Error; + + fn from_str(ucan_token: &str) -> Result { + let &[header, payload, signature] = + ucan_token.splitn(3, '.').collect::>().as_slice() + else { + return Err(Error::TokenParseError { + msg: "malformed token, expected 3 parts separated by dots".to_string(), + }); + }; + + let header = + jose_b64::serde::Json::from_str(header).map_err(|_| Error::TokenParseError { + msg: "malformed header".to_string(), + })?; + + let payload = + jose_b64::serde::Json::from_str(payload).map_err(|_| Error::TokenParseError { + msg: "malformed payload".to_string(), + })?; + + let signature = + jose_b64::serde::Bytes::from_str(signature).map_err(|_| Error::TokenParseError { + msg: "malformed signature".to_string(), + })?; + + Ok(Ucan { + header, + payload, + signature, + }) + } +} From 9c0659506fd111306d346ab71bb9068c1c59d110 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:17:51 -0700 Subject: [PATCH 009/234] feat: add JWSSignature trait for mapping signatures to JWS alg names --- Cargo.toml | 1 + src/crypto.rs | 11 +++++++++++ src/lib.rs | 1 + 3 files changed, 13 insertions(+) create mode 100644 src/crypto.rs diff --git a/Cargo.toml b/Cargo.toml index f882d7f3..c23de7f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ proptest = { version = "1.1", optional = true } rsa = "0.9.2" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" +signature = "2.1.0" thiserror = "1.0" tracing = "0.1" unsigned-varint = "0.7.2" diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 00000000..aceb5bf9 --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,11 @@ +//! Cryptography utilities + +use signature::SignatureEncoding; + +/// A trait for mapping a SignatureEncoding to its algorithm name under JWS +pub trait JWSSignature: SignatureEncoding { + /// The algorithm name under JWS + // I'd originally referenced JWA types directly here, but supporting + // unspecified algorithms, like BLS, means leaving things more open-ended. + const ALGORITHM: &'static str; +} diff --git a/src/lib.rs b/src/lib.rs index 09b1cff9..57b6d7e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ //! rs-ucan pub mod capability; +pub mod crypto; pub mod did_verifier; pub mod error; pub mod plugins; From 6e12522fb13b2edd83f856f5101738152e819d1b Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:21:31 -0700 Subject: [PATCH 010/234] feat: add eddsa support --- Cargo.toml | 1 + src/crypto.rs | 2 ++ src/crypto/eddsa.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 src/crypto/eddsa.rs diff --git a/Cargo.toml b/Cargo.toml index c23de7f3..7fc7c5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ downcast-rs = "1.2.0" dyn-clone = "1.0.14" ecdsa = "0.16.8" ed25519 = "2.2.2" +ed25519-dalek = "2.0.0" erased-serde = "0.3.31" instant = "0.1.12" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } diff --git a/src/crypto.rs b/src/crypto.rs index aceb5bf9..500e0f01 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,6 +2,8 @@ use signature::SignatureEncoding; +pub mod eddsa; + /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { /// The algorithm name under JWS diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs new file mode 100644 index 00000000..0702658c --- /dev/null +++ b/src/crypto/eddsa.rs @@ -0,0 +1,26 @@ +//! EdDSA signature support + +use anyhow::anyhow; +use signature::Verifier; + +use super::JWSSignature; + +impl JWSSignature for ed25519::Signature { + const ALGORITHM: &'static str = "EdDSA"; +} + +/// A verifier for Ed25519 signatures using the `ed25519-dalek` crate +pub fn ed25519_dalek_verifier( + key: &[u8], + payload: &[u8], + signature: &[u8], +) -> Result<(), anyhow::Error> { + let key = ed25519_dalek::VerifyingKey::try_from(key) + .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; + + let signature = ed25519_dalek::Signature::try_from(signature) + .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} From 60ec61210fb0e2b39d0cb07d49063a5567fc907c Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:22:49 -0700 Subject: [PATCH 011/234] feat: add es256 support --- src/crypto.rs | 1 + src/crypto/es256.rs | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 src/crypto/es256.rs diff --git a/src/crypto.rs b/src/crypto.rs index 500e0f01..29713ee8 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -3,6 +3,7 @@ use signature::SignatureEncoding; pub mod eddsa; +pub mod es256; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs new file mode 100644 index 00000000..90a05af9 --- /dev/null +++ b/src/crypto/es256.rs @@ -0,0 +1,7 @@ +//! ES256 signature support + +use super::JWSSignature; + +impl JWSSignature for ecdsa::Signature { + const ALGORITHM: &'static str = "ES256"; +} From 66b8602cd12608a019fc86b36e765f5b780ef40d Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:24:25 -0700 Subject: [PATCH 012/234] feat: add es256k support --- src/crypto.rs | 1 + src/crypto/es256k.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/crypto/es256k.rs diff --git a/src/crypto.rs b/src/crypto.rs index 29713ee8..460cfee4 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,6 +4,7 @@ use signature::SignatureEncoding; pub mod eddsa; pub mod es256; +pub mod es256k; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs new file mode 100644 index 00000000..3c19b50c --- /dev/null +++ b/src/crypto/es256k.rs @@ -0,0 +1,22 @@ +//! ES256K signature support + +use anyhow::anyhow; +use signature::Verifier; + +use super::JWSSignature; + +impl JWSSignature for ecdsa::Signature { + const ALGORITHM: &'static str = "ES256K"; +} + +/// A verifier for ES256k signatures +pub fn k256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { + let key = + k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; + + let signature = k256::ecdsa::Signature::try_from(signature) + .map_err(|_| anyhow!("invalid secp256k1 key"))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} From c82c0ffcf427e8e7bd7d66440f149e3973f0d6b8 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:25:17 -0700 Subject: [PATCH 013/234] feat: add es384 support --- src/crypto.rs | 1 + src/crypto/es384.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/crypto/es384.rs diff --git a/src/crypto.rs b/src/crypto.rs index 460cfee4..7f8d9814 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -5,6 +5,7 @@ use signature::SignatureEncoding; pub mod eddsa; pub mod es256; pub mod es256k; +pub mod es384; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs new file mode 100644 index 00000000..5b998d60 --- /dev/null +++ b/src/crypto/es384.rs @@ -0,0 +1,21 @@ +//! ES384 signature support + +use anyhow::anyhow; +use signature::Verifier; + +use super::JWSSignature; + +impl JWSSignature for ecdsa::Signature { + const ALGORITHM: &'static str = "ES384"; +} + +/// A verifier for ES384 signatures +pub fn p384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { + let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; + + let signature = + p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} From 01acb009d554d9d63768eec4d5a9b806a89d46f1 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:26:09 -0700 Subject: [PATCH 014/234] feat: add es512 support --- src/crypto.rs | 1 + src/crypto/es512.rs | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 src/crypto/es512.rs diff --git a/src/crypto.rs b/src/crypto.rs index 7f8d9814..9f625ac4 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -6,6 +6,7 @@ pub mod eddsa; pub mod es256; pub mod es256k; pub mod es384; +pub mod es512; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/es512.rs b/src/crypto/es512.rs new file mode 100644 index 00000000..f62653ca --- /dev/null +++ b/src/crypto/es512.rs @@ -0,0 +1,7 @@ +//! ES512 signature support + +use super::JWSSignature; + +impl JWSSignature for ecdsa::Signature { + const ALGORITHM: &'static str = "ES512"; +} From f401608f2439cdc8617131ef7d9bd4beddf3c973 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:26:59 -0700 Subject: [PATCH 015/234] feat: add ps256 support --- src/crypto.rs | 1 + src/crypto/ps256.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/crypto/ps256.rs diff --git a/src/crypto.rs b/src/crypto.rs index 9f625ac4..ceaa5ec2 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -7,6 +7,7 @@ pub mod es256; pub mod es256k; pub mod es384; pub mod es512; +pub mod ps256; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs new file mode 100644 index 00000000..3833b72c --- /dev/null +++ b/src/crypto/ps256.rs @@ -0,0 +1,21 @@ +//! PS256 signature support + +use anyhow::anyhow; +use signature::Verifier; + +use super::JWSSignature; + +impl JWSSignature for rsa::pss::Signature { + const ALGORITHM: &'static str = "PS256"; +} + +/// A verifier for PS256 signatures +pub fn p256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { + let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; + + let signature = + p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} From d455ceeab99cd15e70d0414b150e5331ac289d26 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:28:29 -0700 Subject: [PATCH 016/234] feat: add rs256 support --- Cargo.toml | 3 ++- src/crypto.rs | 1 + src/crypto/rs256.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/crypto/rs256.rs diff --git a/Cargo.toml b/Cargo.toml index 7fc7c5cc..0bda8452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,10 @@ p256 = "0.13.2" p384 = "0.13.0" p521 = "0.13.0" proptest = { version = "1.1", optional = true } -rsa = "0.9.2" +rsa = { version = "0.9.2", features = ["sha2"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" +sha2 = "0.10.8" signature = "2.1.0" thiserror = "1.0" tracing = "0.1" diff --git a/src/crypto.rs b/src/crypto.rs index ceaa5ec2..eb4906ba 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -8,6 +8,7 @@ pub mod es256k; pub mod es384; pub mod es512; pub mod ps256; +pub mod rs256; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs new file mode 100644 index 00000000..a3163698 --- /dev/null +++ b/src/crypto/rs256.rs @@ -0,0 +1,28 @@ +//! RS256 signature support + +use anyhow::anyhow; +use signature::Verifier; + +use super::JWSSignature; + +impl JWSSignature for rsa::pkcs1v15::Signature { + const ALGORITHM: &'static str = "RS256"; +} + +/// A verifier for RS256 signatures +pub fn rsassa_pkcs1_v1_5_sha256_verifier( + key: &[u8], + payload: &[u8], + signature: &[u8], +) -> Result<(), anyhow::Error> { + let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) + .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; + + let key = rsa::pkcs1v15::VerifyingKey::::new(key); + + let signature = rsa::pkcs1v15::Signature::try_from(signature) + .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} From 832b33331d03e150f41899cbb4c0b9b8b18f0683 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:30:07 -0700 Subject: [PATCH 017/234] feat: add BLST support --- Cargo.toml | 1 + src/crypto.rs | 1 + src/crypto/bls.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/crypto/bls.rs diff --git a/Cargo.toml b/Cargo.toml index 0bda8452..e7f13c2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +blst = "0.3.11" cid = "0.10.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" diff --git a/src/crypto.rs b/src/crypto.rs index eb4906ba..8beb5e27 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,6 +2,7 @@ use signature::SignatureEncoding; +pub mod bls; pub mod eddsa; pub mod es256; pub mod es256k; diff --git a/src/crypto/bls.rs b/src/crypto/bls.rs new file mode 100644 index 00000000..5067da75 --- /dev/null +++ b/src/crypto/bls.rs @@ -0,0 +1,113 @@ +//! BLS12-381 signature support + +use anyhow::anyhow; +use blst::BLST_ERROR; +use signature::SignatureEncoding; + +use super::JWSSignature; + +/// A BLS12-381 G1 signature +#[derive(Debug, Clone)] +pub struct Bls12381G1Sha256SswuRoNulSignature(pub blst::min_sig::Signature); + +impl<'a> TryFrom<&'a [u8]> for Bls12381G1Sha256SswuRoNulSignature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_sig::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 48] { + fn from(sig: Bls12381G1Sha256SswuRoNulSignature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Bls12381G1Sha256SswuRoNulSignature { + type Repr = [u8; 48]; +} + +impl JWSSignature for Bls12381G1Sha256SswuRoNulSignature { + const ALGORITHM: &'static str = "Bls12381G1"; +} + +/// A BLS12-381 G2 signature +#[derive(Debug, Clone)] +pub struct Bls12381G2Sha256SswuRoNulSignature(pub blst::min_pk::Signature); + +impl<'a> TryFrom<&'a [u8]> for Bls12381G2Sha256SswuRoNulSignature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_pk::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 96] { + fn from(sig: Bls12381G2Sha256SswuRoNulSignature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Bls12381G2Sha256SswuRoNulSignature { + type Repr = [u8; 96]; +} + +impl JWSSignature for Bls12381G2Sha256SswuRoNulSignature { + const ALGORITHM: &'static str = "Bls12381G2"; +} + +/// A verifier for BLS12-381 G1 signatures +pub fn bls_12_381_g1_sha256_sswu_ro_nul_verifier( + key: &[u8], + payload: &[u8], + signature: &[u8], +) -> Result<(), anyhow::Error> { + let dst = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; + let aug = &[]; + + let key = + blst::min_sig::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; + + let signature = blst::min_sig::Signature::uncompress(signature) + .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; + + match signature.verify(true, payload, dst, aug, &key, true) { + BLST_ERROR::BLST_SUCCESS => Ok(()), + BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), + BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), + BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), + BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), + BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), + BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), + BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), + } +} + +/// A verifier for BLS12-381 G2 signatures +pub fn bls_12_381_g2_sha256_sswu_ro_nul_verifier( + key: &[u8], + payload: &[u8], + signature: &[u8], +) -> Result<(), anyhow::Error> { + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + let aug = &[]; + + let key = + blst::min_pk::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; + + let signature = blst::min_pk::Signature::uncompress(signature) + .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; + + match signature.verify(true, payload, dst, aug, &key, true) { + BLST_ERROR::BLST_SUCCESS => Ok(()), + BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), + BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), + BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), + BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), + BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), + BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), + BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), + } +} From cfb7886fba39a47cba6dbc543a82029751c2389e Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 27 Sep 2023 17:40:50 -0700 Subject: [PATCH 018/234] feat: Add a builder for creating UCANs --- src/builder.rs | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 190 insertions(+) create mode 100644 src/builder.rs diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 00000000..7f32288b --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,189 @@ +//! A builder for creating UCANs + +use cid::multihash; +use serde::Serialize; +use signature::Signer; + +use crate::{ + capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, + crypto::JWSSignature, + error::Error, + semantics::fact::DefaultFact, + ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, +}; + +/// The default multihash algorithm used for UCANs +pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; + +/// A builder for creating UCANs +#[derive(Debug, Clone)] +pub struct UcanBuilder { + version: Option, + issuer: Option, + audience: Option, + nonce: Option, + capabilities: Capabilities, + lifetime: Option, + expiration: Option, + not_before: Option, + facts: Option, + proofs: Option>, +} + +impl Default for UcanBuilder { + fn default() -> Self { + Self { + version: Default::default(), + issuer: Default::default(), + audience: Default::default(), + nonce: Default::default(), + capabilities: Default::default(), + lifetime: Default::default(), + expiration: Default::default(), + not_before: Default::default(), + facts: Default::default(), + proofs: Default::default(), + } + } +} + +impl UcanBuilder +where + F: Serialize, + C: CapabilityParser, +{ + /// Set the UCAN version + pub fn version(mut self, version: &str) -> Self { + self.version = Some(version.to_string()); + self + } + + /// Set the issuer of the UCAN + pub fn issued_by>(mut self, issuer: S) -> Self { + self.issuer = Some(issuer.as_ref().to_string()); + self + } + + /// Set the audience of the UCAN + pub fn for_audience>(mut self, audience: S) -> Self { + self.audience = Some(audience.as_ref().to_string()); + self + } + + /// Set the nonce of the UCAN + pub fn with_nonce>(mut self, nonce: S) -> Self { + self.nonce = Some(nonce.as_ref().to_string()); + self + } + + /// Set the lifetime of the UCAN + pub fn with_lifetime(mut self, seconds: u64) -> Self { + self.lifetime = Some(seconds); + self + } + + /// Set the expiration of the UCAN + pub fn with_expiration(mut self, timestamp: u64) -> Self { + self.expiration = Some(timestamp); + self + } + + /// Set the not before of the UCAN + pub fn not_before(mut self, timestamp: u64) -> Self { + self.not_before = Some(timestamp); + self + } + + /// Set the fact of the UCAN + pub fn with_fact(mut self, fact: F) -> Self { + self.facts = Some(fact); + self + } + + /// Add a witness to the proofs of the UCAN + pub fn witnessed_by( + mut self, + authority: &Ucan, + hasher: Option, + ) -> Self + where + F2: Serialize, + C2: CapabilityParser, + { + let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); + + match authority.to_cid(hasher) { + Ok(cid) => { + self.proofs + .get_or_insert(Default::default()) + .push(cid.to_string()); + } + Err(e) => panic!("Failed to add authority: {}", e), + } + + self + } + + /// Claim a capability for the UCAN + pub fn claiming_capability(mut self, capability: Capability) -> Self { + self.capabilities.push(capability); + self + } + + /// Claim multiple capabilities for the UCAN + pub fn claiming_capabilities(mut self, capabilities: &[Capability]) -> Self { + self.capabilities.extend_from_slice(capabilities); + self + } + + /// Sign the UCAN with the given signer + pub fn sign(self, signer: &S) -> Result, Error> + where + S: Signer, + K: JWSSignature, + { + let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); + + let Some(issuer) = self.issuer else { + return Err(Error::SigningError { + msg: "an issuer is required".to_string(), + }); + }; + + let Some(audience) = self.audience else { + return Err(Error::SigningError { + msg: "an audience is required".to_string(), + }); + }; + + let header = jose_b64::serde::Json::new(UcanHeader { + alg: K::ALGORITHM.to_string(), + typ: "JWT".to_string(), + }) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let payload = jose_b64::serde::Json::new(UcanPayload { + ucv: version, + iss: issuer, + aud: audience, + exp: self.expiration, + nbf: self.not_before, + nnc: self.nonce, + cap: self.capabilities, + fct: self.facts, + prf: self.proofs, + }) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let signature = signer + .sign(&[header.as_ref(), ".".as_bytes(), payload.as_ref()].concat()) + .to_vec() + .into(); + + Ok(Ucan { + header, + payload, + signature, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 57b6d7e9..6cf959b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ //! rs-ucan +pub mod builder; pub mod capability; pub mod crypto; pub mod did_verifier; From 841be84bbc16fc477d32fe7dde0f66dcc2ebb89a Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Thu, 28 Sep 2023 13:39:31 -0700 Subject: [PATCH 019/234] test: add UCAN 0.10.0 conformance tests Note that not all of them pass yet, and that the build tests are impossible to support, as currently defined, due to JSON canonicalization issues. Fixtures from: https://github.com/ucan-wg/conformance-tests/pull/13 --- Cargo.toml | 2 + src/ucan.rs | 46 +- tests/conformance.rs | 376 +++++++++ tests/fixtures/0.10.0/all.json | 1418 ++++++++++++++++++++++++++++++++ 4 files changed, 1838 insertions(+), 4 deletions(-) create mode 100644 tests/conformance.rs create mode 100644 tests/fixtures/0.10.0/all.json diff --git a/Cargo.toml b/Cargo.toml index e7f13c2f..d5d87edc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ p384 = "0.13.0" p521 = "0.13.0" proptest = { version = "1.1", optional = true } rsa = { version = "0.9.2", features = ["sha2"] } +semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" sha2 = "0.10.8" @@ -58,6 +59,7 @@ url = "2.4.1" [dev-dependencies] criterion = "0.4" +multihash = "0.18.0" proptest = "1.1" [features] diff --git a/src/ucan.rs b/src/ucan.rs index 891aa116..93aa3b9c 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -13,7 +13,8 @@ use cid::{ multihash::{self, MultihashDigest}, Cid, }; -use serde::{Deserialize, Serialize}; +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize}; /// The current UCAN version pub const UCAN_VERSION: &str = "0.10.0"; @@ -31,6 +32,7 @@ pub struct UcanPayload { pub(crate) ucv: String, pub(crate) iss: String, pub(crate) aud: String, + #[serde(deserialize_with = "deserialize_required_nullable")] pub(crate) exp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) nbf: Option, @@ -64,6 +66,21 @@ where now_time: Option, did_verifier_map: &DidVerifierMap, ) -> Result<(), Error> { + if self.typ() != "JWT" { + return Err(Error::VerifyingError { + msg: format!("expected header typ field to be 'JWT', got {}", self.typ()), + }); + } + + if Version::parse(self.version()).is_err() { + return Err(Error::VerifyingError { + msg: format!( + "expected header ucv field to be a semver, got {}", + self.version() + ), + }); + } + if self.is_expired(now_time) { return Err(Error::VerifyingError { msg: "token is expired".to_string(), @@ -76,6 +93,18 @@ where }); } + // TODO: parse and validate iss and aud DIDs during deserialization + self.payload + .aud + .strip_prefix("did:") + .and_then(|did| did.split_once(':')) + .ok_or(Error::VerifyingError { + msg: format!( + "expected did::, got {}", + self.payload.aud + ), + })?; + let (method, identifier) = self .payload .iss @@ -158,7 +187,7 @@ where /// Note that if a UCAN specifies an NBF but the other does not, the /// other has an unbounded start time and this function will return /// false. - pub fn lifetime_begins_before(&self, other: &Ucan) -> bool { + pub fn lifetime_begins_before(&self, other: &Ucan) -> bool { match (self.payload.nbf, other.payload.nbf) { (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, (Some(_), None) => false, @@ -167,7 +196,7 @@ where } /// Returns true if this UCAN expires no earlier than the other - pub fn lifetime_ends_after(&self, other: &Ucan) -> bool { + pub fn lifetime_ends_after(&self, other: &Ucan) -> bool { match (self.payload.exp, other.payload.exp) { (Some(exp), Some(other_exp)) => exp >= other_exp, (Some(_), None) => false, @@ -176,7 +205,7 @@ where } /// Returns true if this UCAN's lifetime fully encompasses the other - pub fn lifetime_encompasses(&self, other: &Ucan) -> bool { + pub fn lifetime_encompasses(&self, other: &Ucan) -> bool { self.lifetime_begins_before(other) && self.lifetime_ends_after(other) } @@ -322,3 +351,12 @@ impl FromStr for Ucan { }) } } + +fn deserialize_required_nullable<'de, T, D>(deserializer: D) -> Result +where + T: Deserialize<'de>, + D: Deserializer<'de>, +{ + Deserialize::deserialize(deserializer) + .map_err(|_| serde::de::Error::custom("required field is missing or has invalid type")) +} diff --git a/tests/conformance.rs b/tests/conformance.rs new file mode 100644 index 00000000..ce467bc6 --- /dev/null +++ b/tests/conformance.rs @@ -0,0 +1,376 @@ +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; + +use rs_ucan::{ + crypto::eddsa::ed25519_dalek_verifier, + did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + ucan::Ucan, +}; + +trait TestTask { + fn run(&self, name: &str, report: &mut TestReport); +} + +#[derive(Debug, Default)] +struct TestReport { + num_tests: usize, + successes: Vec, + failures: Vec, +} + +#[derive(Debug)] +struct TestFailure { + name: String, + error: String, +} + +impl TestReport { + fn register_success(&mut self, name: &str) { + self.num_tests += 1; + self.successes.push(name.to_string()); + } + + fn register_failure(&mut self, name: &str, error: String) { + self.num_tests += 1; + self.failures.push(TestFailure { + name: name.to_string(), + error, + }); + } + + fn finish(&self) { + for success in &self.successes { + println!("✅ {}", success); + } + + for failure in &self.failures { + println!("❌ {}: {}", failure.name, failure.error); + } + + println!( + "{} tests, {} successes, {} failures", + self.num_tests, + self.successes.len(), + self.failures.len() + ); + + if !self.failures.is_empty() { + panic!(); + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct TestFixture { + name: String, + #[serde(flatten)] + test_case: TestCase, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "task", rename_all = "camelCase")] +enum TestCase { + Verify(VerifyTest), + Refute(RefuteTest), + Build(BuildTest), + ToCID(ToCidTest), +} + +#[derive(Debug, Serialize, Deserialize)] +struct VerifyTest { + inputs: TestInputsTokenAndProofs, + assertions: TestAssertions, +} + +#[derive(Debug, Serialize, Deserialize)] +struct RefuteTest { + inputs: TestInputsTokenAndProofs, + assertions: TestAssertions, + errors: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +struct BuildTest { + inputs: BuildTestInputs, + outputs: BuildTestOutputs, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ToCidTest { + inputs: ToCidTestInputs, + outputs: ToCidTestOutputs, +} + +#[derive(Debug, Serialize, Deserialize)] +struct TestInputsTokenAndProofs { + token: String, + proofs: HashMap, +} + +#[derive(Debug, Serialize, Deserialize)] +struct TestAssertions { + header: TestAssertionsHeader, + payload: TestAssertionsPayload, + signature: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct TestAssertionsHeader { + alg: Option, + typ: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct TestAssertionsPayload { + ucv: Option, + iss: Option, + aud: Option, + exp: Option, + // TODO: CAP + // TODO: FCT + prf: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct BuildTestInputs { + version: Option, + issuer_base64_key: String, + signature_scheme: String, + audience: Option, + not_before: Option, + expiration: Option, + // TODO CAPABILITIES + // TODO FACTS +} + +#[derive(Debug, Serialize, Deserialize)] +struct BuildTestOutputs { + token: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ToCidTestInputs { + token: String, + hasher: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ToCidTestOutputs { + cid: String, +} + +impl TestTask for VerifyTest { + fn run(&self, name: &str, report: &mut TestReport) { + let mut did_key_verifier = DidKeyVerifier::default(); + let mut did_verifier_map = DidVerifierMap::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + did_verifier_map.register(did_key_verifier); + + let Ok(ucan) = Ucan::from_str(&self.inputs.token) else { + report.register_failure(name, "Failed to parse token".to_string()); + + return; + }; + + if let Some(alg) = &self.assertions.header.alg { + if ucan.algorithm() != alg { + report.register_failure( + name, + format!( + "Expected algorithm to be {}, but was {}", + alg, + ucan.algorithm() + ), + ); + + return; + } + } + + if let Some(typ) = &self.assertions.header.typ { + if ucan.typ() != typ { + report.register_failure( + name, + format!("Expected type to be {}, but was {}", typ, ucan.typ()), + ); + + return; + } + } + + if let Some(ucv) = &self.assertions.payload.ucv { + if ucan.version() != ucv { + report.register_failure( + name, + format!("Expected version to be {}, but was {}", ucv, ucan.version()), + ); + + return; + } + } + + if let Some(iss) = &self.assertions.payload.iss { + if ucan.issuer() != iss { + report.register_failure( + name, + format!("Expected issuer to be {}, but was {}", iss, ucan.issuer()), + ); + + return; + } + } + + if let Some(aud) = &self.assertions.payload.aud { + if ucan.audience() != aud { + report.register_failure( + name, + format!( + "Expected audience to be {}, but was {}", + aud, + ucan.audience() + ), + ); + + return; + } + } + + if ucan.expires_at() != self.assertions.payload.exp { + report.register_failure( + name, + format!( + "Expected expiration to be {:?}, but was {:?}", + self.assertions.payload.exp, + ucan.expires_at() + ), + ); + + return; + } + + if ucan.proofs().cloned() != self.assertions.payload.prf { + report.register_failure( + name, + format!( + "Expected proofs to be {:?}, but was {:?}", + self.assertions.payload.prf, + ucan.proofs().cloned() + ), + ); + + return; + } + + let Ok(signature) = serde_json::to_value(ucan.signature()) else { + report.register_failure(name, "Failed to serialize signature".to_string()); + + return; + }; + + let Some(signature) = signature.as_str() else { + report.register_failure(name, "Expected signature to be a string".to_string()); + + return; + }; + + if signature != self.assertions.signature { + report.register_failure( + name, + format!( + "Expected signature to be {}, but was {}", + self.assertions.signature, signature + ), + ); + + return; + } + + if let Err(err) = ucan.validate(Some(rs_ucan::time::now()), &did_verifier_map) { + report.register_failure(name, err.to_string()); + + return; + } + } +} + +impl TestTask for RefuteTest { + fn run(&self, name: &str, report: &mut TestReport) { + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + if let Ok(ucan) = Ucan::from_str(&self.inputs.token) { + if ucan + .validate(Some(rs_ucan::time::now()), &did_verifier_map) + .is_ok() + { + report.register_failure( + &name, + "Expected token to fail validation, but it passed".to_string(), + ); + + return; + } + } + } +} + +impl TestTask for BuildTest { + fn run(&self, _: &str, _: &mut TestReport) { + //TODO: can't assert on signature because of canonicalization issues + } +} + +impl TestTask for ToCidTest { + fn run(&self, name: &str, report: &mut TestReport) { + let ucan = Ucan::from_str(&self.inputs.token).unwrap(); + let hasher = match self.inputs.hasher.as_str() { + "SHA2-256" => multihash::Code::Sha2_256, + "BLAKE3-256" => multihash::Code::Blake3_256, + _ => panic!(), + }; + + let Ok(cid) = ucan.to_cid(hasher) else { + report.register_failure(&name, "Failed to convert to CID".to_string()); + + return; + }; + + if cid.to_string() != self.outputs.cid { + report.register_failure( + &name, + format!( + "Expected CID to be {}, but was {}", + self.outputs.cid, + cid.to_string() + ), + ); + + return; + } + } +} + +#[test] +fn ucan_0_10_0_conformance_tests() { + let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); + let reader = BufReader::new(fixtures_file); + let fixtures: Vec = serde_json::from_reader(reader).unwrap(); + + let mut report = TestReport::default(); + + for fixture in fixtures { + match fixture.test_case { + TestCase::Verify(test) => test.run(&fixture.name, &mut report), + TestCase::Refute(test) => test.run(&fixture.name, &mut report), + TestCase::Build(test) => test.run(&fixture.name, &mut report), + TestCase::ToCID(test) => test.run(&fixture.name, &mut report), + }; + + report.register_success(&fixture.name); + } + + report.finish(); +} diff --git a/tests/fixtures/0.10.0/all.json b/tests/fixtures/0.10.0/all.json new file mode 100644 index 00000000..e4904778 --- /dev/null +++ b/tests/fixtures/0.10.0/all.json @@ -0,0 +1,1418 @@ +[ + { + "name": "UCAN has not expired", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": 9246211200, + "cap": {} + }, + "signature": "pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg" + } + }, + { + "name": "UCAN is ready to be used", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjEsInVjdiI6IjAuMTAuMCJ9.-yaM1x8v4jIvi5ldLsjN3unAJiaFx2D1gl4z_Ct8OCcS_afEW-q8phwyOVu3DKFP8dGoEvlMQMhTfPsiUOCsAQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "nbf": 1, + "cap": {} + }, + "signature": "-yaM1x8v4jIvi5ldLsjN3unAJiaFx2D1gl4z_Ct8OCcS_afEW-q8phwyOVu3DKFP8dGoEvlMQMhTfPsiUOCsAQ" + } + }, + { + "name": "UCAN has same time bounds as proof", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJuYmYiOjEsInByZiI6WyJiYWZrcmVpZTNiZndyMnB4dHpwaHpwc3BiM3BsdmRyaDNvYnE1eWl5aWJmdXVpcWoybTNrY3JyZXhwdSJdLCJ1Y3YiOiIwLjEwLjAifQ.C7ceqIwzJYqC5TQf8PRXjMCYri1JxpioZFU0LIYpM1fP_Xn7Eij9qcRd5WUXvKmUAGmn_gmv8rolXbe4n3UAAA", + "proofs": { + "bafkreie3bfwr2pxtzphzpspb3plvdrh3obq5yiyibfuuiqj2m3kcrrexpu": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjEsInVjdiI6IjAuMTAuMCJ9.qvC6-4agkAxd72ZKNardHy8YHpKGAhz9sbNlWMys0LBoccifGCl-9Yz3bpy4SuosAbWy-W2tc5MGzFCNRmLpAw" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": 9246211200, + "nbf": 1, + "cap": {}, + "prf": [ + "bafkreie3bfwr2pxtzphzpspb3plvdrh3obq5yiyibfuuiqj2m3kcrrexpu" + ] + }, + "signature": "C7ceqIwzJYqC5TQf8PRXjMCYri1JxpioZFU0LIYpM1fP_Xn7Eij9qcRd5WUXvKmUAGmn_gmv8rolXbe4n3UAAA" + } + }, + { + "name": "UCAN expires before proof", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJwcmYiOlsiYmFma3JlaWN6Nnl2czRlcHplbXprNmMyb3E0d3BvamVvNWh1c3diaDd1cGtzNDVpa25kYnllcmdsbmEiXSwidWN2IjoiMC4xMC4wIn0.iWzUN38aE9Kid_f3P8ahMPg7oKHymAVqdx0Lr1XfZqdBPB33T0uBBuGQiMpMPmx_55ReWAulyxZzFgTqgBDKDw", + "proofs": { + "bafkreicz6yvs4epzemzk6c2oq4wpojeo5huswbh7upks45ikndbyerglna": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6MTQwNjkxNDIwMDAsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.qtp60plAo81TRm56PMdlkOwUZT2uPFWzWLtjZmti6_KLULOZXQYN6h9ihXz9MNX3HflUIZoBsWJnPN_8--y4AA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": 9246211200, + "cap": {}, + "prf": [ + "bafkreicz6yvs4epzemzk6c2oq4wpojeo5huswbh7upks45ikndbyerglna" + ] + }, + "signature": "iWzUN38aE9Kid_f3P8ahMPg7oKHymAVqdx0Lr1XfZqdBPB33T0uBBuGQiMpMPmx_55ReWAulyxZzFgTqgBDKDw" + } + }, + { + "name": "UCAN active after proof", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJuYmYiOjIsInByZiI6WyJiYWZrcmVpZ3hpeTdwdndtZmxhaDZjN3picW12a3JpaW02amlodnFwaDIzb216M3V3ZGphN3E0ZnEyaSJdLCJ1Y3YiOiIwLjEwLjAifQ.X9vegej9T07LaA5wPtCj4WcV_vjy2KgkvKYTIT4IXoFvtZwrcUj6ABOG54LpWlXVto-Y09zIi2W3Miwzu10CAw", + "proofs": { + "bafkreigxiy7pvwmflah6c7zbqmvkriim6jihvqph23omz3uwdja7q4fq2i": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjEsInVjdiI6IjAuMTAuMCJ9.-yaM1x8v4jIvi5ldLsjN3unAJiaFx2D1gl4z_Ct8OCcS_afEW-q8phwyOVu3DKFP8dGoEvlMQMhTfPsiUOCsAQ" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "nbf": 2, + "cap": {}, + "prf": [ + "bafkreigxiy7pvwmflah6c7zbqmvkriim6jihvqph23omz3uwdja7q4fq2i" + ] + }, + "signature": "X9vegej9T07LaA5wPtCj4WcV_vjy2KgkvKYTIT4IXoFvtZwrcUj6ABOG54LpWlXVto-Y09zIi2W3Miwzu10CAw" + } + }, + { + "name": "UCAN has a well-formed capability", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + {} + ] + } + } + }, + "signature": "mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + } + }, + { + "name": "UCAN has a well-formed capability with a caveat", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJtYXJrZXRpbmciXX1dfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.NWWHxBFHSW68IvbeB2utg5G67tSvEN9uHGxHOC5nzzoIdjpz39q5qI7CNuXlPLQDGvuUkZSjIAUzKtU3-HvaCg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + { + "templates": [ + "marketing" + ] + } + ] + } + } + }, + "signature": "NWWHxBFHSW68IvbeB2utg5G67tSvEN9uHGxHOC5nzzoIdjpz39q5qI7CNuXlPLQDGvuUkZSjIAUzKtU3-HvaCg" + } + }, + { + "name": "UCAN has multiple well-formed capabilities", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19LCJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbeyJ0ZW1wbGF0ZXMiOlsibWFya2V0aW5nIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f-xu84oUX45_2ncrkTjVX1zmJSBdsqrE21DxOUf-9eV3SjxRPmVpshE1bcTGqyjdxsaST0hdr3CXkBGKLD5wBw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + {} + ] + }, + "mailto:marketing@email.com": { + "email/send": [ + { + "templates": [ + "marketing" + ] + } + ] + } + } + }, + "signature": "f-xu84oUX45_2ncrkTjVX1zmJSBdsqrE21DxOUf-9eV3SjxRPmVpshE1bcTGqyjdxsaST0hdr3CXkBGKLD5wBw" + } + }, + { + "name": "UCAN issuer matches proof audience", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJwcmYiOlsiYmFma3JlaWJ2djQzenQ1ZGZ6d3I1ZWptczNvZWI1cmthenVmemt2cXM2djNpend3bGo2NGptcmNoY2kiXSwidWN2IjoiMC4xMC4wIn0.0BPu7MCETzLUwNqJdmw-D0CTQcXOrXaxJrRr-ONV0LG7e_P5ZkH6K8Et6k6lRp5JL7VhrnD2W1bT6lD2PbC_Cw", + "proofs": { + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": {}, + "prf": [ + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci" + ] + }, + "signature": "0BPu7MCETzLUwNqJdmw-D0CTQcXOrXaxJrRr-ONV0LG7e_P5ZkH6K8Et6k6lRp5JL7VhrnD2W1bT6lD2PbC_Cw" + } + }, + { + "name": "UCAN has a delegated capability", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInByZiI6WyJiYWZrcmVpYnZtaTc2NGNtdGFvNGsybWUybGtzYmY3NGRqcXBscWp3cWFsYWhkNHhmbnR5MzNycnBnbSJdLCJ1Y3YiOiIwLjEwLjAifQ.fwWnOgRSYryzvkvLyqYQZozrzKLIBfW4uGHKG6hR8Dygj1OOrDrcVXY88N7UQmj6O4ETXsrF99om5NK3QBB7Cw", + "proofs": { + "bafkreibvmi764cmtao4k2me2lksbf74djqplqjwqalahd4xfnty33rrpgm": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + {} + ] + } + }, + "prf": [ + "bafkreibvmi764cmtao4k2me2lksbf74djqplqjwqalahd4xfnty33rrpgm" + ] + }, + "signature": "fwWnOgRSYryzvkvLyqYQZozrzKLIBfW4uGHKG6hR8Dygj1OOrDrcVXY88N7UQmj6O4ETXsrF99om5NK3QBB7Cw" + } + }, + { + "name": "UCAN merges delegated capabilities", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19LCJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbe31dfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJwcmYiOlsiYmFma3JlaWJ2bWk3NjRjbXRhbzRrMm1lMmxrc2JmNzRkanFwbHFqd3FhbGFoZDR4Zm50eTMzcnJwZ20iLCJiYWZrcmVpYnNjaGNsbGRvdGVlbWM2enVkNHA1aXhoM2p5cWN0ZG9kNmJnMmdnZW8zb2drd3lyNHFubSJdLCJ1Y3YiOiIwLjEwLjAifQ.2C9kEs6nJmfabHn4iarDAfAbFQ70jwMlM_S76ky7O5ia8s9SYBpCDd9xEWu_9aHpLg34PnpTxOx8GqcWdm6CAA", + "proofs": { + "bafkreibvmi764cmtao4k2me2lksbf74djqplqjwqalahd4xfnty33rrpgm": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA", + "bafkreibschclldoteemc6zud4p5ixh3jyqctdod6bg2ggeo3ogkwyr4qnm": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbe31dfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.7KyzLK-9VeArJJaVwy3UMlR0I_u0J_Wpq0dQmVm45KWMEW8_pxFzLSUSKWIU-nFvcS4ehGLOOTEhuq4S8eTCDg" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + {} + ] + }, + "mailto:marketing@email.com": { + "email/send": [ + {} + ] + } + }, + "prf": [ + "bafkreibvmi764cmtao4k2me2lksbf74djqplqjwqalahd4xfnty33rrpgm", + "bafkreibschclldoteemc6zud4p5ixh3jyqctdod6bg2ggeo3ogkwyr4qnm" + ] + }, + "signature": "2C9kEs6nJmfabHn4iarDAfAbFQ70jwMlM_S76ky7O5ia8s9SYBpCDd9xEWu_9aHpLg34PnpTxOx8GqcWdm6CAA" + } + }, + { + "name": "UCAN capability caveats equal to proof caveats", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2ZmRFpDa0NUV3JlZzg4NjhmRzFGR0ZvZ2NKajVYNlBZOTNwUGNXRG45Ym9iIiwicHJmIjpbImJhZmtyZWloY2ZhcGE2bDMyd256dWthemxpb2FzcHR5MzY1dWh2ZXgzNm9zdGN3amNqN2JyZHZ1eWZxIl0sInVjdiI6IjAuMTAuMCJ9._kh7_uU71DHQBksna_eak-hOPjXfiKQsQgs7Uuv00VNe81qZj9bOcqHSlfVbnH3Gd7K7E86Kftvl-VYEn7NTDw", + "proofs": { + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f4QEc5oN43eUKxUfYuQ9zrjz3A6jiW6XPQCALv9RQ4QWnt4LvNy53gX3Z53lHc_-Ei8ykn4YUSGM3qL5AtdSBA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": { + "mailto:alice@email.com": { + "email/send": [ + { + "templates": [ + "newsletter" + ] + } + ] + } + }, + "prf": [ + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq" + ] + }, + "signature": "_kh7_uU71DHQBksna_eak-hOPjXfiKQsQgs7Uuv00VNe81qZj9bOcqHSlfVbnH3Gd7K7E86Kftvl-VYEn7NTDw" + } + }, + { + "name": "UCAN capability attenuates existing caveats", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbeyJ0ZW1wbGF0ZXMiOlsibmV3c2xldHRlciJdfV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInByZiI6WyJiYWZrcmVpZGJ6emRucGwyNHI3dHRsZnRrdGFwcGR2YTVubW5hNXdseHg1aXZxY2NvcmN5am82eGFxdSJdLCJ1Y3YiOiIwLjEwLjAifQ.l3qeyfVGZRpDCZRMVU9MT2NZxo-4f6sTmEjbGqsg5t8H57olEbMx5nAYFa1x5XBL1Mfo-fj_Ase5r7LppIUGCw", + "proofs": { + "bafkreidbzzdnpl24r7ttlftktappdva5nmna5wlxx5ivqccorcyjo6xaqu": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbeyJ0ZW1wbGF0ZXMiOlsibmV3c2xldHRlciIsIm1hcmtldGluZyJdfV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.I2bU54PhYvKbctQkBrE0YFi1M9bLacUT_Zz7w6QgJSaZ7I2O7F3I3EBr8T9J3BwqTyrVjJwe05mHmBg0GR-QAQ" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": { + "mailto:marketing@email.com": { + "email/send": [ + { + "templates": [ + "newsletter" + ] + } + ] + } + }, + "prf": [ + "bafkreidbzzdnpl24r7ttlftktappdva5nmna5wlxx5ivqccorcyjo6xaqu" + ] + }, + "signature": "l3qeyfVGZRpDCZRMVU9MT2NZxo-4f6sTmEjbGqsg5t8H57olEbMx5nAYFa1x5XBL1Mfo-fj_Ase5r7LppIUGCw" + } + }, + { + "name": "UCAN capability attenuates from no caveats", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbeyJ0ZW1wbGF0ZXMiOlsibmV3c2xldHRlciJdfV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInByZiI6WyJiYWZrcmVpYnNjaGNsbGRvdGVlbWM2enVkNHA1aXhoM2p5cWN0ZG9kNmJnMmdnZW8zb2drd3lyNHFubSJdLCJ1Y3YiOiIwLjEwLjAifQ.qEip9gJLndvsRXIhi0zx4cn73DxteX5J3cpTAX5-ufZHgcHQF76nPZzRUCtGEZ34xQHNcJVfUv4kWWuikwyNAg", + "proofs": { + "bafkreibschclldoteemc6zud4p5ixh3jyqctdod6bg2ggeo3ogkwyr4qnm": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86bWFya2V0aW5nQGVtYWlsLmNvbSI6eyJlbWFpbC9zZW5kIjpbe31dfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.7KyzLK-9VeArJJaVwy3UMlR0I_u0J_Wpq0dQmVm45KWMEW8_pxFzLSUSKWIU-nFvcS4ehGLOOTEhuq4S8eTCDg" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": { + "mailto:marketing@email.com": { + "email/send": [ + { + "templates": [ + "newsletter" + ] + } + ] + } + }, + "prf": [ + "bafkreibschclldoteemc6zud4p5ixh3jyqctdod6bg2ggeo3ogkwyr4qnm" + ] + }, + "signature": "qEip9gJLndvsRXIhi0zx4cn73DxteX5J3cpTAX5-ufZHgcHQF76nPZzRUCtGEZ34xQHNcJVfUv4kWWuikwyNAg" + } + }, + { + "name": "UCAN has a fact", + "task": "verify", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiZmN0Ijp7ImNoYWxsZW5nZSI6ImFiY2RlZiJ9LCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.uhk0MI1CyB3nEu4RxZqoOq3-BucWT86UXtP_th9ffa_uosmj6Aln3AUELkqJDsgr710UguNKQQzJVzmIdPoLCg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {}, + "fct": { + "challenge": "abcdef" + } + }, + "signature": "uhk0MI1CyB3nEu4RxZqoOq3-BucWT86UXtP_th9ffa_uosmj6Aln3AUELkqJDsgr710UguNKQQzJVzmIdPoLCg" + } + }, + { + "name": "UCAN has expired", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6MSwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.uVbv1qjgo20f5z4xsQkvxLFx4pEx60K4Ud-fyjfReE-NJNLwijhCMJiDgLHWc28zK9ml3Ooc4-naOmuipWXLBg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": 1, + "cap": {} + }, + "signature": "uVbv1qjgo20f5z4xsQkvxLFx4pEx60K4Ud-fyjfReE-NJNLwijhCMJiDgLHWc28zK9ml3Ooc4-naOmuipWXLBg" + }, + "errors": [ + "expired" + ] + }, + { + "name": "UCAN is not ready to be used", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjkyNDYyMTEyMDAsInVjdiI6IjAuMTAuMCJ9.KziqvLp9cWEJkRanhjVb2q-d1C-YdphKEd5TkAz3eO-XuisLD_PAvRnXplNFkh04uFaR4uwTY-G5fzYeXphsBQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "nbf": 9246211200, + "cap": {} + }, + "signature": "KziqvLp9cWEJkRanhjVb2q-d1C-YdphKEd5TkAz3eO-XuisLD_PAvRnXplNFkh04uFaR4uwTY-G5fzYeXphsBQ" + }, + "errors": [ + "notReady" + ] + }, + { + "name": "UCAN expires after proofs", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6MTQwNjkxNDIwMDAsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwicHJmIjpbImJhZmtyZWlmbXdzN3U1dzZubHVwcnh1NXpjc3VuMndjcDdyaW94ankycWVtNnBqajV6MzY3ZHA2NGxpIl0sInVjdiI6IjAuMTAuMCJ9.yZkK6eGFgZ9LiKkLb70BeVo0EW3_iLqB6sSER-fgKOu5lVBIoqUL21cENaiZDrfBT0Qwura0rJjkCNEjfnD9Bg", + "proofs": { + "bafkreifmws7u5w6nluprxu5zcsun2wcp7rioxjy2qem6pjj5z367dp64li": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": 14069142000, + "cap": {}, + "prf": [ + "bafkreifmws7u5w6nluprxu5zcsun2wcp7rioxjy2qem6pjj5z367dp64li" + ] + }, + "signature": "yZkK6eGFgZ9LiKkLb70BeVo0EW3_iLqB6sSER-fgKOu5lVBIoqUL21cENaiZDrfBT0Qwura0rJjkCNEjfnD9Bg" + }, + "errors": [ + "timeBoundsViolation" + ] + }, + { + "name": "UCAN ready before proofs", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjEsInByZiI6WyJiYWZrcmVpYWZtZTRxaDc2Nnl5azNtZ3Y0c3hwNjVlM2NkM29vNmVpeGI0dzd1enVsM3BlbGthaTJjNCJdLCJ1Y3YiOiIwLjEwLjAifQ.YOKAbtClCQJziz4Y0L_VuFa6WtnvQaNn4Ft3MmfF-PE1Asph1UgyMf8VKODZl9P-bN85J1ZQmEc9TZhN1qTTCg", + "proofs": { + "bafkreiafme4qh766yyk3mgv4sxp65e3cd3oo6eixb4w7uzul3pelkai2c4": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjIsInVjdiI6IjAuMTAuMCJ9.ejt__REc7NcLFre9mouOn6kszMjLgXvP2RFDObXpHyjtmFAKbQqwVPK3XYzMUPtTBLhw6XQPzazEGucXgBWEBg" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "nbf": 1, + "cap": {}, + "prf": [ + "bafkreiafme4qh766yyk3mgv4sxp65e3cd3oo6eixb4w7uzul3pelkai2c4" + ] + }, + "signature": "YOKAbtClCQJziz4Y0L_VuFa6WtnvQaNn4Ft3MmfF-PE1Asph1UgyMf8VKODZl9P-bN85J1ZQmEc9TZhN1qTTCg" + }, + "errors": [ + "timeBoundsViolation" + ] + }, + { + "name": "UCAN header is missing typ field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.mFFFVP-hfpI16xLV657cFPbmHHCy-LRuXaLaCr0c07o5gi9DLMs0RS54ZOWwNcCVLPwp1howg_aa4tUk9_DuBw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN header is missing alg field", + "task": "refute", + "inputs": { + "token": "eyJ0eXAiOiJKV1QifQ.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.yqNQQoNTHCixB8bjivcgnjBEnO14ILoH_H2lQdzt8sSYNnvMikdhS0unT1oBwY7-n2SAAxpVxIpDd1rFXh-zAg", + "proofs": {} + }, + "assertions": { + "header": { + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN payload is missing ucv field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkifQ.laZ9fqz-DhR_sbVS3S6hxRCgHU9NeWsXmv4ytxqyAgy86nmSy058q45seKfNF1FpXMt-0BsJ59GD5Uo9hLthBw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN payload is missing iss field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwidWN2IjoiMC4xMC4wIn0.b-g9tAwf78671zutr1CQgXlP3aP-2E2HjVbcJeYxAlp0V0qUWUCYErhhvH62NBfBKBO8NHHz1aml6T7ATFiyCQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN payload is missing aud field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ1Y3YiOiIwLjEwLjAiLCJjYXAiOnt9LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIn0.mvBd9TGdEB3p-EIcaXSSqCCCzAg7_8TL5axffLQn2VoAcn0nlHjkT-VwQzdwh5lp5Pt47BplQNTMkhzPw5aZBQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN payload is missing exp field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sInVjdiI6IjAuMTAuMCIsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIn0.YwUzs6T1Rir3bc6MmNlve6yGPWaXZZ4lntabiNmtAN6uY0reiSakxvqQEozFzpbKpECZvIOYagjVCaMwKQATDw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN payload is missing cap field", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInVjdiI6IjAuMTAuMCIsImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkifQ.22qwsTJvEqHCG1X1Gw30piphYmQhYYkih6gxh3M2qzvAXItGdymAsbBY7YY3I8lld7Tx6ZzTRh4shj63Y9LDCQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "missingField" + ] + }, + { + "name": "UCAN header alg field is not a string", + "task": "refute", + "inputs": { + "token": "eyJhbGciOjEsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.bDkVEY5PgTeC_6AY6i64EWHFin-NHxR4eynaUxpo9ThUZmf47G5yhruDB6XLTY389WjM39oDS5Bkh1_oFWlzAg", + "proofs": {} + }, + "assertions": { + "header": { + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN header typ field is not a string", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6MX0.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.1AobwbdMFtPhaqspFW-i7LirzgGrU4su9WZ4EBx-Hy7MuwZmjrpIj1O3fVj4zpJRXpFnOYwsZVu9lqLIeyDYAw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN type is not JWT", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6Ik5PVF9KV1QifQ.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.pk-UHwHy89YbLWtgSveyDAY8GNP519F8oRR3s-GuW1cFNgMOYClTwP-7Olq09daUFmQ09myAO4cLLAvcJcvEBw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload ucv field is not a string", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOjF9.i83bMf3ZTiEntyQgJzlBU0kiAkTHT3VR6uRSY45UITM6VrMz5H94jYKxlolGM53iL4WdfZ0dThdFUvOyepzpBQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload ucv field is not semantically versioned", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwIn0.aDj_GdR1OtwXoeWqt-5n0kmABvk7vqUZJR3qhT9IPljjaEmNATQcoDLxzTH2fe-oiQcFAxq8mX5XtpZ4OlpPDw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload iss field is not a DID", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.aZ8qyiXbEMX4GLsTgtJ8RBJHTGAmMz3elIg48SAVY4r48OZJmtW3JS9LE8boY2azWrksCVs32EehaVQBoIw3DQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload aud field is not a DID", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJjYXAiOnt9LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.jq4S2p5NEwxdB6eHYoVVAeSzcduZip20m8A8M3qKORFZPRSXT2RDxo6SuzTktm_gBMxqpG3_RzOOzTZzywQlAg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "exp": null, + "cap": {} + }, + "signature": "MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload nbf field is not a number", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOiIxIiwidWN2IjoiMC4xMC4wIn0.wNsH1CpmiZjJhfIlFZyxHoH8Xobf-_e0CRr3jzr2kECmICn8sWClr_zu5j2iR0ILj--Bj3k4GsLQTmQtQOx5Ag", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "-yaM1x8v4jIvi5ldLsjN3unAJiaFx2D1gl4z_Ct8OCcS_afEW-q8phwyOVu3DKFP8dGoEvlMQMhTfPsiUOCsAQ" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload exp field is not a number", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6IjkyNDYyMTEyMDAiLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.4_TiWe594ggaM3PDNADAZsfScA7z9ZpwnEDJ4a-x20JZ6qg8gabsVqK7d1O9Zje9TgQuqA4By0o3jwaTpgLEBA", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "cap": {} + }, + "signature": "pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload nnc field is not a string", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJubmMiOjEsInVjdiI6IjAuMTAuMCJ9.7sEB7EzEDslqFjufVeNih0oBquhC_BvYNrelnk1bfSfZ6Qrg4KvP4MiwsDzghMejwvXIm-ujXMFfvKWUoC3NCw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "52Y5NhPBJLzFcqncGX5pFDsfe2yG1PPnE-tvtF795JGTkvxgQnwI9Sec1z9sk71OND6CP-HIfYnVDmhe6uaJAw" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload fct field is not a JSON object", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiZmN0IjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.xHfMIVdVRnJfw5dePQgPLEb8uWAo8QrKufd_JsPsYD0RwXV4HEuC-x46NfZxNo9sQwFhuKBT-6xmNgV2CZODBA", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "uhk0MI1CyB3nEu4RxZqoOq3-BucWT86UXtP_th9ffa_uosmj6Aln3AUELkqJDsgr710UguNKQQzJVzmIdPoLCg" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload cap field is not a JSON object", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6bnVsbCwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.YRrWLF6guHy1BgFTAgTWNA1naDGdsbpbP3Y9RwuC1nHOPephImKtObLDqxg5cSJZindU0YsgYviszuJ7H-TODQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null + }, + "signature": "mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload cap field ability for resource is not a JSON object", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjpudWxsfSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.Q4zXZ0HHWCBFa5CpgQLYK42FI5-aSdjiGNSGAmy9t4Tsbnbs3yv4mj4u9TJJR6ZrCOEmhq3Z-th6aVN_GOvADg", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null + }, + "signature": "mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload cap field caveat is not an array", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOm51bGx9fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.YQrHUn5KXeZd5TcI7n1xjMueErLV5M3CIOLVYybgKlCrgHWOanQclif63cLgFCUSKws7XpmzZfdqSQtUDphSDA", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null + }, + "signature": "mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload cap field caveat is an empty array", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOltdfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.kld4oDIgz6sSf3H7iP3Yi_xKfdzjx6MFBpW4IWEge2_1hXCac2hsfJ094kNkZD8FZ7TuF_LPETpb-dgSlqowBw", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null + }, + "signature": "mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload prf field is not an array", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJwcmYiOnt9LCJ1Y3YiOiIwLjEwLjAifQ.Jeqh3JbtKDug0GgHyc7B6BvpQK4uu96V-FCbO549c3_RDqlK-V44xC4SJ0KLjiRmIDwBu7nLVFWdwItrmQObCA", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "uG2KDLAkshoiEV_vd1k4XTiI3j3xlMF0KWh6Upsxyve5SCLNnSk7AeVYcgjoKqI1TQoBUVRugVBEjhW1eIHVCw" + }, + "errors": [ + "incorrectType" + ] + }, + { + "name": "UCAN payload prf field is not an array of CIDs", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJwcmYiOlsid2UiLCJwcm92ZSIsIm5vdGhpbmciXSwidWN2IjoiMC4xMC4wIn0.rmsYqCZqa4ugeJz0pYDfI1ZqHIvRYHygL-kj-4SZUyPxMffAcU_WK4txhrEPgXnrtsGJsDYH83qoxN1Zs2bAAQ", + "proofs": {} + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "exp": null, + "cap": {} + }, + "signature": "uG2KDLAkshoiEV_vd1k4XTiI3j3xlMF0KWh6Upsxyve5SCLNnSk7AeVYcgjoKqI1TQoBUVRugVBEjhW1eIHVCw" + }, + "errors": [ + "incorrectProofs" + ] + }, + { + "name": "UCAN issuer does not match proof audience", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1rdGFmWlRSRWpKa3ZWNW1mSnhjTHBOQm9WUHdETGhUdU1nOW5nN2RZNHpNQUwiLCJwcmYiOlsiYmFma3JlaWJ2djQzenQ1ZGZ6d3I1ZWptczNvZWI1cmthenVmemt2cXM2djNpend3bGo2NGptcmNoY2kiXSwidWN2IjoiMC4xMC4wIn0.x4AuOHBAlXipWtkYdjwdp_u6uOUlBc_sQHYN76bwXqfOFxc3XiKQQDvk-Gi9GsqZbAo86u6NAXJUrDWHuIkeCw", + "proofs": { + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "cap": {}, + "prf": [ + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci" + ] + }, + "signature": "0BPu7MCETzLUwNqJdmw-D0CTQcXOrXaxJrRr-ONV0LG7e_P5ZkH6K8Et6k6lRp5JL7VhrnD2W1bT6lD2PbC_Cw" + }, + "errors": [ + "invalidDelegation" + ] + }, + { + "name": "UCAN claims a capability that has not been delegated", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInByZiI6WyJiYWZrcmVpYnZ2NDN6dDVkZnp3cjVlam1zM29lYjVya2F6dWZ6a3ZxczZ2M2l6d3dsajY0am1yY2hjaSJdLCJ1Y3YiOiIwLjEwLjAifQ.EdZXPSt8GxcmQu2_5IUVi9XZ5x2-bT_7AaCbGJTZ2q_X_5_9jjE_vd8MhaxnL7RfMIoHUgzVb6JYEZvlow8JDw", + "proofs": { + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "prf": [ + "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci" + ] + }, + "signature": "EdZXPSt8GxcmQu2_5IUVi9XZ5x2-bT_7AaCbGJTZ2q_X_5_9jjE_vd8MhaxnL7RfMIoHUgzVb6JYEZvlow8JDw" + }, + "errors": [ + "invalidDelegation" + ] + }, + { + "name": "UCAN escalates by adding a new caveat", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIiwibWFya2V0aW5nIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2ZmRFpDa0NUV3JlZzg4NjhmRzFGR0ZvZ2NKajVYNlBZOTNwUGNXRG45Ym9iIiwicHJmIjpbImJhZmtyZWloY2ZhcGE2bDMyd256dWthemxpb2FzcHR5MzY1dWh2ZXgzNm9zdGN3amNqN2JyZHZ1eWZxIl0sInVjdiI6IjAuMTAuMCJ9.AQmbtPT0n4SGlRaSY7QFH1kIDl3qglPbcSCtb5AaWpDYqvIhHtIzSvwxKfbrVR2pPs-I5oD2iuhcHivD6OpxCw", + "proofs": { + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f4QEc5oN43eUKxUfYuQ9zrjz3A6jiW6XPQCALv9RQ4QWnt4LvNy53gX3Z53lHc_-Ei8ykn4YUSGM3qL5AtdSBA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "prf": [ + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq" + ] + }, + "signature": "AQmbtPT0n4SGlRaSY7QFH1kIDl3qglPbcSCtb5AaWpDYqvIhHtIzSvwxKfbrVR2pPs-I5oD2iuhcHivD6OpxCw" + }, + "errors": [ + "invalidDelegation" + ] + }, + { + "name": "UCAN escalates to no caveats", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsInByZiI6WyJiYWZrcmVpaGNmYXBhNmwzMnduenVrYXpsaW9hc3B0eTM2NXVodmV4MzZvc3Rjd2pjajdicmR2dXlmcSJdLCJ1Y3YiOiIwLjEwLjAifQ.WwdZ21MV5RW2h_-ROJUVAM2EyeEgtc1KSLNkFUS9Vi2ieeDSImt3TuQ920rsHoE4k7FTiWJ7xoLlXAPSLs2SCQ", + "proofs": { + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f4QEc5oN43eUKxUfYuQ9zrjz3A6jiW6XPQCALv9RQ4QWnt4LvNy53gX3Z53lHc_-Ei8ykn4YUSGM3qL5AtdSBA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "prf": [ + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq" + ] + }, + "signature": "WwdZ21MV5RW2h_-ROJUVAM2EyeEgtc1KSLNkFUS9Vi2ieeDSImt3TuQ920rsHoE4k7FTiWJ7xoLlXAPSLs2SCQ" + }, + "errors": [ + "invalidDelegation" + ] + }, + { + "name": "UCAN escalates by adding a different caveat", + "task": "refute", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWt0YWZaVFJFakprdlY1bWZKeGNMcE5Cb1ZQd0RMaFR1TWc5bmc3ZFk0ek1BTCIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJtYXJrZXRpbmciXX1dfX0sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1rZmZEWkNrQ1RXcmVnODg2OGZHMUZHRm9nY0pqNVg2UFk5M3BQY1dEbjlib2IiLCJwcmYiOlsiYmFma3JlaWhjZmFwYTZsMzJ3bnp1a2F6bGlvYXNwdHkzNjV1aHZleDM2b3N0Y3dqY2o3YnJkdnV5ZnEiXSwidWN2IjoiMC4xMC4wIn0.b1xIr3VnJFcEqljPB4mTG5poRLR6JiPiY_h_Lk-nxzyaEk_JBGmZpj7_imeCHbfyXrnlDBXjRBZ3zKT4y3VlDQ", + "proofs": { + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f4QEc5oN43eUKxUfYuQ9zrjz3A6jiW6XPQCALv9RQ4QWnt4LvNy53gX3Z53lHc_-Ei8ykn4YUSGM3qL5AtdSBA" + } + }, + "assertions": { + "header": { + "alg": "EdDSA", + "typ": "JWT" + }, + "payload": { + "ucv": "0.10.0", + "iss": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "aud": "did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL", + "exp": null, + "prf": [ + "bafkreihcfapa6l32wnzukazlioaspty365uhvex36ostcwjcj7brdvuyfq" + ] + }, + "signature": "b1xIr3VnJFcEqljPB4mTG5poRLR6JiPiY_h_Lk-nxzyaEk_JBGmZpj7_imeCHbfyXrnlDBXjRBZ3zKT4y3VlDQ" + }, + "errors": [ + "invalidDelegation" + ] + }, + { + "name": "UCAN has an expiration", + "task": "build", + "inputs": { + "version": "0.10.0", + "issuer_base64_key": "U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==", + "signature_scheme": "Ed25519", + "audience": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "expiration": 9246211200, + "capabilities": {} + }, + "outputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg" + } + }, + { + "name": "UCAN has a not before", + "task": "build", + "inputs": { + "version": "0.10.0", + "issuer_base64_key": "U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==", + "signature_scheme": "Ed25519", + "audience": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "not_before": 1, + "expiration": null, + "capabilities": {} + }, + "outputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJuYmYiOjEsInVjdiI6IjAuMTAuMCJ9.-yaM1x8v4jIvi5ldLsjN3unAJiaFx2D1gl4z_Ct8OCcS_afEW-q8phwyOVu3DKFP8dGoEvlMQMhTfPsiUOCsAQ" + } + }, + { + "name": "UCAN delegates send email capability", + "task": "build", + "inputs": { + "version": "0.10.0", + "issuer_base64_key": "U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==", + "signature_scheme": "Ed25519", + "audience": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "expiration": null, + "capabilities": { + "mailto:alice@email.com": { + "email/send": [ + {} + ] + } + } + }, + "outputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7fV19fSwiZXhwIjpudWxsLCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.mWmgl4OyAl_OKCB0tYBaxbw_0MkR2jM0W_G6eH8OW39IuB9y9ArbBcCSnG7r0WdeZaJBh6Qf4MxLiuSKM3ZFCA" + } + }, + { + "name": "UCAN delegates send email capability with newsletter template caveat", + "task": "build", + "inputs": { + "version": "0.10.0", + "issuer_base64_key": "U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==", + "signature_scheme": "Ed25519", + "audience": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "expiration": null, + "capabilities": { + "mailto:alice@email.com": { + "email/send": [ + { + "templates": [ + "newsletter" + ] + } + ] + } + } + }, + "outputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6eyJtYWlsdG86YWxpY2VAZW1haWwuY29tIjp7ImVtYWlsL3NlbmQiOlt7InRlbXBsYXRlcyI6WyJuZXdzbGV0dGVyIl19XX19LCJleHAiOm51bGwsImlzcyI6ImRpZDprZXk6ejZNa2s4OWJDM0pyVnFLaWU3MVlFY2M1TTFTTVZ4dUNnTng2ekxaOFNZSnN4QUxpIiwidWN2IjoiMC4xMC4wIn0.f4QEc5oN43eUKxUfYuQ9zrjz3A6jiW6XPQCALv9RQ4QWnt4LvNy53gX3Z53lHc_-Ei8ykn4YUSGM3qL5AtdSBA" + } + }, + { + "name": "UCAN has a fact with a challenge", + "task": "build", + "inputs": { + "version": "0.10.0", + "issuer_base64_key": "U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==", + "signature_scheme": "Ed25519", + "audience": "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob", + "expiration": null, + "facts": { + "challenge": "abcdef" + }, + "capabilities": {} + }, + "outputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiZmN0Ijp7ImNoYWxsZW5nZSI6ImFiY2RlZiJ9LCJpc3MiOiJkaWQ6a2V5Ono2TWtrODliQzNKclZxS2llNzFZRWNjNU0xU01WeHVDZ054NnpMWjhTWUpzeEFMaSIsInVjdiI6IjAuMTAuMCJ9.uhk0MI1CyB3nEu4RxZqoOq3-BucWT86UXtP_th9ffa_uosmj6Aln3AUELkqJDsgr710UguNKQQzJVzmIdPoLCg" + } + }, + { + "name": "Compute CID for token using SHA2-256 hasher", + "task": "toCID", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA", + "hasher": "SHA2-256" + }, + "outputs": { + "cid": "bafkreibvv43zt5dfzwr5ejms3oeb5rkazufzkvqs6v3izwwlj64jmrchci" + } + }, + { + "name": "Compute CID for token using BLAKE3-256 hasher", + "task": "toCID", + "inputs": { + "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6bnVsbCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.MkBYb77b19pn8fCODCMYpqNTs5_neWBsNHKL73U68S1w3sj0RCllCoHq-Ih-rrFsNvNWSSyOQN3ZC_nN966BAA", + "hasher": "BLAKE3-256" + }, + "outputs": { + "cid": "bafkr4icyq2ikdjxba7bytuxjjgxfhouzdbwutrqio77cb5logrg7osnsli" + } + } +] From 5133dcc5b7f7b06411cbac2612f9dd1f56a9963a Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Thu, 28 Sep 2023 13:52:13 -0700 Subject: [PATCH 020/234] test: improve conformance test error messages --- tests/conformance.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/conformance.rs b/tests/conformance.rs index ce467bc6..fe15adb0 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -167,7 +167,7 @@ impl TestTask for VerifyTest { did_verifier_map.register(did_key_verifier); let Ok(ucan) = Ucan::from_str(&self.inputs.token) else { - report.register_failure(name, "Failed to parse token".to_string()); + report.register_failure(name, "failed to parse token".to_string()); return; }; @@ -177,7 +177,7 @@ impl TestTask for VerifyTest { report.register_failure( name, format!( - "Expected algorithm to be {}, but was {}", + "expected algorithm to be {}, but was {}", alg, ucan.algorithm() ), @@ -191,7 +191,7 @@ impl TestTask for VerifyTest { if ucan.typ() != typ { report.register_failure( name, - format!("Expected type to be {}, but was {}", typ, ucan.typ()), + format!("expected type to be {}, but was {}", typ, ucan.typ()), ); return; @@ -202,7 +202,7 @@ impl TestTask for VerifyTest { if ucan.version() != ucv { report.register_failure( name, - format!("Expected version to be {}, but was {}", ucv, ucan.version()), + format!("expected version to be {}, but was {}", ucv, ucan.version()), ); return; @@ -213,7 +213,7 @@ impl TestTask for VerifyTest { if ucan.issuer() != iss { report.register_failure( name, - format!("Expected issuer to be {}, but was {}", iss, ucan.issuer()), + format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), ); return; @@ -225,7 +225,7 @@ impl TestTask for VerifyTest { report.register_failure( name, format!( - "Expected audience to be {}, but was {}", + "expected audience to be {}, but was {}", aud, ucan.audience() ), @@ -239,7 +239,7 @@ impl TestTask for VerifyTest { report.register_failure( name, format!( - "Expected expiration to be {:?}, but was {:?}", + "expected expiration to be {:?}, but was {:?}", self.assertions.payload.exp, ucan.expires_at() ), @@ -252,7 +252,7 @@ impl TestTask for VerifyTest { report.register_failure( name, format!( - "Expected proofs to be {:?}, but was {:?}", + "expected proofs to be {:?}, but was {:?}", self.assertions.payload.prf, ucan.proofs().cloned() ), @@ -262,13 +262,13 @@ impl TestTask for VerifyTest { } let Ok(signature) = serde_json::to_value(ucan.signature()) else { - report.register_failure(name, "Failed to serialize signature".to_string()); + report.register_failure(name, "failed to serialize signature".to_string()); return; }; let Some(signature) = signature.as_str() else { - report.register_failure(name, "Expected signature to be a string".to_string()); + report.register_failure(name, "expected signature to be a string".to_string()); return; }; @@ -277,7 +277,7 @@ impl TestTask for VerifyTest { report.register_failure( name, format!( - "Expected signature to be {}, but was {}", + "expected signature to be {}, but was {}", self.assertions.signature, signature ), ); @@ -308,7 +308,7 @@ impl TestTask for RefuteTest { { report.register_failure( &name, - "Expected token to fail validation, but it passed".to_string(), + "expected token to fail validation, but it passed".to_string(), ); return; @@ -333,7 +333,7 @@ impl TestTask for ToCidTest { }; let Ok(cid) = ucan.to_cid(hasher) else { - report.register_failure(&name, "Failed to convert to CID".to_string()); + report.register_failure(&name, "failed to convert to CID".to_string()); return; }; @@ -342,7 +342,7 @@ impl TestTask for ToCidTest { report.register_failure( &name, format!( - "Expected CID to be {}, but was {}", + "expected CID to be {}, but was {}", self.outputs.cid, cid.to_string() ), From f984afb929a439ef1d3978bdfe58e7da495b0663 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 4 Oct 2023 12:13:37 -0700 Subject: [PATCH 021/234] refactor: parse proofs as CIDs This also generally cleans up the trait bounds, and removes the unused Fact trait --- src/builder.rs | 12 +++++----- src/capability.rs | 2 +- src/lib.rs | 38 +++++++++++++++++++++++++++++++ src/semantics/fact.rs | 25 -------------------- src/semantics/mod.rs | 1 - src/ucan.rs | 53 ++++++++++++++++++++++++++++++++----------- tests/conformance.rs | 19 ++++++++++++---- 7 files changed, 99 insertions(+), 51 deletions(-) delete mode 100644 src/semantics/fact.rs diff --git a/src/builder.rs b/src/builder.rs index 7f32288b..57240779 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,15 +1,15 @@ //! A builder for creating UCANs use cid::multihash; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use signature::Signer; use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, crypto::JWSSignature, error::Error, - semantics::fact::DefaultFact, ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, + CidString, DefaultFact, }; /// The default multihash algorithm used for UCANs @@ -27,7 +27,7 @@ pub struct UcanBuilder { expiration: Option, not_before: Option, facts: Option, - proofs: Option>, + proofs: Option>, } impl Default for UcanBuilder { @@ -49,7 +49,7 @@ impl Default for UcanBuilder { impl UcanBuilder where - F: Serialize, + F: Clone + Serialize, C: CapabilityParser, { /// Set the UCAN version @@ -107,7 +107,7 @@ where hasher: Option, ) -> Self where - F2: Serialize, + F2: Clone + DeserializeOwned, C2: CapabilityParser, { let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); @@ -116,7 +116,7 @@ where Ok(cid) => { self.proofs .get_or_insert(Default::default()) - .push(cid.to_string()); + .push(CidString(cid)); } Err(e) => panic!("Failed to add authority: {}", e), } diff --git a/src/capability.rs b/src/capability.rs index 50623434..90dab77e 100644 --- a/src/capability.rs +++ b/src/capability.rs @@ -101,7 +101,7 @@ impl Capabilities { } /// Handles deserializing capabilities -pub trait CapabilityParser { +pub trait CapabilityParser: Clone { /// Tries to deserialize a capability from a resource_uri, ability, and a deserilizer for the caveat fn try_handle( resource_uri: &Url, diff --git a/src/lib.rs b/src/lib.rs index 6cf959b3..886b08e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,11 @@ //! rs-ucan +use std::str::FromStr; + +use cid::Cid; +use serde::{de, Deserialize, Deserializer, Serialize}; + pub mod builder; pub mod capability; pub mod crypto; @@ -17,6 +22,39 @@ pub mod ucan; /// A decentralized identifier. pub type Did = String; +/// The empty fact +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct EmptyFact {} + +/// The default fact +pub type DefaultFact = EmptyFact; + +/// A newtype around Cid that (de)serializes as a string +#[derive(Debug, Clone)] +pub struct CidString(pub(crate) Cid); + +impl Serialize for CidString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.0.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for CidString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + + Cid::from_str(&s) + .map(CidString) + .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) + } +} + /// Test utilities. #[cfg(any(test, feature = "test_utils"))] #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] diff --git a/src/semantics/fact.rs b/src/semantics/fact.rs deleted file mode 100644 index eb9841ff..00000000 --- a/src/semantics/fact.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! UCAN Facts - -use std::{any::Any, fmt}; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; -use serde::{Deserialize, Serialize}; - -/// A fact defined as part of a semantics -pub trait Fact: DynClone + Downcast + 'static {} - -clone_trait_object!(Fact); -impl_downcast!(Fact); - -impl Fact for T where T: Any + Clone {} - -impl fmt::Debug for dyn Fact { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Fact({})", std::any::type_name::()) - } -} - -/// The empty fact -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DefaultFact {} diff --git a/src/semantics/mod.rs b/src/semantics/mod.rs index c6c9fa33..1d88f3b0 100644 --- a/src/semantics/mod.rs +++ b/src/semantics/mod.rs @@ -2,5 +2,4 @@ pub mod ability; pub mod caveat; -pub mod fact; pub mod resource; diff --git a/src/ucan.rs b/src/ucan.rs index 93aa3b9c..a1b7bc3b 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -6,15 +6,15 @@ use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, did_verifier::DidVerifierMap, error::Error, - semantics::{ability::Ability, fact::DefaultFact, resource::Resource}, - time, + semantics::{ability::Ability, resource::Resource}, + time, CidString, DefaultFact, }; use cid::{ multihash::{self, MultihashDigest}, Cid, }; use semver::Version; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; /// The current UCAN version pub const UCAN_VERSION: &str = "0.10.0"; @@ -44,7 +44,7 @@ pub struct UcanPayload { #[serde(skip_serializing_if = "Option::is_none")] pub(crate) fct: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) prf: Option>, + pub(crate) prf: Option>, } /// A UCAN @@ -57,7 +57,7 @@ pub struct Ucan { impl Ucan where - F: Serialize, + F: Clone + DeserializeOwned, C: CapabilityParser, { /// Validate the UCAN's signature and timestamps @@ -187,7 +187,11 @@ where /// Note that if a UCAN specifies an NBF but the other does not, the /// other has an unbounded start time and this function will return /// false. - pub fn lifetime_begins_before(&self, other: &Ucan) -> bool { + pub fn lifetime_begins_before(&self, other: &Ucan) -> bool + where + F2: DeserializeOwned, + C2: CapabilityParser, + { match (self.payload.nbf, other.payload.nbf) { (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, (Some(_), None) => false, @@ -196,7 +200,11 @@ where } /// Returns true if this UCAN expires no earlier than the other - pub fn lifetime_ends_after(&self, other: &Ucan) -> bool { + pub fn lifetime_ends_after(&self, other: &Ucan) -> bool + where + F2: DeserializeOwned, + C2: CapabilityParser, + { match (self.payload.exp, other.payload.exp) { (Some(exp), Some(other_exp)) => exp >= other_exp, (Some(_), None) => false, @@ -205,7 +213,11 @@ where } /// Returns true if this UCAN's lifetime fully encompasses the other - pub fn lifetime_encompasses(&self, other: &Ucan) -> bool { + pub fn lifetime_encompasses(&self, other: &Ucan) -> bool + where + F2: DeserializeOwned, + C2: CapabilityParser, + { self.lifetime_begins_before(other) && self.lifetime_ends_after(other) } @@ -230,8 +242,11 @@ where } /// Return the `prf` field of the UCAN payload - pub fn proofs(&self) -> Option<&Vec> { - self.payload.prf.as_ref() + pub fn proofs(&self) -> Option> { + self.payload + .prf + .as_ref() + .map(|f| f.iter().map(|c| &c.0).collect()) } /// Return the `exp` field of the UCAN payload @@ -301,7 +316,11 @@ where } } -impl<'a> TryFrom<&'a str> for Ucan { +impl<'a, F, C> TryFrom<&'a str> for Ucan +where + F: DeserializeOwned, + C: CapabilityParser, +{ type Error = Error; fn try_from(ucan_token: &str) -> Result { @@ -309,7 +328,11 @@ impl<'a> TryFrom<&'a str> for Ucan { } } -impl TryFrom for Ucan { +impl TryFrom for Ucan +where + F: DeserializeOwned, + C: CapabilityParser, +{ type Error = Error; fn try_from(ucan_token: String) -> Result { @@ -317,7 +340,11 @@ impl TryFrom for Ucan { } } -impl FromStr for Ucan { +impl FromStr for Ucan +where + F: DeserializeOwned, + C: CapabilityParser, +{ type Err = Error; fn from_str(ucan_token: &str) -> Result { diff --git a/tests/conformance.rs b/tests/conformance.rs index fe15adb0..80ed0ddc 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; use rs_ucan::{ + capability::DefaultCapabilityParser, crypto::eddsa::ed25519_dalek_verifier, did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, ucan::Ucan, + DefaultFact, }; trait TestTask { @@ -166,7 +168,8 @@ impl TestTask for VerifyTest { did_key_verifier.set::(ed25519_dalek_verifier); did_verifier_map.register(did_key_verifier); - let Ok(ucan) = Ucan::from_str(&self.inputs.token) else { + let Ok(ucan) = Ucan::::from_str(&self.inputs.token) + else { report.register_failure(name, "failed to parse token".to_string()); return; @@ -248,13 +251,17 @@ impl TestTask for VerifyTest { return; } - if ucan.proofs().cloned() != self.assertions.payload.prf { + if ucan + .proofs() + .map(|f| f.iter().map(|c| c.to_string()).collect()) + != self.assertions.payload.prf + { report.register_failure( name, format!( "expected proofs to be {:?}, but was {:?}", self.assertions.payload.prf, - ucan.proofs().cloned() + ucan.proofs() ), ); @@ -301,7 +308,8 @@ impl TestTask for RefuteTest { let mut did_verifier_map = DidVerifierMap::default(); did_verifier_map.register(did_key_verifier); - if let Ok(ucan) = Ucan::from_str(&self.inputs.token) { + if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) + { if ucan .validate(Some(rs_ucan::time::now()), &did_verifier_map) .is_ok() @@ -325,7 +333,8 @@ impl TestTask for BuildTest { impl TestTask for ToCidTest { fn run(&self, name: &str, report: &mut TestReport) { - let ucan = Ucan::from_str(&self.inputs.token).unwrap(); + let ucan = + Ucan::::from_str(&self.inputs.token).unwrap(); let hasher = match self.inputs.hasher.as_str() { "SHA2-256" => multihash::Code::Sha2_256, "BLAKE3-256" => multihash::Code::Blake3_256, From 7ab91dde8103b640997a18346eedc6464ee67f0e Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 4 Oct 2023 12:18:41 -0700 Subject: [PATCH 022/234] refactor: require specifying the time to validate at --- src/ucan.rs | 20 ++++++++------------ tests/conformance.rs | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/ucan.rs b/src/ucan.rs index a1b7bc3b..c0ef11c2 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -7,7 +7,7 @@ use crate::{ did_verifier::DidVerifierMap, error::Error, semantics::{ability::Ability, resource::Resource}, - time, CidString, DefaultFact, + CidString, DefaultFact, }; use cid::{ multihash::{self, MultihashDigest}, @@ -61,11 +61,7 @@ where C: CapabilityParser, { /// Validate the UCAN's signature and timestamps - pub fn validate( - &self, - now_time: Option, - did_verifier_map: &DidVerifierMap, - ) -> Result<(), Error> { + pub fn validate(&self, at_time: u64, did_verifier_map: &DidVerifierMap) -> Result<(), Error> { if self.typ() != "JWT" { return Err(Error::VerifyingError { msg: format!("expected header typ field to be 'JWT', got {}", self.typ()), @@ -81,13 +77,13 @@ where }); } - if self.is_expired(now_time) { + if self.is_expired(at_time) { return Err(Error::VerifyingError { msg: "token is expired".to_string(), }); } - if self.is_too_early() { + if self.is_too_early(at_time) { return Err(Error::VerifyingError { msg: "current time is before token validity period begins".to_string(), }); @@ -162,9 +158,9 @@ where } /// Returns true if the UCAN has past its expiration date - pub fn is_expired(&self, now_time: Option) -> bool { + pub fn is_expired(&self, at_time: u64) -> bool { if let Some(exp) = self.payload.exp { - exp < now_time.unwrap_or_else(time::now) + exp < at_time } else { false } @@ -176,9 +172,9 @@ where } /// Returns true if the not-before ("nbf") time is still in the future - pub fn is_too_early(&self) -> bool { + pub fn is_too_early(&self, at_time: u64) -> bool { match self.payload.nbf { - Some(nbf) => nbf > time::now(), + Some(nbf) => nbf > at_time, None => false, } } diff --git a/tests/conformance.rs b/tests/conformance.rs index 80ed0ddc..623c0942 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -292,7 +292,7 @@ impl TestTask for VerifyTest { return; } - if let Err(err) = ucan.validate(Some(rs_ucan::time::now()), &did_verifier_map) { + if let Err(err) = ucan.validate(rs_ucan::time::now(), &did_verifier_map) { report.register_failure(name, err.to_string()); return; @@ -311,7 +311,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan - .validate(Some(rs_ucan::time::now()), &did_verifier_map) + .validate(rs_ucan::time::now(), &did_verifier_map) .is_ok() { report.register_failure( From f16f1a33e980224c6e747d9e5aa4283aa6b4d347 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 4 Oct 2023 12:22:29 -0700 Subject: [PATCH 023/234] feat: add Store and AsyncStore trait with in-memory implementation --- Cargo.toml | 2 ++ src/lib.rs | 1 + src/store.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++ tests/conformance.rs | 23 ++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 src/store.rs diff --git a/Cargo.toml b/Cargo.toml index d5d87edc..10e0f053 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +async-trait = "0.1.73" blst = "0.3.11" cid = "0.10.1" downcast-rs = "1.2.0" @@ -40,6 +41,7 @@ instant = "0.1.12" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = "0.13.1" lazy_static = "1.4.0" +libipld-core = "0.16.0" linkme = "0.3.15" multibase = "0.9.1" p256 = "0.13.2" diff --git a/src/lib.rs b/src/lib.rs index 886b08e7..fe84f447 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub mod did_verifier; pub mod error; pub mod plugins; pub mod semantics; +pub mod store; pub mod time; pub mod ucan; diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 00000000..aab9eff7 --- /dev/null +++ b/src/store.rs @@ -0,0 +1,85 @@ +//! A store for persisting UCAN tokens, to be referencable as proofs by other UCANs + +use std::{collections::HashMap, io::Cursor, marker::PhantomData}; + +use async_trait::async_trait; +use cid::{multihash, Cid}; +use libipld_core::{ + codec::{Codec, Decode, Encode}, + raw::RawCodec, +}; +use multihash::MultihashDigest; + +/// A store for persisting UCAN tokens, to be referencable as proofs by other UCANs +pub trait Store +where + C: Codec, +{ + /// The error type for this store + type Error; + + /// Read a token from the store + fn read(&self, cid: &Cid) -> Result, Self::Error> + where + T: Decode; + + /// Write a token to the store, using the specified hasher + fn write(&mut self, token: T, hasher: multihash::Code) -> Result + where + T: Encode; +} + +/// An async store for persisting UCAN tokens, to be referencable as proofs by other UCANs +// TODO: The send / sync bounds need to be conditional based on the target, to support wasm32 +#[async_trait] +pub trait AsyncStore: Send + Sync +where + C: Codec, +{ + /// The error type for this store + type Error; + + /// Read a token from the store + async fn read(&self, cid: &Cid) -> Result, Self::Error> + where + T: Decode; + + /// Write a token to the store, using the specified hasher + async fn write(&mut self, token: T, hasher: multihash::Code) -> Result + where + T: Encode + Send; +} + +/// An in-memory store for development and testing +#[derive(Debug, Clone, Default)] +pub struct InMemoryStore { + store: HashMap>, + _phantom: PhantomData, +} + +impl Store for InMemoryStore { + type Error = anyhow::Error; + + fn read(&self, cid: &Cid) -> Result, Self::Error> + where + T: Decode, + { + match self.store.get(cid) { + Some(block) => Ok(Some(T::decode(RawCodec, &mut Cursor::new(block))?)), + None => Ok(None), + } + } + + fn write(&mut self, token: T, hasher: multihash::Code) -> Result + where + T: Encode, + { + let block = RawCodec.encode(&token)?; + let digest = hasher.digest(&block); + let cid = Cid::new_v1(RawCodec.into(), digest); + + self.store.insert(cid, block); + + Ok(cid) + } +} diff --git a/tests/conformance.rs b/tests/conformance.rs index 623c0942..1be7c770 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -1,3 +1,4 @@ +use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; @@ -5,6 +6,7 @@ use rs_ucan::{ capability::DefaultCapabilityParser, crypto::eddsa::ed25519_dalek_verifier, did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + store::{self, Store}, ucan::Ucan, DefaultFact, }; @@ -163,11 +165,22 @@ struct ToCidTestOutputs { impl TestTask for VerifyTest { fn run(&self, name: &str, report: &mut TestReport) { + let mut store = store::InMemoryStore::::default(); + let mut did_key_verifier = DidKeyVerifier::default(); let mut did_verifier_map = DidVerifierMap::default(); did_key_verifier.set::(ed25519_dalek_verifier); did_verifier_map.register(did_key_verifier); + for (_cid, token) in self.inputs.proofs.iter() { + store + .write( + Ipld::Bytes(token.as_bytes().to_vec()), + multihash::Code::Sha2_256, + ) + .unwrap(); + } + let Ok(ucan) = Ucan::::from_str(&self.inputs.token) else { report.register_failure(name, "failed to parse token".to_string()); @@ -302,12 +315,22 @@ impl TestTask for VerifyTest { impl TestTask for RefuteTest { fn run(&self, name: &str, report: &mut TestReport) { + let mut store = store::InMemoryStore::::default(); let mut did_key_verifier = DidKeyVerifier::default(); did_key_verifier.set::(ed25519_dalek_verifier); let mut did_verifier_map = DidVerifierMap::default(); did_verifier_map.register(did_key_verifier); + for (_cid, token) in self.inputs.proofs.iter() { + store + .write( + Ipld::Bytes(token.as_bytes().to_vec()), + multihash::Code::Sha2_256, + ) + .unwrap(); + } + if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan From 580b0235e395e8d5c50c90a43190506d33ad4e82 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 4 Oct 2023 12:24:06 -0700 Subject: [PATCH 024/234] refactor: remove unused Ucan::is_authorized --- src/ucan.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/ucan.rs b/src/ucan.rs index c0ef11c2..b44a0db7 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -6,7 +6,6 @@ use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, did_verifier::DidVerifierMap, error::Error, - semantics::{ability::Ability, resource::Resource}, CidString, DefaultFact, }; use cid::{ @@ -285,31 +284,6 @@ where Ok(cid) } - - /// Returns true if the UCAN authorizes the given resource and ability - // TODO: This is an old placeholder implementation that needs to take - // into account the issuer of the capabilities - pub fn is_authorized(&self, resource: &R, ability: &A) -> bool - where - R: Resource, - A: Ability, - { - for capability in self.capabilities() { - if !resource.is_valid_attenuation(capability.resource()) { - continue; - } - - if !ability.is_valid_attenuation(capability.ability()) { - continue; - } - - if capability.caveat().is_valid() { - return true; - } - } - - false - } } impl<'a, F, C> TryFrom<&'a str> for Ucan From d5600925177f64127e2c448669cd3181c732b627 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 4 Oct 2023 12:54:05 -0700 Subject: [PATCH 025/234] test: add tests for WNFS plugin --- src/plugins/wnfs.rs | 224 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/src/plugins/wnfs.rs b/src/plugins/wnfs.rs index 263b2ea8..742e41b2 100644 --- a/src/plugins/wnfs.rs +++ b/src/plugins/wnfs.rs @@ -166,3 +166,227 @@ impl Display for WnfsAbility { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plugin_scheme() { + assert_eq!(WnfsPlugin.scheme(), "wnfs"); + } + + #[test] + fn test_plugin_try_handle_resource_public() -> anyhow::Result<()> { + let resource = + WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/public/path/to/file")?)?; + + assert_eq!( + resource, + Some(WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["path".to_string(), "to".to_string(), "file".to_string()], + }) + ); + + Ok(()) + } + + #[test] + fn test_plugin_try_handle_resource_invalid() -> anyhow::Result<()> { + let resource = + WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/invalid/path/to/file")?)?; + + assert_eq!(resource, None); + + Ok(()) + } + + #[test] + fn test_plugin_try_handle_ability_public() -> anyhow::Result<()> { + let resource = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["path".to_string(), "to".to_string(), "file".to_string()], + }; + + let ability_create = WnfsPlugin.try_handle_ability(&resource, "wnfs/create")?; + let ability_revise = WnfsPlugin.try_handle_ability(&resource, "wnfs/revise")?; + let ability_soft_delete = WnfsPlugin.try_handle_ability(&resource, "wnfs/soft_delete")?; + let ability_overwrite = WnfsPlugin.try_handle_ability(&resource, "wnfs/overwrite")?; + let ability_super_user = WnfsPlugin.try_handle_ability(&resource, "wnfs/super_user")?; + let ability_invalid = WnfsPlugin.try_handle_ability(&resource, "wnfs/not-an-ability")?; + + assert_eq!(ability_create, Some(WnfsAbility::Create)); + assert_eq!(ability_revise, Some(WnfsAbility::Revise)); + assert_eq!(ability_soft_delete, Some(WnfsAbility::SoftDelete)); + assert_eq!(ability_overwrite, Some(WnfsAbility::Overwrite)); + assert_eq!(ability_super_user, Some(WnfsAbility::SuperUser)); + assert_eq!(ability_invalid, None); + + Ok(()) + } + + #[test] + fn test_resource_public_display() { + let resource = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + assert_eq!(resource.to_string(), "wnfs://user/public/foo/bar"); + } + + #[test] + fn test_resource_public_attenuation_identity() { + let resource = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + assert!(resource.is_valid_attenuation(&resource)); + } + + #[test] + fn test_resource_public_attenuation_child() { + let parent = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let child = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], + }; + + assert!(child.is_valid_attenuation(&parent)); + } + + #[test] + fn test_resource_public_attenuation_descendent() { + let ancestor = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let descendent = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec![ + "foo".to_string(), + "bar".to_string(), + "baz".to_string(), + "qux".to_string(), + ], + }; + + assert!(descendent.is_valid_attenuation(&ancestor)); + } + + #[test] + fn test_resource_public_attenuation_parent() { + let parent = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let child = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], + }; + + assert!(!parent.is_valid_attenuation(&child)); + } + + #[test] + fn test_resource_public_attenuation_ancestor() { + let ancestor = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let descendent = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec![ + "foo".to_string(), + "bar".to_string(), + "baz".to_string(), + "qux".to_string(), + ], + }; + + assert!(!ancestor.is_valid_attenuation(&descendent)); + } + + #[test] + fn test_resource_public_attenuation_sibling() { + let sibling_1 = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let sibling_2 = WnfsResource::PublicPath { + user: "user".to_string(), + path: vec!["foo".to_string(), "baz".to_string()], + }; + + assert!(!sibling_1.is_valid_attenuation(&sibling_2)); + assert!(!sibling_2.is_valid_attenuation(&sibling_1)); + } + + #[test] + fn test_resource_public_attenuation_distinct_users() { + let path_1 = WnfsResource::PublicPath { + user: "user1".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + let path_2 = WnfsResource::PublicPath { + user: "user2".to_string(), + path: vec!["foo".to_string(), "bar".to_string()], + }; + + assert!(!path_1.is_valid_attenuation(&path_2)); + assert!(!path_2.is_valid_attenuation(&path_1)); + } + + #[test] + fn test_ability_attenuation() { + assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Create)); + assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Revise)); + assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SoftDelete)); + assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Overwrite)); + assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SuperUser)); + + assert!(!WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Create)); + assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Revise)); + assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SoftDelete)); + assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Overwrite)); + assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SuperUser)); + + assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Create)); + assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Revise)); + assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SoftDelete)); + assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Overwrite)); + assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SuperUser)); + + assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Create)); + assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Revise)); + assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SoftDelete)); + assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Overwrite)); + assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); + + assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Create)); + assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Revise)); + assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::SoftDelete)); + assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Overwrite)); + assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); + } + + #[test] + fn test_ability_display() { + assert_eq!(WnfsAbility::Create.to_string(), "wnfs/create"); + assert_eq!(WnfsAbility::Revise.to_string(), "wnfs/revise"); + assert_eq!(WnfsAbility::SoftDelete.to_string(), "wnfs/soft_delete"); + assert_eq!(WnfsAbility::Overwrite.to_string(), "wnfs/overwrite"); + assert_eq!(WnfsAbility::SuperUser.to_string(), "wnfs/super_user"); + } +} From bd338ddb08d4e8aba5e2696730a719602165bf86 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Thu, 5 Oct 2023 10:04:59 -0700 Subject: [PATCH 026/234] fix: fix a few bugs in UCAN plugin + add tests --- src/plugins/ucan.rs | 284 +++++++++++++++++++++++++++++++++++++++++--- src/ucan.rs | 132 +++++++++++++++++++- 2 files changed, 399 insertions(+), 17 deletions(-) diff --git a/src/plugins/ucan.rs b/src/plugins/ucan.rs index fe3e3a2f..77f0aedd 100644 --- a/src/plugins/ucan.rs +++ b/src/plugins/ucan.rs @@ -34,6 +34,10 @@ impl Plugin for UcanPlugin { &self, resource_uri: &Url, ) -> Result, Self::Error> { + // TODO: I'm not handling the OwnedBy or OwnedByWithScheme cases yet, + // because the spec probably needs to be modified to treat the DID as + // a literal, by wrapping it in square brackets, to avoid parsing issues + // from treating it as an authority with a port. match resource_uri.path() { "*" => Ok(Some(UcanResource::AllProvable)), "./*" => Ok(Some(UcanResource::LocallyProvable)), @@ -42,18 +46,7 @@ impl Plugin for UcanPlugin { return Ok(Some(UcanResource::ByCid(cid))); } - match resource_uri - .path_segments() - .map(|p| p.collect::>()) - .as_deref() - { - Some([did, "*"]) => Ok(Some(UcanResource::OwnedBy(did.to_string()))), - Some([did, scheme]) => Ok(Some(UcanResource::OwnedByWithScheme( - did.to_string(), - scheme.to_string(), - ))), - _ => Ok(None), - } + Ok(None) } } } @@ -100,8 +93,8 @@ impl Display for UcanResource { UcanResource::ByCid(cid) => cid.to_string(), UcanResource::AllProvable => "*".to_string(), UcanResource::LocallyProvable => "./*".to_string(), - UcanResource::OwnedBy(did) => format!("{}/*", did), - UcanResource::OwnedByWithScheme(did, scheme) => format!("{}/{}", did, scheme), + UcanResource::OwnedBy(did) => format!("//{}/*", did), + UcanResource::OwnedByWithScheme(did, scheme) => format!("//{}/{}", did, scheme), }; f.write_fmt(format_args!("ucan:{}", hier_part)) @@ -134,6 +127,267 @@ impl Ability for UcanAbilityDelegation { impl Display for UcanAbilityDelegation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "*") + write!(f, "ucan/*") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plugin_scheme() { + assert_eq!(UcanPlugin.scheme(), "ucan"); + } + + #[test] + fn test_plugin_try_handle_resource_by_cid() -> anyhow::Result<()> { + let resource = UcanPlugin.try_handle_resource(&Url::parse( + "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + )?)?; + + assert_eq!( + resource, + Some(UcanResource::ByCid(Cid::try_from( + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + )?)) + ); + + Ok(()) + } + + #[test] + fn test_plugin_try_handle_resource_all_provable() -> anyhow::Result<()> { + let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:*")?)?; + + assert_eq!(resource, Some(UcanResource::AllProvable)); + + Ok(()) + } + + #[test] + fn test_plugin_try_handle_resource_locally_provable() -> anyhow::Result<()> { + let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:./*")?)?; + + assert_eq!(resource, Some(UcanResource::LocallyProvable)); + + Ok(()) + } + + #[test] + #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] + fn test_plugin_try_handle_resource_owned_by() -> anyhow::Result<()> { + let resource = UcanPlugin.try_handle_resource(&Url::parse( + "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*", + )?)?; + + assert_eq!( + resource, + Some(UcanResource::OwnedBy( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string() + )) + ); + + Ok(()) + } + + #[test] + #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] + fn test_plugin_try_handle_resource_owned_with_scheme() -> anyhow::Result<()> { + let resource = UcanPlugin.try_handle_resource(&Url::parse( + "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs", + )?)?; + + assert_eq!( + resource, + Some(UcanResource::OwnedByWithScheme( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + "wnfs".to_string() + )) + ); + + Ok(()) + } + + #[test] + fn test_plugin_try_handle_ability_delegation() -> anyhow::Result<()> { + let ability = UcanPlugin.try_handle_ability(&UcanResource::AllProvable, "ucan/*")?; + + assert_eq!(ability, Some(UcanAbilityDelegation)); + + Ok(()) + } + + #[test] + fn test_resource_by_cid_display() -> anyhow::Result<()> { + let resource = UcanResource::ByCid(Cid::try_from( + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + )?); + + assert_eq!( + resource.to_string(), + "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + ); + + Ok(()) + } + + #[test] + fn test_resource_all_provable_display() { + let resource = UcanResource::AllProvable; + + assert_eq!(resource.to_string(), "ucan:*"); + } + + #[test] + fn test_resource_locally_provable_display() { + let resource = UcanResource::LocallyProvable; + + assert_eq!(resource.to_string(), "ucan:./*"); + } + + #[test] + fn test_resource_owned_by_display() { + let resource = UcanResource::OwnedBy( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + ); + + assert_eq!( + resource.to_string(), + "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*" + ); + } + + #[test] + fn test_resource_owned_by_with_scheme_display() { + let resource = UcanResource::OwnedByWithScheme( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + "wnfs".to_string(), + ); + + assert_eq!( + resource.to_string(), + "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs" + ); + } + + #[test] + fn test_ability_delegation_display() { + let ability = UcanAbilityDelegation; + + assert_eq!(ability.to_string(), "ucan/*"); + } + + #[test] + fn test_resource_attenuation() -> anyhow::Result<()> { + let all_provable = UcanResource::AllProvable; + let locally_provable = UcanResource::LocallyProvable; + + let by_cid_1 = UcanResource::ByCid(Cid::try_from( + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + )?); + + let by_cid_2 = UcanResource::ByCid(Cid::try_from( + "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", + )?); + + let owned_by_1 = UcanResource::OwnedBy( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + ); + + let owned_by_2 = UcanResource::OwnedBy("did:example:123456789abcdefghi".to_string()); + + let owned_by_with_scheme_1 = UcanResource::OwnedByWithScheme( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + "wnfs".to_string(), + ); + + let owned_by_with_scheme_2 = UcanResource::OwnedByWithScheme( + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), + "ucan".to_string(), + ); + + assert!(all_provable.is_valid_attenuation(&all_provable)); + assert!(!all_provable.is_valid_attenuation(&locally_provable)); + assert!(!all_provable.is_valid_attenuation(&by_cid_1)); + assert!(!all_provable.is_valid_attenuation(&by_cid_2)); + assert!(!all_provable.is_valid_attenuation(&owned_by_1)); + assert!(!all_provable.is_valid_attenuation(&owned_by_2)); + assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!locally_provable.is_valid_attenuation(&all_provable)); + assert!(locally_provable.is_valid_attenuation(&locally_provable)); + assert!(!locally_provable.is_valid_attenuation(&by_cid_1)); + assert!(!locally_provable.is_valid_attenuation(&by_cid_2)); + assert!(!locally_provable.is_valid_attenuation(&owned_by_1)); + assert!(!locally_provable.is_valid_attenuation(&owned_by_2)); + assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!by_cid_1.is_valid_attenuation(&all_provable)); + assert!(!by_cid_1.is_valid_attenuation(&locally_provable)); + assert!(by_cid_1.is_valid_attenuation(&by_cid_1)); + assert!(!by_cid_1.is_valid_attenuation(&by_cid_2)); + assert!(!by_cid_1.is_valid_attenuation(&owned_by_1)); + assert!(!by_cid_1.is_valid_attenuation(&owned_by_2)); + assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!by_cid_2.is_valid_attenuation(&all_provable)); + assert!(!by_cid_2.is_valid_attenuation(&locally_provable)); + assert!(!by_cid_2.is_valid_attenuation(&by_cid_1)); + assert!(by_cid_2.is_valid_attenuation(&by_cid_2)); + assert!(!by_cid_2.is_valid_attenuation(&owned_by_1)); + assert!(!by_cid_2.is_valid_attenuation(&owned_by_2)); + assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!owned_by_1.is_valid_attenuation(&all_provable)); + assert!(!owned_by_1.is_valid_attenuation(&locally_provable)); + assert!(!owned_by_1.is_valid_attenuation(&by_cid_1)); + assert!(!owned_by_1.is_valid_attenuation(&by_cid_2)); + assert!(owned_by_1.is_valid_attenuation(&owned_by_1)); + assert!(!owned_by_1.is_valid_attenuation(&owned_by_2)); + assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!owned_by_2.is_valid_attenuation(&all_provable)); + assert!(!owned_by_2.is_valid_attenuation(&locally_provable)); + assert!(!owned_by_2.is_valid_attenuation(&by_cid_1)); + assert!(!owned_by_2.is_valid_attenuation(&by_cid_2)); + assert!(!owned_by_2.is_valid_attenuation(&owned_by_1)); + assert!(owned_by_2.is_valid_attenuation(&owned_by_2)); + assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&all_provable)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&locally_provable)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_1)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_2)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_1)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_2)); + assert!(owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_2)); + + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&all_provable)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&locally_provable)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_1)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_2)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_1)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_2)); + assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_1)); + assert!(owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_2)); + + Ok(()) + } + + #[test] + fn test_ability_attenuation() -> anyhow::Result<()> { + let ability = UcanAbilityDelegation; + + assert!(ability.is_valid_attenuation(&ability)); + + Ok(()) } } diff --git a/src/ucan.rs b/src/ucan.rs index b44a0db7..c7cca9c3 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -1,17 +1,20 @@ //! JWT embedding of a UCAN -use std::str::FromStr; +use std::{collections::vec_deque::VecDeque, str::FromStr}; use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, did_verifier::DidVerifierMap, error::Error, - CidString, DefaultFact, + semantics::{ability::Ability, resource::Resource}, + store::Store, + CidString, DefaultFact, Did, }; use cid::{ multihash::{self, MultihashDigest}, Cid, }; +use libipld_core::{ipld::Ipld, raw::RawCodec}; use semver::Version; use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; @@ -131,6 +134,131 @@ where did_verifier_map.verify(method, identifier, signed_data.as_bytes(), &self.signature) } + /// Returns true if the UCAN is authorized by the given issuer to + /// perform the ability against the resource + pub fn capabilities_for( + &self, + issuer: Did, + resource: R, + ability: A, + at_time: u64, + did_verifier_map: &DidVerifierMap, + store: &S, + ) -> Result, Error> + where + R: Resource, + A: Ability, + S: Store, + { + let mut capabilities = vec![]; + let mut proof_queue: VecDeque<(Ucan, Capability, Capability)> = VecDeque::default(); + + self.validate(at_time, did_verifier_map)?; + + for capability in self.capabilities() { + if !resource.is_valid_attenuation(capability.resource()) { + continue; + } + + if !ability.is_valid_attenuation(capability.ability()) { + continue; + } + + proof_queue.push_back((self.clone(), capability.clone(), capability.clone())); + } + + while let Some((ucan, attenuated_cap, leaf_cap)) = proof_queue.pop_front() { + for proof_cid in ucan.proofs().unwrap_or(vec![]) { + match store + .read::(proof_cid) + .map_err(|e| Error::InternalUcanError { + msg: format!( + "error while retrieving proof ({}) from store, {}", + proof_cid, e + ), + })? { + Some(Ipld::Bytes(bytes)) => { + let token = + String::from_utf8(bytes).map_err(|e| Error::InternalUcanError { + msg: format!( + "error converting token for proof ({}) into UTF-8 string, {}", + proof_cid, e + ), + })?; + + let proof_ucan = + Ucan::from_str(&token).map_err(|e| Error::InternalUcanError { + msg: format!( + "error decoding token for proof ({}) into UCAN, {}", + proof_cid, e + ), + })?; + + if ucan.expires_at() > proof_ucan.expires_at() { + continue; + } + + if ucan.not_before() < proof_ucan.not_before() { + continue; + } + + if ucan.issuer() != proof_ucan.audience() { + continue; + } + + if ucan.validate(at_time, did_verifier_map).is_err() { + continue; + } + + for capability in self.capabilities() { + if !attenuated_cap + .resource() + .is_valid_attenuation(capability.resource()) + { + continue; + } + + if !attenuated_cap + .ability() + .is_valid_attenuation(capability.ability()) + { + continue; + } + + if !attenuated_cap + .caveat() + .is_valid_attenuation(capability.caveat()) + { + continue; + } + + if ucan.issuer() == issuer { + capabilities.push(leaf_cap.clone()); + } + + proof_queue.push_back(( + proof_ucan.clone(), + capability.clone(), + leaf_cap.clone(), + )); + } + } + Some(ipld) => { + return Err(Error::InternalUcanError { + msg: format!( + "expected proof ({}) to map to bytes, got {:?}", + proof_cid, ipld + ), + }) + } + None => continue, + } + } + } + + Ok(capabilities) + } + /// Encode the UCAN as a JWT token pub fn encode(&self) -> Result { let header = serde_json::to_value(&self.header) From aaf3102fb64f512091c606bf3d9d9ca145e21fed Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 12:27:12 -0700 Subject: [PATCH 027/234] fix: properly base64 encode the header and payload when signing in the builder --- Cargo.toml | 2 +- src/builder.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10e0f053..46e3bda8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ downcast-rs = "1.2.0" dyn-clone = "1.0.14" ecdsa = "0.16.8" ed25519 = "2.2.2" -ed25519-dalek = "2.0.0" +ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } erased-serde = "0.3.31" instant = "0.1.12" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } diff --git a/src/builder.rs b/src/builder.rs index 57240779..8f0c9991 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -175,10 +175,23 @@ where }) .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - let signature = signer - .sign(&[header.as_ref(), ".".as_bytes(), payload.as_ref()].concat()) - .to_vec() - .into(); + let header_b64 = serde_json::to_value(&header) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let payload_b64 = serde_json::to_value(&payload) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let signed_data = format!( + "{}.{}", + header_b64.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of header".to_string(), + })?, + payload_b64.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of payload".to_string(), + })?, + ); + + let signature = signer.sign(signed_data.as_bytes()).to_vec().into(); Ok(Ucan { header, @@ -187,3 +200,52 @@ where }) } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use multibase::Base; + use signature::rand_core; + + use crate::{ + crypto::eddsa::ed25519_dalek_verifier, + did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + }; + + use super::*; + + #[test] + fn test_round_trip_validate() -> Result<(), anyhow::Error> { + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let ucan: Ucan = UcanBuilder::default() + .issued_by(ed25519_to_did(iss_key.verifying_key())) + .for_audience(ed25519_to_did(aud_key.verifying_key())) + .sign(&iss_key)?; + + let token = ucan.encode()?; + let decoded: Ucan = Ucan::from_str(&token)?; + + assert!(decoded.validate(0, &did_verifier_map).is_ok()); + + Ok(()) + } + + fn ed25519_to_did(key: ed25519_dalek::VerifyingKey) -> String { + format!( + "did:key:{}", + multibase::encode( + Base::Base58Btc, + &[&[0xed, 0x01], key.to_bytes().as_ref()].concat() + ) + ) + } +} From 417fd9fe77c0893ad1bb8c0e042fd4b4af2474ac Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 12:44:22 -0700 Subject: [PATCH 028/234] fix: support invoking the register_plugin! macro from outside the create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Krüger --- src/plugins.rs | 8 +++++--- src/plugins/ucan.rs | 1 - src/plugins/wnfs.rs | 5 +---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/plugins.rs b/src/plugins.rs index 5b130fd5..034d4122 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -20,7 +20,8 @@ pub mod ucan; pub mod wnfs; #[distributed_slice] -static STATIC_PLUGINS: [&dyn Plugin< +#[doc(hidden)] +pub static STATIC_PLUGINS: [&dyn Plugin< Resource = Box, Ability = Box, Caveat = Box, @@ -88,7 +89,8 @@ where C: 'static, E: 'static, { - inner: &'static dyn Plugin, + #[doc(hidden)] + pub inner: &'static dyn Plugin, } impl Plugin for WrappedPlugin @@ -216,7 +218,7 @@ macro_rules! register_plugin { Resource = Box, Ability = Box, Caveat = Box, - Error = Error, + Error = $crate::error::Error, > = &$crate::plugins::WrappedPlugin { inner: $plugin }; }; } diff --git a/src/plugins/ucan.rs b/src/plugins/ucan.rs index 77f0aedd..9190d590 100644 --- a/src/plugins/ucan.rs +++ b/src/plugins/ucan.rs @@ -6,7 +6,6 @@ use cid::Cid; use url::Url; use crate::{ - error::Error, semantics::{ability::Ability, caveat::EmptyCaveat}, Did, }; diff --git a/src/plugins/wnfs.rs b/src/plugins/wnfs.rs index 742e41b2..66e3045b 100644 --- a/src/plugins/wnfs.rs +++ b/src/plugins/wnfs.rs @@ -2,10 +2,7 @@ use std::fmt::Display; -use crate::{ - error::Error, - semantics::{ability::Ability, caveat::EmptyCaveat, resource::Resource}, -}; +use crate::semantics::{ability::Ability, caveat::EmptyCaveat, resource::Resource}; use url::Url; use super::Plugin; From 5c314104d387e83f241f5ba38a4c08e5112df124 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 13:09:41 -0700 Subject: [PATCH 029/234] fix: handle deserializing caveats for the top ability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Krüger --- src/plugins.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins.rs b/src/plugins.rs index 034d4122..29227b6f 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -11,7 +11,7 @@ use crate::{ error::Error, semantics::{ ability::{Ability, TopAbility}, - caveat::Caveat, + caveat::{Caveat, EmptyCaveat}, resource::Resource, }, }; @@ -151,6 +151,13 @@ where return Ok(None); }; + if ability.is::() { + return Ok(Some(Box::new( + erased_serde::deserialize::(deserializer) + .map_err(|e| anyhow::anyhow!(e))?, + ))); + } + let Some(ability) = ability.downcast_ref::() else { return Ok(None); }; From 876237593f4d789a4bb0cb7147f7f2e04a934131 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 13:18:56 -0700 Subject: [PATCH 030/234] refactor: explicitly specify UCANs generics when constructing one --- src/builder.rs | 2 +- src/ucan.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 8f0c9991..7a80bf36 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -193,7 +193,7 @@ where let signature = signer.sign(signed_data.as_bytes()).to_vec().into(); - Ok(Ucan { + Ok(Ucan:: { header, payload, signature, diff --git a/src/ucan.rs b/src/ucan.rs index c7cca9c3..998c7f9c 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -422,7 +422,7 @@ where type Error = Error; fn try_from(ucan_token: &str) -> Result { - Ucan::from_str(ucan_token) + Ucan::::from_str(ucan_token) } } @@ -469,7 +469,7 @@ where msg: "malformed signature".to_string(), })?; - Ok(Ucan { + Ok(Ucan:: { header, payload, signature, From 12409b954e4da1cf83cf190e95d9efd451925108 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 13:48:43 -0700 Subject: [PATCH 031/234] fix: handle the top ability and capabilities rooted under the initial UCAN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Krüger --- src/capability.rs | 29 ++++- src/ucan.rs | 277 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 278 insertions(+), 28 deletions(-) diff --git a/src/capability.rs b/src/capability.rs index 90dab77e..6701feb7 100644 --- a/src/capability.rs +++ b/src/capability.rs @@ -21,14 +21,37 @@ pub type DefaultCapabilityParser = PluginCapability; #[derive(Debug, Clone)] pub struct Capability { /// The resource - pub resource: Box, + resource: Box, /// The ability - pub ability: Box, + ability: Box, /// The caveat - pub caveat: Box, + caveat: Box, } impl Capability { + /// Creates a new capability + pub fn new(resource: R, ability: A, caveat: C) -> Self + where + R: Resource, + A: Ability, + C: Caveat, + { + Self { + resource: Box::new(resource), + ability: Box::new(ability), + caveat: Box::new(caveat), + } + } + + /// Creates a new capability by cloning the resource, ability, and caveat as trait objects + pub fn clone_box(resource: &dyn Resource, ability: &dyn Ability, caveat: &dyn Caveat) -> Self { + Self { + resource: dyn_clone::clone_box(resource), + ability: dyn_clone::clone_box(ability), + caveat: dyn_clone::clone_box(caveat), + } + } + /// Returns the resource pub fn resource(&self) -> &dyn Resource { &*self.resource diff --git a/src/ucan.rs b/src/ucan.rs index 998c7f9c..a036f9a2 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -156,15 +156,17 @@ where self.validate(at_time, did_verifier_map)?; for capability in self.capabilities() { - if !resource.is_valid_attenuation(capability.resource()) { + let attenuated = Capability::clone_box(&resource, &ability, capability.caveat()); + + if !attenuated.is_subsumed_by(capability) { continue; } - if !ability.is_valid_attenuation(capability.ability()) { - continue; + if self.issuer() == issuer { + capabilities.push(attenuated.clone()) } - proof_queue.push_back((self.clone(), capability.clone(), capability.clone())); + proof_queue.push_back((self.clone(), capability.clone(), attenuated)); } while let Some((ucan, attenuated_cap, leaf_cap)) = proof_queue.pop_front() { @@ -206,33 +208,16 @@ where continue; } - if ucan.validate(at_time, did_verifier_map).is_err() { + if proof_ucan.validate(at_time, did_verifier_map).is_err() { continue; } - for capability in self.capabilities() { - if !attenuated_cap - .resource() - .is_valid_attenuation(capability.resource()) - { - continue; - } - - if !attenuated_cap - .ability() - .is_valid_attenuation(capability.ability()) - { + for capability in proof_ucan.capabilities() { + if !attenuated_cap.is_subsumed_by(capability) { continue; } - if !attenuated_cap - .caveat() - .is_valid_attenuation(capability.caveat()) - { - continue; - } - - if ucan.issuer() == issuer { + if proof_ucan.issuer() == issuer { capabilities.push(leaf_cap.clone()); } @@ -485,3 +470,245 @@ where Deserialize::deserialize(deserializer) .map_err(|_| serde::de::Error::custom("required field is missing or has invalid type")) } + +#[cfg(test)] +mod tests { + + use multibase::Base; + use signature::rand_core; + + use crate::{ + builder::UcanBuilder, + crypto::eddsa::ed25519_dalek_verifier, + did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + plugins::wnfs::{WnfsAbility, WnfsResource}, + semantics::{ability::TopAbility, caveat::EmptyCaveat}, + store::InMemoryStore, + }; + + use super::*; + + #[test] + fn test_capabilities_for_empty() -> Result<(), anyhow::Error> { + let store = InMemoryStore::::default(); + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let ucan: Ucan = UcanBuilder::default() + .issued_by(ed25519_to_did(iss_key.verifying_key())) + .for_audience(ed25519_to_did(aud_key.verifying_key())) + .sign(&iss_key)?; + + let capabilities = ucan.capabilities_for( + ed25519_to_did(iss_key.verifying_key()), + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Create, + 0, + &did_verifier_map, + &store, + )?; + + assert!(capabilities.is_empty()); + + Ok(()) + } + + #[test] + fn test_capabilities_for_root_capability_exact() -> Result<(), anyhow::Error> { + let store = InMemoryStore::::default(); + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let ucan: Ucan = UcanBuilder::default() + .issued_by(ed25519_to_did(iss_key.verifying_key())) + .for_audience(ed25519_to_did(aud_key.verifying_key())) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Create, + EmptyCaveat {}, + )) + .sign(&iss_key)?; + + let capabilities = ucan.capabilities_for( + ed25519_to_did(iss_key.verifying_key()), + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Create, + 0, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 1); + + assert_eq!( + capabilities[0].resource().downcast_ref::(), + Some(&WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }) + ); + + assert_eq!( + capabilities[0].ability().downcast_ref::(), + Some(&WnfsAbility::Create) + ); + + assert_eq!( + capabilities[0].caveat().downcast_ref::(), + Some(&EmptyCaveat {}) + ); + + Ok(()) + } + + #[test] + fn test_capabilities_for_root_capability_subsumed_by_semantics() -> Result<(), anyhow::Error> { + let store = InMemoryStore::::default(); + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let ucan: Ucan = UcanBuilder::default() + .issued_by(ed25519_to_did(iss_key.verifying_key())) + .for_audience(ed25519_to_did(aud_key.verifying_key())) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Overwrite, + EmptyCaveat {}, + )) + .sign(&iss_key)?; + + let capabilities = ucan.capabilities_for( + ed25519_to_did(iss_key.verifying_key()), + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string(), "vacation".to_string()], + }, + WnfsAbility::Create, + 0, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 1); + + assert_eq!( + capabilities[0].resource().downcast_ref::(), + Some(&WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string(), "vacation".to_string()], + }) + ); + + assert_eq!( + capabilities[0].ability().downcast_ref::(), + Some(&WnfsAbility::Create) + ); + + assert_eq!( + capabilities[0].caveat().downcast_ref::(), + Some(&EmptyCaveat {}) + ); + + Ok(()) + } + + #[test] + fn test_capabilities_for_root_capability_subsumed_by_top() -> Result<(), anyhow::Error> { + let store = InMemoryStore::::default(); + let mut did_key_verifier = DidKeyVerifier::default(); + did_key_verifier.set::(ed25519_dalek_verifier); + + let mut did_verifier_map = DidVerifierMap::default(); + did_verifier_map.register(did_key_verifier); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let ucan: Ucan = UcanBuilder::default() + .issued_by(ed25519_to_did(iss_key.verifying_key())) + .for_audience(ed25519_to_did(aud_key.verifying_key())) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat {}, + )) + .sign(&iss_key)?; + + let capabilities = ucan.capabilities_for( + ed25519_to_did(iss_key.verifying_key()), + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string(), "vacation".to_string()], + }, + WnfsAbility::Overwrite, + 0, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 1); + + assert_eq!( + capabilities[0].resource().downcast_ref::(), + Some(&WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string(), "vacation".to_string()], + }) + ); + + assert_eq!( + capabilities[0].ability().downcast_ref::(), + Some(&WnfsAbility::Overwrite) + ); + + assert_eq!( + capabilities[0].caveat().downcast_ref::(), + Some(&EmptyCaveat {}) + ); + + Ok(()) + } + + fn ed25519_to_did(key: ed25519_dalek::VerifyingKey) -> String { + format!( + "did:key:{}", + multibase::encode( + Base::Base58Btc, + &[&[0xed, 0x01], key.to_bytes().as_ref()].concat() + ) + ) + } +} From f1c88e25c5ffc31f4452a26c19351862a15e4220 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 13:51:19 -0700 Subject: [PATCH 032/234] refactor: use shorthand singleton syntax for EmptyCaveat --- src/semantics/caveat.rs | 2 +- src/ucan.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs index c4f9297a..5215beb3 100644 --- a/src/semantics/caveat.rs +++ b/src/semantics/caveat.rs @@ -28,7 +28,7 @@ impl fmt::Debug for dyn Caveat { /// A caveat that is always valid #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct EmptyCaveat {} +pub struct EmptyCaveat; impl Caveat for EmptyCaveat { fn is_valid(&self) -> bool { diff --git a/src/ucan.rs b/src/ucan.rs index a036f9a2..13334c3b 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -543,7 +543,7 @@ mod tests { path: vec!["photos".to_string()], }, WnfsAbility::Create, - EmptyCaveat {}, + EmptyCaveat, )) .sign(&iss_key)?; @@ -576,7 +576,7 @@ mod tests { assert_eq!( capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat {}) + Some(&EmptyCaveat) ); Ok(()) @@ -603,7 +603,7 @@ mod tests { path: vec!["photos".to_string()], }, WnfsAbility::Overwrite, - EmptyCaveat {}, + EmptyCaveat, )) .sign(&iss_key)?; @@ -636,7 +636,7 @@ mod tests { assert_eq!( capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat {}) + Some(&EmptyCaveat) ); Ok(()) @@ -663,7 +663,7 @@ mod tests { path: vec!["photos".to_string()], }, TopAbility, - EmptyCaveat {}, + EmptyCaveat, )) .sign(&iss_key)?; @@ -696,7 +696,7 @@ mod tests { assert_eq!( capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat {}) + Some(&EmptyCaveat) ); Ok(()) From 3c088bdde6b3e81a6b003b4a9150f9e3017b26cc Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 15:04:29 -0700 Subject: [PATCH 033/234] feat: use features to automatically register DID verifiers and signature verifiers --- Cargo.toml | 9 ++++++++- src/builder.rs | 11 ++--------- src/crypto/eddsa.rs | 6 +----- src/crypto/es256.rs | 14 ++++++++++++++ src/crypto/es256k.rs | 2 +- src/crypto/es384.rs | 2 +- src/crypto/ps256.rs | 13 ++++++++----- src/crypto/rs256.rs | 6 +----- src/did_verifier.rs | 17 ++++++++++++++++- src/did_verifier/did_key.rs | 34 +++++++++++++++++++++++++++++++++- src/ucan.rs | 27 +++++---------------------- tests/conformance.rs | 15 +++------------ 12 files changed, 93 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46e3bda8..6234052a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,8 +65,15 @@ multihash = "0.18.0" proptest = "1.1" [features] -default = [] +default = ["did-key", "eddsa-verifier", "es256-verifier", "es256k-verifier", "es384-verifier", "ps256-verifier", "rs256-verifier"] test_utils = ["proptest"] +did-key = [] +eddsa-verifier = [] +es256-verifier = [] +es256k-verifier = [] +es384-verifier = [] +ps256-verifier = [] +rs256-verifier = [] [metadata.docs.rs] all-features = true diff --git a/src/builder.rs b/src/builder.rs index 7a80bf36..d3101e52 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -208,20 +208,13 @@ mod tests { use multibase::Base; use signature::rand_core; - use crate::{ - crypto::eddsa::ed25519_dalek_verifier, - did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, - }; + use crate::did_verifier::DidVerifierMap; use super::*; #[test] fn test_round_trip_validate() -> Result<(), anyhow::Error> { - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs index 0702658c..ee33302c 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -10,11 +10,7 @@ impl JWSSignature for ed25519::Signature { } /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate -pub fn ed25519_dalek_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { +pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = ed25519_dalek::VerifyingKey::try_from(key) .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs index 90a05af9..c3800a95 100644 --- a/src/crypto/es256.rs +++ b/src/crypto/es256.rs @@ -1,7 +1,21 @@ //! ES256 signature support +use anyhow::anyhow; +use signature::Verifier; + use super::JWSSignature; impl JWSSignature for ecdsa::Signature { const ALGORITHM: &'static str = "ES256"; } + +/// A verifier for PS256 signatures +pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { + let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; + + let signature = + p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; + + key.verify(payload, &signature) + .map_err(|e| anyhow!("signature mismatch, {}", e)) +} diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs index 3c19b50c..a11c26dc 100644 --- a/src/crypto/es256k.rs +++ b/src/crypto/es256k.rs @@ -10,7 +10,7 @@ impl JWSSignature for ecdsa::Signature { } /// A verifier for ES256k signatures -pub fn k256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs index 5b998d60..d4735632 100644 --- a/src/crypto/es384.rs +++ b/src/crypto/es384.rs @@ -10,7 +10,7 @@ impl JWSSignature for ecdsa::Signature { } /// A verifier for ES384 signatures -pub fn p384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; let signature = diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs index 3833b72c..438dd377 100644 --- a/src/crypto/ps256.rs +++ b/src/crypto/ps256.rs @@ -9,12 +9,15 @@ impl JWSSignature for rsa::pss::Signature { const ALGORITHM: &'static str = "PS256"; } -/// A verifier for PS256 signatures -pub fn p256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; +/// A verifier for RS256 signatures +pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { + let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) + .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; - let signature = - p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; + let key = rsa::pss::VerifyingKey::::new(key); + + let signature = rsa::pss::Signature::try_from(signature) + .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; key.verify(payload, &signature) .map_err(|e| anyhow!("signature mismatch, {}", e)) diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index a3163698..da373703 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -10,11 +10,7 @@ impl JWSSignature for rsa::pkcs1v15::Signature { } /// A verifier for RS256 signatures -pub fn rsassa_pkcs1_v1_5_sha256_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { +pub fn rs256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; diff --git a/src/did_verifier.rs b/src/did_verifier.rs index 089f3f83..0c25fcd8 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -5,14 +5,29 @@ use std::collections::HashMap; use crate::error::Error; +use self::did_key::DidKeyVerifier; + pub mod did_key; /// A map from did method to verifier -#[derive(Debug, Default)] +#[derive(Debug)] pub struct DidVerifierMap { map: HashMap>, } +impl Default for DidVerifierMap { + fn default() -> Self { + let mut did_verifier_map = Self { + map: HashMap::new(), + }; + + #[cfg(feature = "did-key")] + did_verifier_map.register(DidKeyVerifier::default()); + + did_verifier_map + } +} + impl DidVerifierMap { /// Register a verifier pub fn register(&mut self, verifier: V) -> &mut Self diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs index 11331f23..2ea0bc53 100644 --- a/src/did_verifier/did_key.rs +++ b/src/did_verifier/did_key.rs @@ -6,18 +6,50 @@ use std::{any::TypeId, collections::HashMap}; use anyhow::anyhow; use multibase::Base; +use crate::crypto::{ + eddsa::eddsa_verifier, es256::es256_verifier, es256k::es256k_verifier, es384::es384_verifier, + ps256::ps256_verifier, rs256::rs256_verifier, +}; + use super::DidVerifier; /// A closure for verifying a signature pub type SignatureVerifier = dyn Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error>; /// did:key method verifier -#[derive(Default)] pub struct DidKeyVerifier { /// map from type id of signature to verifier function verifier_map: HashMap>, } +impl Default for DidKeyVerifier { + fn default() -> Self { + let mut did_key_verifier = Self { + verifier_map: HashMap::new(), + }; + + #[cfg(feature = "eddsa-verifier")] + did_key_verifier.set::(eddsa_verifier); + + #[cfg(feature = "es256-verifier")] + did_key_verifier.set::, _>(es256_verifier); + + #[cfg(feature = "es256k-verifier")] + did_key_verifier.set::, _>(es256k_verifier); + + #[cfg(feature = "es384-verifier")] + did_key_verifier.set::, _>(es384_verifier); + + #[cfg(feature = "ps256-verifier")] + did_key_verifier.set::(ps256_verifier); + + #[cfg(feature = "rs256-verifier")] + did_key_verifier.set::(rs256_verifier); + + did_key_verifier + } +} + impl DidKeyVerifier { /// set verifier function for type `T` pub fn set(&mut self, f: F) -> &mut Self diff --git a/src/ucan.rs b/src/ucan.rs index 13334c3b..df8ec0bf 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -479,8 +479,7 @@ mod tests { use crate::{ builder::UcanBuilder, - crypto::eddsa::ed25519_dalek_verifier, - did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + did_verifier::DidVerifierMap, plugins::wnfs::{WnfsAbility, WnfsResource}, semantics::{ability::TopAbility, caveat::EmptyCaveat}, store::InMemoryStore, @@ -491,11 +490,7 @@ mod tests { #[test] fn test_capabilities_for_empty() -> Result<(), anyhow::Error> { let store = InMemoryStore::::default(); - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); @@ -525,11 +520,7 @@ mod tests { #[test] fn test_capabilities_for_root_capability_exact() -> Result<(), anyhow::Error> { let store = InMemoryStore::::default(); - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); @@ -585,11 +576,7 @@ mod tests { #[test] fn test_capabilities_for_root_capability_subsumed_by_semantics() -> Result<(), anyhow::Error> { let store = InMemoryStore::::default(); - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); @@ -645,11 +632,7 @@ mod tests { #[test] fn test_capabilities_for_root_capability_subsumed_by_top() -> Result<(), anyhow::Error> { let store = InMemoryStore::::default(); - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); diff --git a/tests/conformance.rs b/tests/conformance.rs index 1be7c770..a5721677 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -4,8 +4,7 @@ use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; use rs_ucan::{ capability::DefaultCapabilityParser, - crypto::eddsa::ed25519_dalek_verifier, - did_verifier::{did_key::DidKeyVerifier, DidVerifierMap}, + did_verifier::DidVerifierMap, store::{self, Store}, ucan::Ucan, DefaultFact, @@ -166,11 +165,7 @@ struct ToCidTestOutputs { impl TestTask for VerifyTest { fn run(&self, name: &str, report: &mut TestReport) { let mut store = store::InMemoryStore::::default(); - - let mut did_key_verifier = DidKeyVerifier::default(); - let mut did_verifier_map = DidVerifierMap::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); for (_cid, token) in self.inputs.proofs.iter() { store @@ -316,11 +311,7 @@ impl TestTask for VerifyTest { impl TestTask for RefuteTest { fn run(&self, name: &str, report: &mut TestReport) { let mut store = store::InMemoryStore::::default(); - let mut did_key_verifier = DidKeyVerifier::default(); - did_key_verifier.set::(ed25519_dalek_verifier); - - let mut did_verifier_map = DidVerifierMap::default(); - did_verifier_map.register(did_key_verifier); + let did_verifier_map = DidVerifierMap::default(); for (_cid, token) in self.inputs.proofs.iter() { store From fafd18327797cc9ef57411b68657356e4911efed Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 15:06:45 -0700 Subject: [PATCH 034/234] fix: re-export linkme to avoid requiring callers install it manually --- src/lib.rs | 3 +++ src/plugins.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fe84f447..bb728e91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,9 @@ pub mod store; pub mod time; pub mod ucan; +#[doc(hidden)] +pub use linkme; + /// A decentralized identifier. pub type Did = String; diff --git a/src/plugins.rs b/src/plugins.rs index 29227b6f..76944d57 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -220,7 +220,7 @@ pub fn register_plugin( #[macro_export] macro_rules! register_plugin { ($name:ident, $plugin:expr) => { - #[linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] + #[$crate::linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] static $name: &'static dyn Plugin< Resource = Box, Ability = Box, From 297ba2e9b9bbb4dc3d0bdef39ba394c4de0a4ec9 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Fri, 6 Oct 2023 15:43:04 -0700 Subject: [PATCH 035/234] fix: require that resources, abilities, and caveats are Send + Sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Krüger --- src/semantics/ability.rs | 2 +- src/semantics/caveat.rs | 2 +- src/semantics/resource.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/semantics/ability.rs b/src/semantics/ability.rs index 74a8117e..d4758496 100644 --- a/src/semantics/ability.rs +++ b/src/semantics/ability.rs @@ -8,7 +8,7 @@ use dyn_clone::{clone_trait_object, DynClone}; use super::caveat::Caveat; /// An ability defined as part of a semantics -pub trait Ability: Display + DynClone + Downcast + 'static { +pub trait Ability: Send + Sync + Display + DynClone + Downcast + 'static { /// Returns true if self is a valid attenuation of other fn is_valid_attenuation(&self, other: &dyn Ability) -> bool; diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs index 5215beb3..e6d6e5fa 100644 --- a/src/semantics/caveat.rs +++ b/src/semantics/caveat.rs @@ -8,7 +8,7 @@ use erased_serde::serialize_trait_object; use serde::{Deserialize, Serialize}; /// A caveat defined as part of a semantics -pub trait Caveat: DynClone + Downcast + erased_serde::Serialize + 'static { +pub trait Caveat: Send + Sync + DynClone + Downcast + erased_serde::Serialize + 'static { /// Returns true if the caveat is valid fn is_valid(&self) -> bool; diff --git a/src/semantics/resource.rs b/src/semantics/resource.rs index d706b4e3..a7f5360b 100644 --- a/src/semantics/resource.rs +++ b/src/semantics/resource.rs @@ -6,7 +6,7 @@ use downcast_rs::{impl_downcast, Downcast}; use dyn_clone::{clone_trait_object, DynClone}; /// A resource defined as part of a semantics -pub trait Resource: Display + DynClone + Downcast + 'static { +pub trait Resource: Send + Sync + Display + DynClone + Downcast + 'static { /// Returns true if self is a valid attenuation of other fn is_valid_attenuation(&self, other: &dyn Resource) -> bool; } From 909ffb705c09e0a8a0d5054dd005df245f52e171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Wed, 11 Oct 2023 14:52:25 +0200 Subject: [PATCH 036/234] fix: Qualify `Plugin` use in `register_plugin!` macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philipp Krüger --- src/plugins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins.rs b/src/plugins.rs index 76944d57..f86d5994 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -221,7 +221,7 @@ pub fn register_plugin( macro_rules! register_plugin { ($name:ident, $plugin:expr) => { #[$crate::linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] - static $name: &'static dyn Plugin< + static $name: &'static dyn $crate::plugins::Plugin< Resource = Box, Ability = Box, Caveat = Box, From eb58bbe1b88b0fc53facf1ee5e3e63e2919626f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Mon, 16 Oct 2023 20:37:14 +0200 Subject: [PATCH 037/234] feat: Implement Serialize/Deserialize for UCAN Also: Take `impl AsRef` in some places instead of `String` (and standardize on specifying it as an `impl`-in-place). --- src/builder.rs | 6 +++--- src/did_verifier.rs | 2 +- src/ucan.rs | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index d3101e52..0ccae2f9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -59,19 +59,19 @@ where } /// Set the issuer of the UCAN - pub fn issued_by>(mut self, issuer: S) -> Self { + pub fn issued_by(mut self, issuer: impl AsRef) -> Self { self.issuer = Some(issuer.as_ref().to_string()); self } /// Set the audience of the UCAN - pub fn for_audience>(mut self, audience: S) -> Self { + pub fn for_audience(mut self, audience: impl AsRef) -> Self { self.audience = Some(audience.as_ref().to_string()); self } /// Set the nonce of the UCAN - pub fn with_nonce>(mut self, nonce: S) -> Self { + pub fn with_nonce(mut self, nonce: impl AsRef) -> Self { self.nonce = Some(nonce.as_ref().to_string()); self } diff --git a/src/did_verifier.rs b/src/did_verifier.rs index 0c25fcd8..c840fd38 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -56,7 +56,7 @@ impl DidVerifierMap { self.map .get(method) .ok_or_else(|| Error::VerifyingError { - msg: format!("Unrecognized DID method, {}", method).to_string(), + msg: format!("Unrecognized DID method, {}", method), })? .verify(identifier, payload, signature) .map_err(|e| Error::VerifyingError { msg: e.to_string() }) diff --git a/src/ucan.rs b/src/ucan.rs index df8ec0bf..4bf5f309 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -8,7 +8,7 @@ use crate::{ error::Error, semantics::{ability::Ability, resource::Resource}, store::Store, - CidString, DefaultFact, Did, + CidString, DefaultFact, }; use cid::{ multihash::{self, MultihashDigest}, @@ -138,7 +138,7 @@ where /// perform the ability against the resource pub fn capabilities_for( &self, - issuer: Did, + issuer: impl AsRef, resource: R, ability: A, at_time: u64, @@ -150,6 +150,8 @@ where A: Ability, S: Store, { + let issuer = issuer.as_ref(); + let mut capabilities = vec![]; let mut proof_queue: VecDeque<(Ucan, Capability, Capability)> = VecDeque::default(); @@ -462,6 +464,35 @@ where } } +impl<'de, F, C> Deserialize<'de> for Ucan +where + C: CapabilityParser, + F: DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ucan::::from_str(&String::deserialize(deserializer)?) + .map_err(|e| serde::de::Error::custom(e.to_string())) + } +} + +impl Serialize for Ucan +where + C: CapabilityParser, + F: Clone + DeserializeOwned, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.encode() + .map_err(|e| serde::ser::Error::custom(e.to_string()))? + .serialize(serializer) + } +} + fn deserialize_required_nullable<'de, T, D>(deserializer: D) -> Result where T: Deserialize<'de>, From 7da838ec43e617c95b5ad6defa53147ed15f1caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Wed, 18 Oct 2023 19:03:17 +0200 Subject: [PATCH 038/234] fix: Encode `EmptyCaveat` as `{}` instead of `null` --- src/semantics/caveat.rs | 43 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs index e6d6e5fa..88ca019b 100644 --- a/src/semantics/caveat.rs +++ b/src/semantics/caveat.rs @@ -5,7 +5,7 @@ use std::fmt; use downcast_rs::{impl_downcast, Downcast}; use dyn_clone::{clone_trait_object, DynClone}; use erased_serde::serialize_trait_object; -use serde::{Deserialize, Serialize}; +use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize}; /// A caveat defined as part of a semantics pub trait Caveat: Send + Sync + DynClone + Downcast + erased_serde::Serialize + 'static { @@ -27,7 +27,7 @@ impl fmt::Debug for dyn Caveat { } /// A caveat that is always valid -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct EmptyCaveat; impl Caveat for EmptyCaveat { @@ -53,3 +53,42 @@ impl Caveat for Box { (**self).is_valid_attenuation(other) } } + +impl<'de> Deserialize<'de> for EmptyCaveat { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct NoFieldsVisitor; + + impl<'de> Visitor<'de> for NoFieldsVisitor { + type Value = EmptyCaveat; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an empty object") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + if let Some(field) = map.next_key()? { + return Err(serde::de::Error::unknown_field(field, &[])); + } + + Ok(EmptyCaveat) + } + } + + deserializer.deserialize_map(NoFieldsVisitor) + } +} + +impl Serialize for EmptyCaveat { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_map(Some(0))?.end() + } +} From ef72ace285fde831ddb7e8696ca016feaa409091 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 23 Oct 2023 12:51:22 -0700 Subject: [PATCH 039/234] test: add test for serializing / deserializing empty caveats --- src/semantics/caveat.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs index 88ca019b..836d5023 100644 --- a/src/semantics/caveat.rs +++ b/src/semantics/caveat.rs @@ -92,3 +92,34 @@ impl Serialize for EmptyCaveat { serializer.serialize_map(Some(0))?.end() } } + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn test_serialize_empty_caveat() { + let caveat = EmptyCaveat; + let serialized = serde_json::to_string(&caveat).unwrap(); + + assert_eq!(serialized, "{}"); + } + + #[test] + fn test_deserialize_empty_caveat() { + let deserialized: EmptyCaveat = serde_json::from_value(json!({})).unwrap(); + + assert_eq!(deserialized, EmptyCaveat); + } + + #[test] + fn test_deserialize_empty_caveat_unexpected_fields() { + let deserialized: Result = serde_json::from_value(json!({ + "foo": true + })); + + assert!(deserialized.is_err()); + } +} From 5d272de3b49c233448d1e460812895c7d7f671b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 24 Oct 2023 16:07:46 +0200 Subject: [PATCH 040/234] feat: Allow the same resource scheme for multiple plugins --- src/capability.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/capability.rs b/src/capability.rs index 6701feb7..de4377ad 100644 --- a/src/capability.rs +++ b/src/capability.rs @@ -147,28 +147,28 @@ impl CapabilityParser for PluginCapability { ) -> Result, anyhow::Error> { let resource_scheme = resource_uri.scheme(); - let Some(plugin) = crate::plugins::plugins().find(|p| p.scheme() == resource_scheme) else { - return Ok(None); - }; - - let Some(resource) = plugin.try_handle_resource(resource_uri)? else { - return Ok(None); - }; - - let Some(ability) = plugin.try_handle_ability(&resource, ability)? else { - return Ok(None); - }; - - let Some(caveat) = plugin.try_handle_caveat(&resource, &ability, caveat_deserializer)? - else { - return Ok(None); - }; + for plugin in crate::plugins::plugins().filter(|p| p.scheme() == resource_scheme) { + let Some(resource) = plugin.try_handle_resource(resource_uri)? else { + continue; + }; + + let Some(ability) = plugin.try_handle_ability(&resource, ability)? else { + continue; + }; + + let Some(caveat) = plugin.try_handle_caveat(&resource, &ability, caveat_deserializer)? + else { + continue; + }; + + return Ok(Some(Capability { + resource, + ability, + caveat, + })); + } - Ok(Some(Capability { - resource, - ability, - caveat, - })) + Ok(None) } } From a4d93f12de7993e0927979c64a06981913f4e3ff Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 25 Oct 2023 16:41:17 -0700 Subject: [PATCH 041/234] refactor: compute the issuer DID from the Signer automatically --- src/builder.rs | 37 +++++++------------------------------ src/crypto.rs | 10 +++++++++- src/crypto/eddsa.rs | 18 +++++++++++++++++- src/ucan.rs | 33 +++++++++------------------------ 4 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0ccae2f9..4d2a30a1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -6,7 +6,7 @@ use signature::Signer; use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, - crypto::JWSSignature, + crypto::{JWSSignature, SignerDid}, error::Error, ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, CidString, DefaultFact, @@ -19,7 +19,6 @@ pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; #[derive(Debug, Clone)] pub struct UcanBuilder { version: Option, - issuer: Option, audience: Option, nonce: Option, capabilities: Capabilities, @@ -34,7 +33,6 @@ impl Default for UcanBuilder { fn default() -> Self { Self { version: Default::default(), - issuer: Default::default(), audience: Default::default(), nonce: Default::default(), capabilities: Default::default(), @@ -58,12 +56,6 @@ where self } - /// Set the issuer of the UCAN - pub fn issued_by(mut self, issuer: impl AsRef) -> Self { - self.issuer = Some(issuer.as_ref().to_string()); - self - } - /// Set the audience of the UCAN pub fn for_audience(mut self, audience: impl AsRef) -> Self { self.audience = Some(audience.as_ref().to_string()); @@ -139,16 +131,14 @@ where /// Sign the UCAN with the given signer pub fn sign(self, signer: &S) -> Result, Error> where - S: Signer, + S: Signer + SignerDid, K: JWSSignature, { let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); - let Some(issuer) = self.issuer else { - return Err(Error::SigningError { - msg: "an issuer is required".to_string(), - }); - }; + let issuer = signer.did().map_err(|e| Error::SigningError { + msg: format!("failed to construct DID, {}", e), + })?; let Some(audience) = self.audience else { return Err(Error::SigningError { @@ -203,10 +193,8 @@ where #[cfg(test)] mod tests { - use std::str::FromStr; - - use multibase::Base; use signature::rand_core; + use std::str::FromStr; use crate::did_verifier::DidVerifierMap; @@ -220,8 +208,7 @@ mod tests { let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let ucan: Ucan = UcanBuilder::default() - .issued_by(ed25519_to_did(iss_key.verifying_key())) - .for_audience(ed25519_to_did(aud_key.verifying_key())) + .for_audience(aud_key.did()?) .sign(&iss_key)?; let token = ucan.encode()?; @@ -231,14 +218,4 @@ mod tests { Ok(()) } - - fn ed25519_to_did(key: ed25519_dalek::VerifyingKey) -> String { - format!( - "did:key:{}", - multibase::encode( - Base::Base58Btc, - &[&[0xed, 0x01], key.to_bytes().as_ref()].concat() - ) - ) - } } diff --git a/src/crypto.rs b/src/crypto.rs index 8beb5e27..055ef1b5 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,6 @@ //! Cryptography utilities -use signature::SignatureEncoding; +use signature::{SignatureEncoding, Signer}; pub mod bls; pub mod eddsa; @@ -18,3 +18,11 @@ pub trait JWSSignature: SignatureEncoding { // unspecified algorithms, like BLS, means leaving things more open-ended. const ALGORITHM: &'static str; } + +/// A trait for mapping a Signer to its DID. In most cases, this will +/// be a DID with method did-key, however other methods can be supported +/// by implementing this trait for a custom signer. +pub trait SignerDid: Signer { + /// The DID of the signer + fn did(&self) -> Result; +} diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs index ee33302c..7f764576 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -1,14 +1,30 @@ //! EdDSA signature support use anyhow::anyhow; +use multibase::Base; use signature::Verifier; -use super::JWSSignature; +use super::{JWSSignature, SignerDid}; impl JWSSignature for ed25519::Signature { const ALGORITHM: &'static str = "EdDSA"; } +impl SignerDid for ed25519_dalek::SigningKey { + fn did(&self) -> Result { + let mut buf = unsigned_varint::encode::u128_buffer(); + let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); + + Ok(format!( + "did:key:{}", + multibase::encode( + Base::Base58Btc, + [multicodec, self.verifying_key().to_bytes().as_ref()].concat() + ) + )) + } +} + /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = ed25519_dalek::VerifyingKey::try_from(key) diff --git a/src/ucan.rs b/src/ucan.rs index 4bf5f309..350aaa3c 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -504,12 +504,11 @@ where #[cfg(test)] mod tests { - - use multibase::Base; use signature::rand_core; use crate::{ builder::UcanBuilder, + crypto::SignerDid, did_verifier::DidVerifierMap, plugins::wnfs::{WnfsAbility, WnfsResource}, semantics::{ability::TopAbility, caveat::EmptyCaveat}, @@ -527,12 +526,11 @@ mod tests { let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let ucan: Ucan = UcanBuilder::default() - .issued_by(ed25519_to_did(iss_key.verifying_key())) - .for_audience(ed25519_to_did(aud_key.verifying_key())) + .for_audience(aud_key.did()?) .sign(&iss_key)?; let capabilities = ucan.capabilities_for( - ed25519_to_did(iss_key.verifying_key()), + iss_key.did()?, WnfsResource::PublicPath { user: "alice".to_string(), path: vec!["photos".to_string()], @@ -557,8 +555,7 @@ mod tests { let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let ucan: Ucan = UcanBuilder::default() - .issued_by(ed25519_to_did(iss_key.verifying_key())) - .for_audience(ed25519_to_did(aud_key.verifying_key())) + .for_audience(aud_key.did()?) .claiming_capability(Capability::new( WnfsResource::PublicPath { user: "alice".to_string(), @@ -570,7 +567,7 @@ mod tests { .sign(&iss_key)?; let capabilities = ucan.capabilities_for( - ed25519_to_did(iss_key.verifying_key()), + iss_key.did()?, WnfsResource::PublicPath { user: "alice".to_string(), path: vec!["photos".to_string()], @@ -613,8 +610,7 @@ mod tests { let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let ucan: Ucan = UcanBuilder::default() - .issued_by(ed25519_to_did(iss_key.verifying_key())) - .for_audience(ed25519_to_did(aud_key.verifying_key())) + .for_audience(aud_key.did()?) .claiming_capability(Capability::new( WnfsResource::PublicPath { user: "alice".to_string(), @@ -626,7 +622,7 @@ mod tests { .sign(&iss_key)?; let capabilities = ucan.capabilities_for( - ed25519_to_did(iss_key.verifying_key()), + iss_key.did()?, WnfsResource::PublicPath { user: "alice".to_string(), path: vec!["photos".to_string(), "vacation".to_string()], @@ -669,8 +665,7 @@ mod tests { let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); let ucan: Ucan = UcanBuilder::default() - .issued_by(ed25519_to_did(iss_key.verifying_key())) - .for_audience(ed25519_to_did(aud_key.verifying_key())) + .for_audience(aud_key.did()?) .claiming_capability(Capability::new( WnfsResource::PublicPath { user: "alice".to_string(), @@ -682,7 +677,7 @@ mod tests { .sign(&iss_key)?; let capabilities = ucan.capabilities_for( - ed25519_to_did(iss_key.verifying_key()), + iss_key.did()?, WnfsResource::PublicPath { user: "alice".to_string(), path: vec!["photos".to_string(), "vacation".to_string()], @@ -715,14 +710,4 @@ mod tests { Ok(()) } - - fn ed25519_to_did(key: ed25519_dalek::VerifyingKey) -> String { - format!( - "did:key:{}", - multibase::encode( - Base::Base58Btc, - &[&[0xed, 0x01], key.to_bytes().as_ref()].concat() - ) - ) - } } From 5cad95019773e112993c176364a4260da30f3ed4 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 25 Oct 2023 17:12:33 -0700 Subject: [PATCH 042/234] fix: honor UCAN lifetimes --- src/builder.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 4d2a30a1..5a128b40 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,6 +8,7 @@ use crate::{ capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, crypto::{JWSSignature, SignerDid}, error::Error, + time, ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, CidString, DefaultFact, }; @@ -152,11 +153,22 @@ where }) .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + let expiration = match (self.expiration, self.lifetime) { + (None, None) => None, + (None, Some(lifetime)) => Some(time::now() + lifetime), + (Some(expiration), None) => Some(expiration), + (Some(_), Some(_)) => { + return Err(Error::SigningError { + msg: "only one of expiration or lifetime may be set".to_string(), + }) + } + }; + let payload = jose_b64::serde::Json::new(UcanPayload { ucv: version, iss: issuer, aud: audience, - exp: self.expiration, + exp: expiration, nbf: self.not_before, nnc: self.nonce, cap: self.capabilities, From f9d56a674f38d5fa63907dd345a3b19555b6820c Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 25 Oct 2023 17:13:28 -0700 Subject: [PATCH 043/234] test: test querying the capabilities for an invocation proof chain --- src/store.rs | 13 +++++-- src/ucan.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++ tests/conformance.rs | 10 +---- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/store.rs b/src/store.rs index aab9eff7..8e22f725 100644 --- a/src/store.rs +++ b/src/store.rs @@ -10,6 +10,8 @@ use libipld_core::{ }; use multihash::MultihashDigest; +use crate::builder::DEFAULT_MULTIHASH; + /// A store for persisting UCAN tokens, to be referencable as proofs by other UCANs pub trait Store where @@ -24,7 +26,7 @@ where T: Decode; /// Write a token to the store, using the specified hasher - fn write(&mut self, token: T, hasher: multihash::Code) -> Result + fn write(&mut self, token: T, hasher: Option) -> Result where T: Encode; } @@ -45,7 +47,11 @@ where T: Decode; /// Write a token to the store, using the specified hasher - async fn write(&mut self, token: T, hasher: multihash::Code) -> Result + async fn write( + &mut self, + token: T, + hasher: Option, + ) -> Result where T: Encode + Send; } @@ -70,10 +76,11 @@ impl Store for InMemoryStore { } } - fn write(&mut self, token: T, hasher: multihash::Code) -> Result + fn write(&mut self, token: T, hasher: Option) -> Result where T: Encode, { + let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); let block = RawCodec.encode(&token)?; let digest = hasher.digest(&block); let cid = Cid::new_v1(RawCodec.into(), digest); diff --git a/src/ucan.rs b/src/ucan.rs index 350aaa3c..c475b0f4 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -513,6 +513,7 @@ mod tests { plugins::wnfs::{WnfsAbility, WnfsResource}, semantics::{ability::TopAbility, caveat::EmptyCaveat}, store::InMemoryStore, + time, }; use super::*; @@ -710,4 +711,90 @@ mod tests { Ok(()) } + + #[test] + fn test_capabilities_for_invocation() -> Result<(), anyhow::Error> { + let mut store = InMemoryStore::::default(); + let did_verifier_map = DidVerifierMap::default(); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let root_ucan: Ucan = UcanBuilder::default() + .for_audience(aud_key.did()?) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat, + )) + .with_lifetime(60) + .sign(&iss_key)?; + + store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; + + let invocation: Ucan = UcanBuilder::default() + .for_audience("did:web:fission.codes") + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + EmptyCaveat, + )) + .witnessed_by(&root_ucan, None) + .sign(&aud_key)?; + + let capabilities = invocation.capabilities_for( + iss_key.did()?, + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + time::now(), + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 1); + + assert_eq!( + capabilities[0].resource().downcast_ref::(), + Some(&WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }) + ); + + assert_eq!( + capabilities[0].ability().downcast_ref::(), + Some(&WnfsAbility::Revise) + ); + + assert_eq!( + capabilities[0].caveat().downcast_ref::(), + Some(&EmptyCaveat) + ); + + let capabilities = invocation.capabilities_for( + iss_key.did()?, + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + // Past the lifetime of the root UCAN + time::now() + 61, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 0); + + Ok(()) + } } diff --git a/tests/conformance.rs b/tests/conformance.rs index a5721677..6b637286 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -169,10 +169,7 @@ impl TestTask for VerifyTest { for (_cid, token) in self.inputs.proofs.iter() { store - .write( - Ipld::Bytes(token.as_bytes().to_vec()), - multihash::Code::Sha2_256, - ) + .write(Ipld::Bytes(token.as_bytes().to_vec()), None) .unwrap(); } @@ -315,10 +312,7 @@ impl TestTask for RefuteTest { for (_cid, token) in self.inputs.proofs.iter() { store - .write( - Ipld::Bytes(token.as_bytes().to_vec()), - multihash::Code::Sha2_256, - ) + .write(Ipld::Bytes(token.as_bytes().to_vec()), None) .unwrap(); } From 28345a45bf664b334ccae54843c2987dfa42363f Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Thu, 26 Oct 2023 13:04:08 -0700 Subject: [PATCH 044/234] chore: remove cargo-spellcheck from nix.flake Something has changed upstream that's causing issues building the package on darwin. I haven't found a fix yet, so I'm removing the package for now. For future reference, the error was: ``` > = note: ld: framework not found SystemConfiguration > clang-11: error: linker command failed with exit code 1 (use -v to see invocation) > > > error: could not compile `cargo-spellcheck` (build script) due to previous error ``` --- flake.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/flake.nix b/flake.nix index d931932b..06023a78 100644 --- a/flake.nix +++ b/flake.nix @@ -45,7 +45,6 @@ cargo-expand cargo-nextest cargo-outdated - cargo-spellcheck cargo-sort cargo-udeps cargo-watch From 827a7cde982a0ac89cb026e36e74706c9e18d802 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Thu, 26 Oct 2023 13:10:37 -0700 Subject: [PATCH 045/234] chore: run `nix flake update` --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 04f6cbbe..29249c58 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1695806987, - "narHash": "sha256-fX5kGs66NZIxCMcpAGIpxuftajHL8Hil1vjHmjjl118=", + "lastModified": 1698266953, + "narHash": "sha256-jf72t7pC8+8h8fUslUYbWTX5rKsRwOzRMX8jJsGqDXA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f3dab3509afca932f3f4fd0908957709bb1c1f57", + "rev": "75a52265bda7fd25e06e3a67dee3f0354e73243c", "type": "github" }, "original": { @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1695780708, - "narHash": "sha256-+0difm874E5ra98MeLxW8SfoxfL+Wzn3cLzKGGd2I4M=", + "lastModified": 1698199907, + "narHash": "sha256-n8RtHBIb0rLuYs4RDehW6mj6r6Yam/ODY1af/VCcurw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e04538a3e155ebe4d15a281559119f63d33116bb", + "rev": "22b8d29fd22cfaa2c311e0d6fd8a0ed9c2a1152b", "type": "github" }, "original": { From 98ae17d53149d840bd5c6f719ed847d980133aae Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 10:58:58 -0700 Subject: [PATCH 046/234] chore: set crate-type to cdylib in prep for wasm builds --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6234052a..1660512a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ repository = "https://github.com/ucan-wg/rs-ucan" authors = ["Quinn Wilton "] [lib] +crate-type = ["cdylib", "rlib"] path = "src/lib.rs" bench = false From 0b2a2d78713d9088c0cbf0da1e5dfc037b19fdd2 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 11:23:02 -0700 Subject: [PATCH 047/234] refactor: gate static plugin registration behind the target There's likely a better way to do this, so that we're able to disable the feature if desired, or for other environments, like in no-std embedded targets. A feature could help with me, but I'd like to have it enabled by default, and default-features brings with it some other ergonomics concerns, so this is as good a starting point as any. --- Cargo.toml | 6 +++++- src/lib.rs | 1 + src/plugins.rs | 42 +++++++++++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1660512a..1f1a5197 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ path = "examples/counterparts.rs" anyhow = "1.0.75" async-trait = "0.1.73" blst = "0.3.11" +cfg-if = "0.1" cid = "0.10.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" @@ -43,7 +44,6 @@ jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = "0.13.1" lazy_static = "1.4.0" libipld-core = "0.16.0" -linkme = "0.3.15" multibase = "0.9.1" p256 = "0.13.2" p384 = "0.13.0" @@ -60,6 +60,10 @@ tracing = "0.1" unsigned-varint = "0.7.2" url = "2.4.1" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +# `linkme` relies on linker features that aren't available in wasm32 +linkme = "0.3.15" + [dev-dependencies] criterion = "0.4" multihash = "0.18.0" diff --git a/src/lib.rs b/src/lib.rs index bb728e91..6d0118d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod time; pub mod ucan; #[doc(hidden)] +#[cfg(not(target_arch = "wasm32"))] pub use linkme; /// A decentralized identifier. diff --git a/src/plugins.rs b/src/plugins.rs index f86d5994..6f19c827 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -1,10 +1,11 @@ //! Plugins for definining custom semantics -use core::fmt; -use std::sync::RwLock; +#[cfg(not(target_arch = "wasm32"))] +use linkme::distributed_slice; +use core::fmt; use downcast_rs::{impl_downcast, Downcast}; -use linkme::distributed_slice; +use std::sync::RwLock; use url::Url; use crate::{ @@ -19,6 +20,7 @@ use crate::{ pub mod ucan; pub mod wnfs; +#[cfg(not(target_arch = "wasm32"))] #[distributed_slice] #[doc(hidden)] pub static STATIC_PLUGINS: [&dyn Plugin< @@ -188,14 +190,24 @@ pub fn plugins() -> impl Iterator< Error = Error, >, > { - let static_plugins = STATIC_PLUGINS.iter().copied(); - let runtime_plugins = RUNTIME_PLUGINS - .read() - .expect("plugin lock poisoned") - .clone() - .into_iter(); - - static_plugins.chain(runtime_plugins) + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + RUNTIME_PLUGINS + .read() + .expect("plugin lock poisoned") + .clone() + .into_iter() + } else { + let static_plugins = STATIC_PLUGINS.iter().copied(); + let runtime_plugins = RUNTIME_PLUGINS + .read() + .expect("plugin lock poisoned") + .clone() + .into_iter(); + + static_plugins.chain(runtime_plugins) + } + } } /// Register a plugin @@ -217,6 +229,7 @@ pub fn register_plugin( } /// Register a plugin at compile time +#[cfg(not(target_arch = "wasm32"))] #[macro_export] macro_rules! register_plugin { ($name:ident, $plugin:expr) => { @@ -229,3 +242,10 @@ macro_rules! register_plugin { > = &$crate::plugins::WrappedPlugin { inner: $plugin }; }; } + +/// Register a plugin at compile time +#[cfg(target_arch = "wasm32")] +#[macro_export] +macro_rules! register_plugin { + ($name:ident, $plugin:expr) => {}; +} From e6a64d3b84883d5f35992f3bef1391c4c42ca989 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 11:34:15 -0700 Subject: [PATCH 048/234] chore: remove unused `tracing` crate --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1f1a5197..77de614c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ serde_json = "1.0.107" sha2 = "0.10.8" signature = "2.1.0" thiserror = "1.0" -tracing = "0.1" unsigned-varint = "0.7.2" url = "2.4.1" From 0257b3b8f274bab4e94df323ed133ac35ddbba2b Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 11:44:08 -0700 Subject: [PATCH 049/234] refactor: gate support for each algorithm behind features This is intended to reduce the code-size for wasm builds, by allowing for minimal builds that only include support for the required algorithms. This also uses rexported versions of types from the `sha2` and and `ecdsa` crates, allowing us to remove those as direct dependencies. --- Cargo.toml | 43 ++++++++++++++---------- src/crypto.rs | 8 +++++ src/crypto/bls.rs | 2 ++ src/crypto/eddsa.rs | 6 +++- src/crypto/es256.rs | 5 ++- src/crypto/es256k.rs | 5 ++- src/crypto/es384.rs | 5 ++- src/crypto/ps256.rs | 5 ++- src/crypto/rs256.rs | 5 ++- src/did_verifier/did_key.rs | 65 ++++++++++++++++++++++++++++++------- 10 files changed, 115 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 77de614c..9be93503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,31 +30,30 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" async-trait = "0.1.73" -blst = "0.3.11" +blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" cid = "0.10.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" -ecdsa = "0.16.8" -ed25519 = "2.2.2" -ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } +ecdsa = { version = "0.16.8", optional = true, default-features = false } +ed25519 = { version = "2.2.2", optional = true, default-features = false } +ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } erased-serde = "0.3.31" instant = "0.1.12" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } -k256 = "0.13.1" +k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" libipld-core = "0.16.0" multibase = "0.9.1" -p256 = "0.13.2" -p384 = "0.13.0" -p521 = "0.13.0" +p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } +p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } +p521 = { version = "0.13.0", optional = true, default-features = false } proptest = { version = "1.1", optional = true } -rsa = { version = "0.9.2", features = ["sha2"] } +rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" -sha2 = "0.10.8" -signature = "2.1.0" +signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" unsigned-varint = "0.7.2" url = "2.4.1" @@ -72,12 +71,22 @@ proptest = "1.1" default = ["did-key", "eddsa-verifier", "es256-verifier", "es256k-verifier", "es384-verifier", "ps256-verifier", "rs256-verifier"] test_utils = ["proptest"] did-key = [] -eddsa-verifier = [] -es256-verifier = [] -es256k-verifier = [] -es384-verifier = [] -ps256-verifier = [] -rs256-verifier = [] +eddsa = ["dep:ed25519", "dep:ed25519-dalek"] +es256 = ["dep:p256"] +es256k = ["dep:k256"] +es384 = ["dep:p384"] +es512 = ["dep:ecdsa", "dep:p521"] +ps256 = ["dep:rsa"] +rs256 = ["dep:rsa"] +bls = ["dep:blst"] +eddsa-verifier = ["eddsa"] +es256-verifier = ["es256"] +es256k-verifier = ["es256k"] +es384-verifier = ["es384"] +es512-verifier = ["es512"] +ps256-verifier = ["ps256"] +rs256-verifier = ["rs256"] +bls-verifier = ["bls"] [metadata.docs.rs] all-features = true diff --git a/src/crypto.rs b/src/crypto.rs index 055ef1b5..cd81a6cc 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,13 +2,21 @@ use signature::{SignatureEncoding, Signer}; +#[cfg(feature = "bls")] pub mod bls; +#[cfg(feature = "eddsa")] pub mod eddsa; +#[cfg(feature = "es256")] pub mod es256; +#[cfg(feature = "es256k")] pub mod es256k; +#[cfg(feature = "es384")] pub mod es384; +#[cfg(feature = "es512")] pub mod es512; +#[cfg(feature = "ps256")] pub mod ps256; +#[cfg(feature = "rs256")] pub mod rs256; /// A trait for mapping a SignatureEncoding to its algorithm name under JWS diff --git a/src/crypto/bls.rs b/src/crypto/bls.rs index 5067da75..f7362b62 100644 --- a/src/crypto/bls.rs +++ b/src/crypto/bls.rs @@ -59,6 +59,7 @@ impl JWSSignature for Bls12381G2Sha256SswuRoNulSignature { } /// A verifier for BLS12-381 G1 signatures +#[cfg(feature = "bls-verifier")] pub fn bls_12_381_g1_sha256_sswu_ro_nul_verifier( key: &[u8], payload: &[u8], @@ -86,6 +87,7 @@ pub fn bls_12_381_g1_sha256_sswu_ro_nul_verifier( } /// A verifier for BLS12-381 G2 signatures +#[cfg(feature = "bls-verifier")] pub fn bls_12_381_g2_sha256_sswu_ro_nul_verifier( key: &[u8], payload: &[u8], diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs index 7f764576..6006e00b 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -1,9 +1,12 @@ //! EdDSA signature support +#[cfg(feature = "eddsa-verifier")] use anyhow::anyhow; -use multibase::Base; +#[cfg(feature = "eddsa-verifier")] use signature::Verifier; +use multibase::Base; + use super::{JWSSignature, SignerDid}; impl JWSSignature for ed25519::Signature { @@ -26,6 +29,7 @@ impl SignerDid for ed25519_dalek::SigningKey { } /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate +#[cfg(feature = "eddsa-verifier")] pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = ed25519_dalek::VerifyingKey::try_from(key) .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs index c3800a95..48f030ca 100644 --- a/src/crypto/es256.rs +++ b/src/crypto/es256.rs @@ -1,15 +1,18 @@ //! ES256 signature support +#[cfg(feature = "es256-verifier")] use anyhow::anyhow; +#[cfg(feature = "es256-verifier")] use signature::Verifier; use super::JWSSignature; -impl JWSSignature for ecdsa::Signature { +impl JWSSignature for p256::ecdsa::Signature { const ALGORITHM: &'static str = "ES256"; } /// A verifier for PS256 signatures +#[cfg(feature = "es256-verifier")] pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs index a11c26dc..e521b50e 100644 --- a/src/crypto/es256k.rs +++ b/src/crypto/es256k.rs @@ -1,15 +1,18 @@ //! ES256K signature support +#[cfg(feature = "es256k-verifier")] use anyhow::anyhow; +#[cfg(feature = "es256k-verifier")] use signature::Verifier; use super::JWSSignature; -impl JWSSignature for ecdsa::Signature { +impl JWSSignature for k256::ecdsa::Signature { const ALGORITHM: &'static str = "ES256K"; } /// A verifier for ES256k signatures +#[cfg(feature = "es256k-verifier")] pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs index d4735632..8f8687dc 100644 --- a/src/crypto/es384.rs +++ b/src/crypto/es384.rs @@ -1,15 +1,18 @@ //! ES384 signature support +#[cfg(feature = "es384-verifier")] use anyhow::anyhow; +#[cfg(feature = "es384-verifier")] use signature::Verifier; use super::JWSSignature; -impl JWSSignature for ecdsa::Signature { +impl JWSSignature for p384::ecdsa::Signature { const ALGORITHM: &'static str = "ES384"; } /// A verifier for ES384 signatures +#[cfg(feature = "es384-verifier")] pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs index 438dd377..547548d7 100644 --- a/src/crypto/ps256.rs +++ b/src/crypto/ps256.rs @@ -1,6 +1,8 @@ //! PS256 signature support +#[cfg(feature = "ps256-verifier")] use anyhow::anyhow; +#[cfg(feature = "ps256-verifier")] use signature::Verifier; use super::JWSSignature; @@ -10,11 +12,12 @@ impl JWSSignature for rsa::pss::Signature { } /// A verifier for RS256 signatures +#[cfg(feature = "ps256-verifier")] pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; - let key = rsa::pss::VerifyingKey::::new(key); + let key = rsa::pss::VerifyingKey::::new(key); let signature = rsa::pss::Signature::try_from(signature) .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index da373703..9f39c1ff 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -1,6 +1,8 @@ //! RS256 signature support +#[cfg(feature = "rs256-verifier")] use anyhow::anyhow; +#[cfg(feature = "rs256-verifier")] use signature::Verifier; use super::JWSSignature; @@ -10,11 +12,12 @@ impl JWSSignature for rsa::pkcs1v15::Signature { } /// A verifier for RS256 signatures +#[cfg(feature = "rs256-verifier")] pub fn rs256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; - let key = rsa::pkcs1v15::VerifyingKey::::new(key); + let key = rsa::pkcs1v15::VerifyingKey::::new(key); let signature = rsa::pkcs1v15::Signature::try_from(signature) .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs index 2ea0bc53..4c2f658f 100644 --- a/src/did_verifier/did_key.rs +++ b/src/did_verifier/did_key.rs @@ -1,16 +1,24 @@ //! did:key method verifier +#[cfg(feature = "eddsa-verifier")] +use crate::crypto::eddsa::eddsa_verifier; +#[cfg(feature = "es256-verifier")] +use crate::crypto::es256::es256_verifier; +#[cfg(feature = "es256k-verifier")] +use crate::crypto::es256k::es256k_verifier; +#[cfg(feature = "es384-verifier")] +use crate::crypto::es384::es384_verifier; +#[cfg(feature = "ps256-verifier")] +use crate::crypto::ps256::ps256_verifier; +#[cfg(feature = "rs256-verifier")] +use crate::crypto::rs256::rs256_verifier; + use core::fmt; use std::{any::TypeId, collections::HashMap}; use anyhow::anyhow; use multibase::Base; -use crate::crypto::{ - eddsa::eddsa_verifier, es256::es256_verifier, es256k::es256k_verifier, es384::es384_verifier, - ps256::ps256_verifier, rs256::rs256_verifier, -}; - use super::DidVerifier; /// A closure for verifying a signature @@ -24,6 +32,7 @@ pub struct DidKeyVerifier { impl Default for DidKeyVerifier { fn default() -> Self { + #[allow(unused_mut)] let mut did_key_verifier = Self { verifier_map: HashMap::new(), }; @@ -32,13 +41,13 @@ impl Default for DidKeyVerifier { did_key_verifier.set::(eddsa_verifier); #[cfg(feature = "es256-verifier")] - did_key_verifier.set::, _>(es256_verifier); + did_key_verifier.set::(es256_verifier); #[cfg(feature = "es256k-verifier")] - did_key_verifier.set::, _>(es256k_verifier); + did_key_verifier.set::(es256k_verifier); #[cfg(feature = "es384-verifier")] - did_key_verifier.set::, _>(es384_verifier); + did_key_verifier.set::(es384_verifier); #[cfg(feature = "ps256-verifier")] did_key_verifier.set::(ps256_verifier); @@ -94,24 +103,33 @@ impl DidVerifier for DidKeyVerifier { multicodec_pub_key.validate_pub_key_len(public_key)?; + #[allow(unreachable_patterns)] let verifier = match multicodec_pub_key { + #[cfg(feature = "es256k")] MulticodecPubKey::Secp256k1Compressed => self .verifier_map - .get(&TypeId::of::>()), + .get(&TypeId::of::()), + #[cfg(feature = "eddsa")] MulticodecPubKey::X25519 => return Err(anyhow!("x25519 not supported for signing")), + #[cfg(feature = "eddsa")] MulticodecPubKey::Ed25519 => self.verifier_map.get(&TypeId::of::()), + #[cfg(feature = "es256")] MulticodecPubKey::P256Compressed => self .verifier_map - .get(&TypeId::of::>()), + .get(&TypeId::of::()), + #[cfg(feature = "es384")] MulticodecPubKey::P384Compressed => self .verifier_map - .get(&TypeId::of::>()), + .get(&TypeId::of::()), + #[cfg(feature = "es521")] MulticodecPubKey::P521Compressed => self .verifier_map .get(&TypeId::of::>()), + #[cfg(feature = "rs256")] MulticodecPubKey::RSAPKCS1 => self .verifier_map .get(&TypeId::of::()), + _ => Option::<&Box>::None, } .ok_or_else(|| anyhow!("no registered verifier for signature type"))?; @@ -129,24 +147,34 @@ impl fmt::Debug for DidKeyVerifier { #[derive(Debug)] pub enum MulticodecPubKey { /// secp256k1 compressed public key + #[cfg(feature = "es256k")] Secp256k1Compressed, /// x25519 public key + #[cfg(feature = "eddsa")] X25519, /// ed25519 public key + #[cfg(feature = "eddsa")] Ed25519, /// p256 compressed public key + #[cfg(feature = "es256")] P256Compressed, /// p384 compressed public key + #[cfg(feature = "es384")] P384Compressed, /// p521 compressed public key + #[cfg(feature = "es521")] P521Compressed, /// rsa pkcs1 public key + #[cfg(feature = "rs256")] RSAPKCS1, } impl MulticodecPubKey { + #[allow(unused_variables)] fn validate_pub_key_len(&self, pub_key: &[u8]) -> Result<(), anyhow::Error> { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "es256k")] MulticodecPubKey::Secp256k1Compressed => { if pub_key.len() != 33 { return Err(anyhow!( @@ -155,6 +183,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "eddsa")] MulticodecPubKey::X25519 => { if pub_key.len() != 32 { return Err(anyhow!( @@ -163,6 +192,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "eddsa")] MulticodecPubKey::Ed25519 => { if pub_key.len() != 32 { return Err(anyhow!( @@ -171,6 +201,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "es256")] MulticodecPubKey::P256Compressed => { if pub_key.len() != 33 { return Err(anyhow!( @@ -179,6 +210,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "es384")] MulticodecPubKey::P384Compressed => { if pub_key.len() != 49 { return Err(anyhow!( @@ -187,6 +219,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "es521")] MulticodecPubKey::P521Compressed => { if pub_key.len() > 67 { return Err(anyhow!( @@ -195,6 +228,7 @@ impl MulticodecPubKey { )); } } + #[cfg(feature = "rs256")] MulticodecPubKey::RSAPKCS1 => match pub_key.len() { 94 | 126 | 162 | 226 | 294 | 422 | 546 => {} n => { @@ -204,8 +238,10 @@ impl MulticodecPubKey { )); } }, + _ => return Err(anyhow!("unsupported public key type")), }; + #[allow(unreachable_code)] Ok(()) } } @@ -215,12 +251,19 @@ impl TryFrom for MulticodecPubKey { fn try_from(value: u128) -> Result { match value { + #[cfg(feature = "es256k")] 0xe7 => Ok(MulticodecPubKey::Secp256k1Compressed), + #[cfg(feature = "eddsa")] 0xec => Ok(MulticodecPubKey::X25519), + #[cfg(feature = "eddsa")] 0xed => Ok(MulticodecPubKey::Ed25519), + #[cfg(feature = "es256")] 0x1200 => Ok(MulticodecPubKey::P256Compressed), + #[cfg(feature = "es384")] 0x1201 => Ok(MulticodecPubKey::P384Compressed), + #[cfg(feature = "es521")] 0x1202 => Ok(MulticodecPubKey::P521Compressed), + #[cfg(feature = "rs256")] 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), _ => Err(anyhow!("unsupported multicodec")), } From 537f4fe3d4a3ec95a7ed6c55ad5b48b86da23c8e Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 11:45:57 -0700 Subject: [PATCH 050/234] refactor: only register the did-key verifier if the feature is set --- src/did_verifier.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/did_verifier.rs b/src/did_verifier.rs index c840fd38..62c5ab59 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -5,8 +5,7 @@ use std::collections::HashMap; use crate::error::Error; -use self::did_key::DidKeyVerifier; - +#[cfg(feature = "did-key")] pub mod did_key; /// A map from did method to verifier @@ -17,12 +16,13 @@ pub struct DidVerifierMap { impl Default for DidVerifierMap { fn default() -> Self { + #[allow(unused_mut)] let mut did_verifier_map = Self { map: HashMap::new(), }; #[cfg(feature = "did-key")] - did_verifier_map.register(DidKeyVerifier::default()); + did_verifier_map.register(did_key::DidKeyVerifier::default()); did_verifier_map } From b874f0032c9ab19458c97c0330a258c8611c6d4a Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 11:47:25 -0700 Subject: [PATCH 051/234] refactor: configure dev dependencies for wasm32 support --- Cargo.toml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9be93503..2d9a571b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,10 +62,20 @@ url = "2.4.1" # `linkme` relies on linker features that aren't available in wasm32 linkme = "0.3.15" +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "*", features = ["js"] } + [dev-dependencies] -criterion = "0.4" multihash = "0.18.0" -proptest = "1.1" + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +criterion = "0.4" +proptest = { version = "*" } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +criterion = { version = "0.4", default-features = false } +proptest = { version = "*", default-features = false, features = ["std"] } +wasm-bindgen-test = "0.2" [features] default = ["did-key", "eddsa-verifier", "es256-verifier", "es256k-verifier", "es384-verifier", "ps256-verifier", "rs256-verifier"] From 074a18e5b66f347bfc9618ceec3c5029208c469e Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 12:10:40 -0700 Subject: [PATCH 052/234] chore: configure the flake to use clang from nixpkgs This allows us to build the blst crate for wasm32 targets from MacOS, which requires wasm32 support in clang, which is not included in the system version of clang provided by MacOS systems --- flake.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake.nix b/flake.nix index 06023a78..28c4a5cd 100644 --- a/flake.nix +++ b/flake.nix @@ -53,6 +53,10 @@ { devShells.default = pkgs.mkShell { name = "rs-ucan"; + + # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + stdenv = pkgs.clangStdenv; + nativeBuildInputs = with pkgs; [ # The ordering of these two items is important. For nightly rustfmt to be used instead of From a56c45069a1ecd22bad01e8a5210e3ec95dd0718 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 30 Oct 2023 12:12:16 -0700 Subject: [PATCH 053/234] chore: enable wasm32-unknown-unknown as a Rust target in the flake --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 28c4a5cd..6ca7e995 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,7 @@ rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { extensions = ["cargo" "clippy" "rustfmt" "rust-src" "rust-std"]; + targets = ["wasm32-unknown-unknown" "wasm32-wasi"]; }; nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; @@ -48,6 +49,7 @@ cargo-sort cargo-udeps cargo-watch + wasm-tools ]; in rec { From 069e2fcae7cd6b4623ff91097063e47654917ed1 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 13:35:03 -0800 Subject: [PATCH 054/234] refactor: remove K generic from SignerDid --- src/builder.rs | 2 +- src/crypto.rs | 4 ++-- src/crypto/eddsa.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 5a128b40..f4a62953 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -132,7 +132,7 @@ where /// Sign the UCAN with the given signer pub fn sign(self, signer: &S) -> Result, Error> where - S: Signer + SignerDid, + S: Signer + SignerDid, K: JWSSignature, { let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); diff --git a/src/crypto.rs b/src/crypto.rs index cd81a6cc..75009dbb 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,6 @@ //! Cryptography utilities -use signature::{SignatureEncoding, Signer}; +use signature::SignatureEncoding; #[cfg(feature = "bls")] pub mod bls; @@ -30,7 +30,7 @@ pub trait JWSSignature: SignatureEncoding { /// A trait for mapping a Signer to its DID. In most cases, this will /// be a DID with method did-key, however other methods can be supported /// by implementing this trait for a custom signer. -pub trait SignerDid: Signer { +pub trait SignerDid { /// The DID of the signer fn did(&self) -> Result; } diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs index 6006e00b..6bfc130f 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -13,7 +13,7 @@ impl JWSSignature for ed25519::Signature { const ALGORITHM: &'static str = "EdDSA"; } -impl SignerDid for ed25519_dalek::SigningKey { +impl SignerDid for ed25519_dalek::SigningKey { fn did(&self) -> Result { let mut buf = unsigned_varint::encode::u128_buffer(); let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); From 321555fd2d41ef90858e65cb89b3bcdbd7f7e557 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 13:37:43 -0800 Subject: [PATCH 055/234] chore: add some js related files to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ff787987..f408d360 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ private *.tmp .history .DS_Store +.wireit +.nyc_output +tests/report +node_modules +dist From 60b5b2cbdb9ff3de7f010638e774ece327f4ad2c Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 13:42:13 -0800 Subject: [PATCH 056/234] refactor: fall back to a default multihash if unspecified --- src/builder.rs | 5 ----- src/lib.rs | 5 ++++- src/store.rs | 2 +- src/ucan.rs | 6 +++--- tests/conformance.rs | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f4a62953..a8bd6daa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,9 +13,6 @@ use crate::{ CidString, DefaultFact, }; -/// The default multihash algorithm used for UCANs -pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; - /// A builder for creating UCANs #[derive(Debug, Clone)] pub struct UcanBuilder { @@ -103,8 +100,6 @@ where F2: Clone + DeserializeOwned, C2: CapabilityParser, { - let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); - match authority.to_cid(hasher) { Ok(cid) => { self.proofs diff --git a/src/lib.rs b/src/lib.rs index 6d0118d8..3d9e176a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ use std::str::FromStr; -use cid::Cid; +use cid::{multihash, Cid}; use serde::{de, Deserialize, Deserializer, Serialize}; pub mod builder; @@ -24,6 +24,9 @@ pub mod ucan; #[cfg(not(target_arch = "wasm32"))] pub use linkme; +/// The default multihash algorithm used for UCANs +pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; + /// A decentralized identifier. pub type Did = String; diff --git a/src/store.rs b/src/store.rs index 8e22f725..c388aaa5 100644 --- a/src/store.rs +++ b/src/store.rs @@ -10,7 +10,7 @@ use libipld_core::{ }; use multihash::MultihashDigest; -use crate::builder::DEFAULT_MULTIHASH; +use crate::DEFAULT_MULTIHASH; /// A store for persisting UCAN tokens, to be referencable as proofs by other UCANs pub trait Store diff --git a/src/ucan.rs b/src/ucan.rs index c475b0f4..488c5b19 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -8,7 +8,7 @@ use crate::{ error::Error, semantics::{ability::Ability, resource::Resource}, store::Store, - CidString, DefaultFact, + CidString, DefaultFact, DEFAULT_MULTIHASH, }; use cid::{ multihash::{self, MultihashDigest}, @@ -390,11 +390,11 @@ where } /// Return the CID v1 of the UCAN encoded as a JWT token - pub fn to_cid(&self, hasher: multihash::Code) -> Result { + pub fn to_cid(&self, hasher: Option) -> Result { static RAW_CODEC: u64 = 0x55; let token = self.encode()?; - let digest = hasher.digest(token.as_bytes()); + let digest = hasher.unwrap_or(DEFAULT_MULTIHASH).digest(token.as_bytes()); let cid = Cid::new_v1(RAW_CODEC, digest); Ok(cid) diff --git a/tests/conformance.rs b/tests/conformance.rs index 6b637286..a69552fa 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -349,7 +349,7 @@ impl TestTask for ToCidTest { _ => panic!(), }; - let Ok(cid) = ucan.to_cid(hasher) else { + let Ok(cid) = ucan.to_cid(Some(hasher)) else { report.register_failure(&name, "failed to convert to CID".to_string()); return; From 42c7386ce6ecee992c0a8e613fbf6379bce80cf7 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 13:43:42 -0800 Subject: [PATCH 057/234] refactor: switch from instant to web-time for wasm support --- Cargo.toml | 2 +- src/time.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d9a571b..82f356f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ ecdsa = { version = "0.16.8", optional = true, default-features = false } ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } erased-serde = "0.3.31" -instant = "0.1.12" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" @@ -57,6 +56,7 @@ signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" unsigned-varint = "0.7.2" url = "2.4.1" +web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # `linkme` relies on linker features that aren't available in wasm32 diff --git a/src/time.rs b/src/time.rs index 13d6cb00..e57163a0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,6 @@ //! Time utilities -use instant::SystemTime; +use web_time::SystemTime; /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { From 6b7c87cd3cbf4caac6516fcc83b0d3bcbda1002d Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 13:57:11 -0800 Subject: [PATCH 058/234] feat: add support for async signing --- Cargo.toml | 1 + src/builder.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 82f356f7..afd15707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" diff --git a/src/builder.rs b/src/builder.rs index a8bd6daa..ef062361 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,5 +1,6 @@ //! A builder for creating UCANs +use async_signature::AsyncSigner; use cid::multihash; use serde::{de::DeserializeOwned, Serialize}; use signature::Signer; @@ -196,6 +197,84 @@ where signature, }) } + + /// Sign the UCAN with the given async signer + pub async fn sign_async(self, signer: &S) -> Result, Error> + where + S: AsyncSigner + SignerDid, + K: JWSSignature + 'static, + { + let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); + + let issuer = signer.did().map_err(|e| Error::SigningError { + msg: format!("failed to construct DID, {}", e), + })?; + + let Some(audience) = self.audience else { + return Err(Error::SigningError { + msg: "an audience is required".to_string(), + }); + }; + + let header = jose_b64::serde::Json::new(UcanHeader { + alg: K::ALGORITHM.to_string(), + typ: "JWT".to_string(), + }) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let expiration = match (self.expiration, self.lifetime) { + (None, None) => None, + (None, Some(lifetime)) => Some(time::now() + lifetime), + (Some(expiration), None) => Some(expiration), + (Some(_), Some(_)) => { + return Err(Error::SigningError { + msg: "only one of expiration or lifetime may be set".to_string(), + }) + } + }; + + let payload = jose_b64::serde::Json::new(UcanPayload { + ucv: version, + iss: issuer, + aud: audience, + exp: expiration, + nbf: self.not_before, + nnc: self.nonce, + cap: self.capabilities, + fct: self.facts, + prf: self.proofs, + }) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let header_b64 = serde_json::to_value(&header) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let payload_b64 = serde_json::to_value(&payload) + .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; + + let signed_data = format!( + "{}.{}", + header_b64.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of header".to_string(), + })?, + payload_b64.as_str().ok_or(Error::InternalUcanError { + msg: "Expected base64 encoding of payload".to_string(), + })?, + ); + + let signature = signer + .sign_async(signed_data.as_bytes()) + .await + .map_err(|e| Error::SigningError { msg: e.to_string() })? + .to_vec() + .into(); + + Ok(Ucan:: { + header, + payload, + signature, + }) + } } #[cfg(test)] From d8e5039154e02ca879995580f7922724e5cac371 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Wed, 22 Nov 2023 14:08:10 -0800 Subject: [PATCH 059/234] feat: add start of WIP wasm bindings --- Cargo.toml | 6 + flake.nix | 5 +- package.json | 223 +++ pnpm-lock.yaml | 3025 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 + src/wasm.rs | 310 +++++ src/workerd.js | 6 + tests/rs_ucan.test.js | 116 ++ 8 files changed, 3696 insertions(+), 1 deletion(-) create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/wasm.rs create mode 100644 src/workerd.js create mode 100644 tests/rs_ucan.test.js diff --git a/Cargo.toml b/Cargo.toml index afd15707..f19758f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,13 @@ web-time = "0.2.3" linkme = "0.3.15" [target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = { version = "0.1" } getrandom = { version = "*", features = ["js"] } +js-sys = { version = "0.3" } +serde-wasm-bindgen = "0.6.1" +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = { version = "0.4" } +web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] multihash = "0.18.0" diff --git a/flake.nix b/flake.nix index 6ca7e995..71d7c6cd 100644 --- a/flake.nix +++ b/flake.nix @@ -49,7 +49,8 @@ cargo-sort cargo-udeps cargo-watch - wasm-tools + binaryen + wasm-bindgen-cli ]; in rec { @@ -70,6 +71,8 @@ protobuf direnv self.packages.${system}.irust + nodejs + nodePackages.pnpm ] ++ format-pkgs ++ cargo-installs diff --git a/package.json b/package.json new file mode 100644 index 00000000..26486d77 --- /dev/null +++ b/package.json @@ -0,0 +1,223 @@ +{ + "name": "ucan", + "version": "0.1.0", + "description": "A UCAN library built from rs-ucan", + "repository": { + "type": "git", + "url": "git+https://github.com/fission-codes/rs-ucan.git" + }, + "keywords": [ + "authorization" + ], + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/fission-codes/rs-ucan/issues" + }, + "homepage": "https://github.com/fission-codes/rs-ucan#readme", + "module": "dist/bundler/rs_ucan.js", + "types": "dist/nodejs/rs_ucan.d.ts", + "exports": { + ".": { + "workerd": "./dist/web/workerd.js", + "browser": "./dist/bundler/rs_ucan.js", + "node": "./dist/nodejs/rs_ucan.cjs", + "default": "./dist/bundler/rs_ucan.js", + "types": "./dist/nodejs/rs_ucan.d.ts" + }, + "./nodejs": { + "default": "./dist/nodejs/rs_ucan.cjs", + "types": "./dist/nodejs/rs_ucan.d.ts" + }, + "./web": { + "default": "./dist/web/rs_ucan.js", + "types": "./dist/web/rs_ucan.d.ts" + }, + "./workerd": { + "default": "./dist/web/workerd.js", + "types": "./dist/web/rs_ucan.d.ts" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "export PROFILE=dev && export TARGET_DIR=debug && pnpm run build:all", + "release": "export PROFILE=release && export TARGET_DIR=release && pnpm run build:all", + "build:all": "wireit", + "clean": "wireit", + "test": "wireit", + "test:node": "wireit" + }, + "wireit": { + "compile": { + "command": "cargo build --target wasm32-unknown-unknown --profile $PROFILE", + "env": { + "PROFILE": { + "external": true + } + } + }, + "opt": { + "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "env": { + "TARGET_DIR": { + "external": true + } + }, + "dependencies": [ + "compile" + ] + }, + "bindgen:bundler": { + "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "env": { + "TARGET_DIR": { + "external": true + } + }, + "dependencies": [ + "opt" + ], + "output": [ + "dist/bundler" + ] + }, + "bindgen:nodejs": { + "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && move-file dist/nodejs/rs_ucan.js dist/nodejs/rs_ucan.cjs", + "env": { + "TARGET_DIR": { + "external": true + } + }, + "dependencies": [ + "opt" + ], + "output": [ + "dist/nodejs" + ] + }, + "bindgen:web": { + "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && cpy --flat src/workerd.js dist/web", + "env": { + "TARGET_DIR": { + "external": true + } + }, + "dependencies": [ + "opt" + ], + "output": [ + "dist/web" + ] + }, + "bindgen:deno": { + "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "env": { + "TARGET_DIR": { + "external": true + } + }, + "dependencies": [ + "opt" + ], + "output": [ + "dist/deno" + ] + }, + "build:all": { + "dependencies": [ + "bindgen:bundler", + "bindgen:nodejs", + "bindgen:web", + "bindgen:deno" + ] + }, + "clean": { + "command": "rimraf dist" + }, + "test:prepare": { + "command": "cross-env mkdir tests/report", + "output": [ + "tests/report" + ] + }, + "test:chromium": { + "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", + "dependencies": [ + "build", + "test:prepare" + ], + "output": [ + "tests/report/chromium.json" + ] + }, + "test:firefox": { + "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", + "dependencies": [ + "build", + "test:prepare" + ], + "output": [ + "tests/report/firefox.json" + ] + }, + "test:webkit": { + "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", + "dependencies": [ + "build", + "test:prepare" + ], + "output": [ + "tests/report/webkit.json" + ] + }, + "test:browser": { + "dependencies": [ + "test:chromium", + "test:firefox", + "test:webkit" + ] + }, + "test:node": { + "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", + "dependencies": [ + "build", + "test:prepare" + ], + "output": [ + "tests/report/node.json" + ] + }, + "test:report": { + "command": "nyc report --reporter=json-summary --report-dir tests/report", + "dependencies": [ + "test:chromium" + ], + "output": [ + "tests/report/coverage-summary.json" + ] + }, + "test": { + "dependencies": [ + "test:browser", + "test:node", + "test:report" + ] + } + }, + "devDependencies": { + "@types/assert": "^1.5.6", + "@types/mocha": "^10.0.1", + "assert": "^2.0.0", + "cpy-cli": "^5.0.0", + "cross-env": "^7.0.3", + "mocha": "^10.2.0", + "move-file-cli": "^3.0.0", + "nyc": "^15.1.0", + "playwright-test": "^12.1.2", + "rimraf": "^5.0.1", + "typescript": "^5.1.6", + "wireit": "^0.10.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..fa2e6ed3 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3025 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + '@types/assert': + specifier: ^1.5.6 + version: 1.5.8 + '@types/mocha': + specifier: ^10.0.1 + version: 10.0.3 + assert: + specifier: ^2.0.0 + version: 2.1.0 + cpy-cli: + specifier: ^5.0.0 + version: 5.0.0 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + mocha: + specifier: ^10.2.0 + version: 10.2.0 + move-file-cli: + specifier: ^3.0.0 + version: 3.0.0 + nyc: + specifier: ^15.1.0 + version: 15.1.0 + playwright-test: + specifier: ^12.1.2 + version: 12.4.3 + rimraf: + specifier: ^5.0.1 + version: 5.0.5 + typescript: + specifier: ^5.1.6 + version: 5.2.2 + wireit: + specifier: ^0.10.0 + version: 0.10.0 + +packages: + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@arr/every@1.0.1: + resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} + engines: {node: '>=4'} + dev: true + + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.20 + chalk: 2.4.2 + dev: true + + /@babel/compat-data@7.23.2: + resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core@7.23.2: + resolution: {integrity: sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.0 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.2) + '@babel/helpers': 7.23.2 + '@babel/parser': 7.23.0 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 + convert-source-map: 2.0.0 + debug: 4.3.4(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator@7.23.0: + resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + jsesc: 2.5.2 + dev: true + + /@babel/helper-compilation-targets@7.22.15: + resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.23.2 + '@babel/helper-validator-option': 7.22.15 + browserslist: 4.22.1 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 + dev: true + + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + dev: true + + /@babel/helper-module-imports@7.22.15: + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + dev: true + + /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): + resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.2 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + dev: true + + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + dev: true + + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-option@7.22.15: + resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helpers@7.23.2: + resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 + '@babel/types': 7.23.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight@7.22.20: + resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser@7.23.0: + resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.0 + dev: true + + /@babel/template@7.22.15: + resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 + dev: true + + /@babel/traverse@7.23.2: + resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.0 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 + debug: 4.3.4(supports-color@8.1.1) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types@7.23.0: + resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@esbuild/android-arm64@0.19.5: + resolution: {integrity: sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.5: + resolution: {integrity: sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.5: + resolution: {integrity: sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.5: + resolution: {integrity: sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.5: + resolution: {integrity: sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.5: + resolution: {integrity: sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.5: + resolution: {integrity: sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.5: + resolution: {integrity: sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.5: + resolution: {integrity: sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.5: + resolution: {integrity: sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.5: + resolution: {integrity: sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.5: + resolution: {integrity: sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.5: + resolution: {integrity: sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.5: + resolution: {integrity: sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.5: + resolution: {integrity: sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.5: + resolution: {integrity: sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.5: + resolution: {integrity: sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.5: + resolution: {integrity: sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.5: + resolution: {integrity: sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.5: + resolution: {integrity: sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.5: + resolution: {integrity: sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.5: + resolution: {integrity: sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + + /@polka/url@0.5.0: + resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==} + dev: true + + /@polka/url@1.0.0-next.23: + resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==} + dev: true + + /@types/assert@1.5.8: + resolution: {integrity: sha512-tL1NSDf4CF5hiVTnLd4KSth6bmRO3+tw8cJzEAUaN6fYQ26DIixX0lRFmtrA0jhxUS8SL6PDWtphMz3maxapjA==} + dev: true + + /@types/istanbul-lib-coverage@2.0.5: + resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==} + dev: true + + /@types/minimist@1.2.4: + resolution: {integrity: sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==} + dev: true + + /@types/mocha@10.0.3: + resolution: {integrity: sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ==} + dev: true + + /@types/normalize-package-data@2.4.3: + resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==} + dev: true + + /acorn-loose@8.4.0: + resolution: {integrity: sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==} + engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.11.2 + dev: true + + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /aggregate-error@4.0.1: + resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: '>=12'} + dependencies: + clean-stack: 4.2.0 + indent-string: 5.0.0 + dev: true + + /ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /append-transform@2.0.0: + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} + dependencies: + default-require-extensions: 3.0.1 + dev: true + + /archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /arrify@3.0.0: + resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} + engines: {node: '>=12'} + dev: true + + /assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + dependencies: + call-bind: 1.0.5 + is-nan: 1.3.2 + object-is: 1.1.5 + object.assign: 4.1.4 + util: 0.12.5 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /bl@5.1.0: + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + dependencies: + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /browserslist@4.22.1: + resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001558 + electron-to-chromium: 1.4.570 + node-releases: 2.0.13 + update-browserslist-db: 1.0.13(browserslist@4.22.1) + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /c8@8.0.1: + resolution: {integrity: sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 2.0.0 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.6 + rimraf: 3.0.2 + test-exclude: 6.0.0 + v8-to-istanbul: 9.1.3 + yargs: 17.7.2 + yargs-parser: 21.1.1 + dev: true + + /caching-transform@4.0.0: + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} + dependencies: + hasha: 5.2.2 + make-dir: 3.1.0 + package-hash: 4.0.0 + write-file-atomic: 3.0.3 + dev: true + + /call-bind@1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + dev: true + + /camelcase-keys@7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + dev: true + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + dev: true + + /caniuse-lite@1.0.30001558: + resolution: {integrity: sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==} + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /clean-stack@4.2.0: + resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: '>=12'} + dependencies: + escape-string-regexp: 5.0.0 + dev: true + + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + dev: true + + /cli-spinners@2.9.1: + resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==} + engines: {node: '>=6'} + dev: true + + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true + + /cp-file@10.0.0: + resolution: {integrity: sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg==} + engines: {node: '>=14.16'} + dependencies: + graceful-fs: 4.2.11 + nested-error-stacks: 2.1.1 + p-event: 5.0.1 + dev: true + + /cpy-cli@5.0.0: + resolution: {integrity: sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + cpy: 10.1.0 + meow: 12.1.1 + dev: true + + /cpy@10.1.0: + resolution: {integrity: sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==} + engines: {node: '>=16'} + dependencies: + arrify: 3.0.0 + cp-file: 10.0.0 + globby: 13.2.2 + junk: 4.0.1 + micromatch: 4.0.5 + nested-error-stacks: 2.1.1 + p-filter: 3.0.0 + p-map: 6.0.0 + dev: true + + /cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + dependencies: + type-fest: 1.4.0 + dev: true + + /debug@4.3.4(supports-color@8.1.1): + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /decamelize@5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + dev: true + + /default-require-extensions@3.0.1: + resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} + engines: {node: '>=8'} + dependencies: + strip-bom: 4.0.0 + dev: true + + /define-data-property@1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + has-property-descriptors: 1.0.1 + object-keys: 1.1.1 + dev: true + + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /electron-to-chromium@1.4.570: + resolution: {integrity: sha512-5GxH0PLSIfXKOUMMHMCT4M0olwj1WwAxsQHzVW5Vh3kbsvGw8b4k7LHQmTLC2aRhsgFzrF57XJomca4XLc/WHA==} + dev: true + + /emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + dev: true + + /esbuild-plugin-wasm@1.1.0: + resolution: {integrity: sha512-0bQ6+1tUbySSnxzn5jnXHMDvYnT0cN/Wd4Syk8g/sqAIJUg7buTIi22svS3Qz6ssx895NT+TgLPb33xi1OkZig==} + engines: {node: '>=0.10.0'} + dev: true + + /esbuild@0.19.5: + resolution: {integrity: sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.19.5 + '@esbuild/android-arm64': 0.19.5 + '@esbuild/android-x64': 0.19.5 + '@esbuild/darwin-arm64': 0.19.5 + '@esbuild/darwin-x64': 0.19.5 + '@esbuild/freebsd-arm64': 0.19.5 + '@esbuild/freebsd-x64': 0.19.5 + '@esbuild/linux-arm': 0.19.5 + '@esbuild/linux-arm64': 0.19.5 + '@esbuild/linux-ia32': 0.19.5 + '@esbuild/linux-loong64': 0.19.5 + '@esbuild/linux-mips64el': 0.19.5 + '@esbuild/linux-ppc64': 0.19.5 + '@esbuild/linux-riscv64': 0.19.5 + '@esbuild/linux-s390x': 0.19.5 + '@esbuild/linux-x64': 0.19.5 + '@esbuild/netbsd-x64': 0.19.5 + '@esbuild/openbsd-x64': 0.19.5 + '@esbuild/sunos-x64': 0.19.5 + '@esbuild/win32-arm64': 0.19.5 + '@esbuild/win32-ia32': 0.19.5 + '@esbuild/win32-x64': 0.19.5 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /exit-hook@4.0.0: + resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} + engines: {node: '>=18'} + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + dev: true + + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /foreground-child@2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 3.0.7 + dev: true + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /fromentries@1.3.2: + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: true + + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-intrinsic@1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + dev: true + + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + dev: true + + /glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} + dependencies: + is-stream: 2.0.1 + type-fest: 0.8.1 + dev: true + + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: true + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + dev: true + + /is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.13 + dev: true + + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + dev: true + + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-hook@3.0.0: + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} + dependencies: + append-transform: 2.0.0 + dev: true + + /istanbul-lib-instrument@4.0.3: + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.23.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-processinfo@2.0.3: + resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} + engines: {node: '>=8'} + dependencies: + archy: 1.0.0 + cross-spawn: 7.0.3 + istanbul-lib-coverage: 3.2.0 + p-map: 3.0.0 + rimraf: 3.0.2 + uuid: 8.3.2 + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /junk@4.0.1: + resolution: {integrity: sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==} + engines: {node: '>=12.20'} + dev: true + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-symbols@5.1.0: + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} + engines: {node: '>=12'} + dependencies: + chalk: 5.3.0 + is-unicode-supported: 1.3.0 + dev: true + + /lru-cache@10.0.1: + resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} + engines: {node: 14 || >=16.14} + dev: true + + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.1 + dev: true + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /matchit@1.1.0: + resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==} + engines: {node: '>=6'} + dependencies: + '@arr/every': 1.0.1 + dev: true + + /meow@10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + '@types/minimist': 1.2.4 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + dev: true + + /meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + dev: true + + /merge-options@3.0.4: + resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + engines: {node: '>=10'} + dependencies: + is-plain-obj: 2.1.0 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + + /mocha@10.2.0: + resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /move-file-cli@3.0.0: + resolution: {integrity: sha512-d9ef0fnyX6K/1sKvKG1F0cssJpIrzxWITjkiq3ufC/GQcMNsPMaNEmv+PnPdlBBzRAy4/EMkLkeQfuL946okuQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + meow: 10.1.5 + move-file: 3.1.0 + dev: true + + /move-file@3.1.0: + resolution: {integrity: sha512-4aE3U7CCBWgrQlQDMq8da4woBWDGHioJFiOZ8Ie6Yq2uwYQ9V2kGhTz4x3u6Wc+OU17nw0yc3rJ/lQ4jIiPe3A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-exists: 5.0.0 + dev: true + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /mrmime@1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid@3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /nanoid@5.0.2: + resolution: {integrity: sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==} + engines: {node: ^18 || >=20} + hasBin: true + dev: true + + /nested-error-stacks@2.1.1: + resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + dev: true + + /node-preload@0.2.1: + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} + dependencies: + process-on-spawn: 1.0.0 + dev: true + + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + dev: true + + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.13.1 + semver: 7.5.4 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /nyc@15.1.0: + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} + hasBin: true + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + caching-transform: 4.0.0 + convert-source-map: 1.9.0 + decamelize: 1.2.0 + find-cache-dir: 3.3.2 + find-up: 4.1.0 + foreground-child: 2.0.0 + get-package-type: 0.1.0 + glob: 7.2.3 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-hook: 3.0.0 + istanbul-lib-instrument: 4.0.3 + istanbul-lib-processinfo: 2.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + make-dir: 3.1.0 + node-preload: 0.2.1 + p-map: 3.0.0 + process-on-spawn: 1.0.0 + resolve-from: 5.0.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + spawn-wrap: 2.0.0 + test-exclude: 6.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - supports-color + dev: true + + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /ora@7.0.1: + resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} + engines: {node: '>=16'} + dependencies: + chalk: 5.3.0 + cli-cursor: 4.0.0 + cli-spinners: 2.9.1 + is-interactive: 2.0.0 + is-unicode-supported: 1.3.0 + log-symbols: 5.1.0 + stdin-discarder: 0.1.0 + string-width: 6.1.0 + strip-ansi: 7.1.0 + dev: true + + /p-event@5.0.1: + resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-timeout: 5.1.0 + dev: true + + /p-filter@3.0.0: + resolution: {integrity: sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-map: 5.5.0 + dev: true + + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /p-map@5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: '>=12'} + dependencies: + aggregate-error: 4.0.1 + dev: true + + /p-map@6.0.0: + resolution: {integrity: sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==} + engines: {node: '>=16'} + dev: true + + /p-timeout@5.1.0: + resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} + engines: {node: '>=12'} + dev: true + + /p-timeout@6.1.2: + resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} + engines: {node: '>=14.16'} + dev: true + + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /package-hash@4.0.0: + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} + dependencies: + graceful-fs: 4.2.11 + hasha: 5.2.2 + lodash.flattendeep: 4.4.0 + release-zalgo: 1.0.0 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.13 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.1 + minipass: 7.0.4 + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /playwright-core@1.39.0: + resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} + engines: {node: '>=16'} + hasBin: true + dev: true + + /playwright-test@12.4.3: + resolution: {integrity: sha512-51nFyab0RSOM23dTq34C61hAx0e13Scab6U5VJW7RjFhpEkQXBeq46R7WNeY0mHaYEUaLYdSch51ceibtQ4Tzg==} + engines: {node: '>=16.0.0'} + hasBin: true + dependencies: + acorn-loose: 8.4.0 + assert: 2.1.0 + buffer: 6.0.3 + c8: 8.0.1 + camelcase: 8.0.0 + chokidar: 3.5.3 + cpy: 10.1.0 + esbuild: 0.19.5 + esbuild-plugin-wasm: 1.1.0 + events: 3.3.0 + execa: 8.0.1 + exit-hook: 4.0.0 + globby: 13.2.2 + kleur: 4.1.5 + lilconfig: 2.1.0 + lodash: 4.17.21 + merge-options: 3.0.4 + nanoid: 5.0.2 + ora: 7.0.1 + p-timeout: 6.1.2 + path-browserify: 1.0.1 + playwright-core: 1.39.0 + polka: 0.5.2 + premove: 4.0.0 + process: 0.11.10 + sade: 1.8.1 + sirv: 2.0.3 + source-map: 0.6.1 + source-map-support: 0.5.21 + stream-browserify: 3.0.0 + tempy: 3.1.0 + test-exclude: 6.0.0 + util: 0.12.5 + v8-to-istanbul: 9.1.3 + dev: true + + /polka@0.5.2: + resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==} + dependencies: + '@polka/url': 0.5.0 + trouter: 2.0.1 + dev: true + + /premove@4.0.0: + resolution: {integrity: sha512-zim/Hr4+FVdCIM7zL9b9Z0Wfd5Ya3mnKtiuDv7L5lzYzanSq6cOcVJ7EFcgK4I0pt28l8H0jX/x3nyog380XgQ==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /process-on-spawn@1.0.0: + resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} + engines: {node: '>=8'} + dependencies: + fromentries: 1.3.2 + dev: true + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + + /proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /read-pkg-up@8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + dev: true + + /read-pkg@6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + dependencies: + '@types/normalize-package-data': 2.4.3 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + dev: true + + /release-zalgo@1.0.0: + resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} + engines: {node: '>=4'} + dependencies: + es6-error: 4.1.1 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true + + /set-function-length@1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /sirv@2.0.3: + resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.23 + mrmime: 1.0.1 + totalist: 3.0.1 + dev: true + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /spawn-wrap@2.0.0: + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} + dependencies: + foreground-child: 2.0.0 + is-windows: 1.0.2 + make-dir: 3.1.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + which: 2.0.2 + dev: true + + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.16 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.16 + dev: true + + /spdx-license-ids@3.0.16: + resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} + dev: true + + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /stdin-discarder@0.1.0: + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + bl: 5.1.0 + dev: true + + /stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + + /string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 10.3.0 + strip-ansi: 7.1.0 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-indent@4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + dev: true + + /tempy@3.1.0: + resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} + engines: {node: '>=14.16'} + dependencies: + is-stream: 3.0.0 + temp-dir: 3.0.0 + type-fest: 2.19.0 + unique-string: 3.0.0 + dev: true + + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: true + + /trim-newlines@4.1.1: + resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} + engines: {node: '>=12'} + dev: true + + /trouter@2.0.1: + resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==} + engines: {node: '>=6'} + dependencies: + matchit: 1.1.0 + dev: true + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: true + + /typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + dev: true + + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + dependencies: + crypto-random-string: 4.0.0 + dev: true + + /update-browserslist-db@1.0.13(browserslist@4.22.1): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.22.1 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.12 + which-typed-array: 1.1.13 + dev: true + + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: true + + /v8-to-istanbul@9.1.3: + resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + '@types/istanbul-lib-coverage': 2.0.5 + convert-source-map: 2.0.0 + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + dev: true + + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: true + + /which-typed-array@1.1.13: + resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /wireit@0.10.0: + resolution: {integrity: sha512-4TX6V9D/2iXUBzdqQaUG+cRePle0mDx1Q7x4Ka2cA8lgp1+ZBhrOTiLsXYRl2roQEldEFgQ2Ff1W8YgyNWAa6w==} + engines: {node: '>=14.14.0'} + hasBin: true + dependencies: + braces: 3.0.2 + chokidar: 3.5.3 + fast-glob: 3.3.1 + jsonc-parser: 3.2.0 + proper-lockfile: 4.1.2 + dev: true + + /workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + dev: true + + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/src/lib.rs b/src/lib.rs index 3d9e176a..a7a023f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,12 @@ pub mod store; pub mod time; pub mod ucan; +#[cfg(target_arch = "wasm32")] +mod wasm; + +#[cfg(target_arch = "wasm32")] +pub use wasm::*; + #[doc(hidden)] #[cfg(not(target_arch = "wasm32"))] pub use linkme; diff --git a/src/wasm.rs b/src/wasm.rs new file mode 100644 index 00000000..0575d3c1 --- /dev/null +++ b/src/wasm.rs @@ -0,0 +1,310 @@ +use anyhow::{anyhow, bail}; +use async_signature::AsyncSigner; +use async_trait::async_trait; +use js_sys::{Date, Error, Reflect, Uint8Array}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; +use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto}; + +use crate::{builder::UcanBuilder, capability::DefaultCapabilityParser, crypto::SignerDid}; + +/// Convenience alias around `Result` +pub type JsResult = Result; + +/// A UCAN whose facts are a JSON value +#[wasm_bindgen] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Ucan { + ucan: crate::ucan::Ucan, +} + +struct JsSigner { + key_pair: CryptoKeyPair, + _marker: std::marker::PhantomData K>, +} + +impl JsSigner { + fn subtle_crypto() -> Result { + let global = js_sys::global(); + + match Reflect::get(&global, &JsValue::from_str("crypto")) { + Ok(value) => { + let crypto = value + .dyn_into::() + .map_err(|_| anyhow!("Failed to cast value to Crypto"))? + .subtle(); + + Ok(crypto) + } + Err(_) => bail!("Failed to get crypto from global object"), + } + } + + fn new(key_pair: CryptoKeyPair) -> Self { + Self { + key_pair, + _marker: std::marker::PhantomData, + } + } + + fn signing_key(&self) -> Result { + match Reflect::get(&self.key_pair, &JsValue::from_str("privateKey")) { + Ok(key) => key + .dyn_into::() + .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), + Err(_) => bail!("Failed to get privateKey from CryptoKeyPair"), + } + } + + fn verifying_key(&self) -> Result { + match Reflect::get(&self.key_pair, &JsValue::from_str("publicKey")) { + Ok(key) => key + .dyn_into::() + .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), + Err(_) => bail!("Failed to get publicKey from CryptoKeyPair"), + } + } +} + +impl SignerDid for JsSigner { + fn did(&self) -> Result { + Ok("test".to_string()) + } +} + +#[async_trait(?Send)] +impl AsyncSigner for JsSigner { + async fn sign_async( + &self, + msg: &[u8], + ) -> Result { + let subtle = Self::subtle_crypto().map_err(|e| async_signature::Error::from_source(e))?; + + // This can be done without copying using the unsafe `Uint8Array::view` method, + // but I've opted to stick to safe APIs for now, until we benchmark signing. + let data = Uint8Array::from(msg).buffer(); + + let key = self + .signing_key() + .map_err(|e| async_signature::Error::from_source(e))?; + + let promise = subtle + .sign_with_str_and_buffer_source("RSASSA-PKCS1-v1_5", &key, &data) + .map_err(|_| async_signature::Error::new())?; + + let result = JsFuture::from(promise) + .await + .map_err(|_| async_signature::Error::new())?; + + let signature = + rsa::pkcs1v15::Signature::try_from(Uint8Array::new(&result).to_vec().as_slice()) + .map_err(|_| async_signature::Error::new())?; + + Ok(signature) + } +} + +#[wasm_bindgen] +impl Ucan { + /// Returns a boolean indicating whether the given UCAN is expired at the given date + #[wasm_bindgen(js_name = "isExpired")] + pub fn is_expired(&self, at_time: &Date) -> bool { + let at_time = f64::floor(at_time.get_time() / 1000.) as u64; + + self.ucan.is_expired(at_time) + } + + /// Returns true if the UCAN is not yet valid at the given date + #[wasm_bindgen(js_name = "isTooEarly")] + pub fn is_too_early(&self, at_time: &Date) -> bool { + let at_time = f64::floor(at_time.get_time() / 1000.) as u64; + + self.ucan.is_too_early(at_time) + } + + /// Returns the UCAN's signature as a `Uint8Array` + #[wasm_bindgen(getter)] + pub fn signature(&self) -> Vec { + self.ucan.signature().to_vec() + } + + /// Returns the `typ` field of the UCAN's JWT header + #[wasm_bindgen(getter)] + pub fn typ(&self) -> String { + self.ucan.typ().to_string() + } + + /// Returns the `alg` field of the UCAN's JWT header + #[wasm_bindgen(getter)] + pub fn algorithm(&self) -> String { + self.ucan.algorithm().to_string() + } + + /// Returns the `iss` field of the UCAN's JWT payload + #[wasm_bindgen(getter)] + pub fn issuer(&self) -> String { + self.ucan.issuer().to_string() + } + + /// Returns the `aud` field of the UCAN's JWT payload + #[wasm_bindgen(getter)] + pub fn audience(&self) -> String { + self.ucan.audience().to_string() + } + + /// Returns the `exp` field of the UCAN's JWT payload + #[wasm_bindgen(getter, js_name = "expiresAt")] + pub fn expires_at(&self) -> Option { + self.ucan + .expires_at() + .map(|expires_at| Date::new(&JsValue::from_f64((expires_at as f64) * 1000.))) + } + + /// Returns the `nbf` field of the UCAN's JWT payload + #[wasm_bindgen(getter, js_name = "notBefore")] + pub fn not_before(&self) -> Option { + self.ucan + .not_before() + .map(|not_before| Date::new(&JsValue::from_f64((not_before as f64) * 1000.))) + } + + /// Returns the `nnc` field of the UCAN's JWT payload + #[wasm_bindgen(getter)] + pub fn nonce(&self) -> Option { + self.ucan.nonce().map(String::to_string) + } + + /// Returns the `fct` field of the UCAN's JWT payload + #[wasm_bindgen(getter)] + pub fn facts(&self) -> JsResult { + self.ucan + .facts() + .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) + .map_err(|e| Error::new(&format!("Failed to serialize facts: {}", e))) + } + + /// Returns the `vsn` field of the UCAN's JWT payload + #[wasm_bindgen(getter)] + pub fn version(&self) -> String { + self.ucan.version().to_string() + } + + /// Returns the CID of the UCAN + #[wasm_bindgen] + pub fn cid(&self) -> JsResult { + match self.ucan.to_cid(None) { + Ok(cid) => Ok(cid.to_string()), + Err(e) => Err(Error::new(&format!("Failed to convert to CID: {}", e))), + } + } +} + +/// Decode a UCAN +#[wasm_bindgen] +pub async fn decode(token: String) -> JsResult { + let ucan = + crate::ucan::Ucan::from_str(&token).map_err(|e| Error::new(e.to_string().as_ref()))?; + + Ok(Ucan { ucan }) +} + +/// Options for building a UCAN +#[derive(Debug, Deserialize)] +pub struct BuildOptions { + /// The lifetime of the UCAN in seconds + #[serde(rename = "lifetimeInSeconds")] + pub lifetime_in_seconds: Option, + /// The expiration time of the UCAN in seconds since epoch + pub expiration: Option, + /// The time before which the UCAN is not valid in seconds since epoch + #[serde(rename = "notBefore")] + pub not_before: Option, + /// The facts included in the UCAN + pub facts: Option, + /// The proof CIDs referenced by the UCAN + pub proofs: Option>, + /// The nonce of the UCAN + pub nonce: Option, + // TODO: capabilities +} + +/// Build a UCAN +#[wasm_bindgen] +pub async fn build(issuer: CryptoKeyPair, audience: &str, options: JsValue) -> JsResult { + let options: BuildOptions = + serde_wasm_bindgen::from_value(options).map_err(|e| Error::new(e.to_string().as_ref()))?; + + let builder = + UcanBuilder::::default().for_audience(audience); + + let builder = match options.lifetime_in_seconds { + Some(lifetime_in_seconds) => builder.with_lifetime(lifetime_in_seconds), + None => builder, + }; + + let builder = match options.expiration { + Some(expiration) => builder.with_expiration(expiration), + None => builder, + }; + + let builder = match options.not_before { + Some(not_before) => builder.not_before(not_before), + None => builder, + }; + + let builder = match options.facts { + Some(facts) => builder.with_fact(facts), + None => builder, + }; + + let builder = match options.nonce { + Some(nonce) => builder.with_nonce(nonce), + None => builder, + }; + + // TODO: proofs (need store) + + let signer = JsSigner::::new(issuer); + + let ucan = builder + .sign_async(&signer) + .await + .map_err(|e| Error::new(e.to_string().as_ref()))?; + + Ok(Ucan { ucan }) +} + +/// Panic hook lets us get better error messages if our Rust code ever panics. +/// +/// For more details see +/// +#[wasm_bindgen(js_name = "setPanicHook")] +pub fn set_panic_hook() { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} + +#[wasm_bindgen] +extern "C" { + // For alerting + pub(crate) fn alert(s: &str); + // For logging in the console. + #[wasm_bindgen(js_namespace = console)] + pub fn log(s: &str); +} + +/// Return a representation of an object owned by JS. +#[macro_export] +macro_rules! value { + ($value:expr) => { + wasm_bindgen::JsValue::from($value) + }; +} + +/// Calls the wasm_bindgen console.log. +#[macro_export] +macro_rules! console_log { + ($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) +} diff --git a/src/workerd.js b/src/workerd.js new file mode 100644 index 00000000..ebd2009d --- /dev/null +++ b/src/workerd.js @@ -0,0 +1,6 @@ +// This entry point is inserted into ./lib/workerd to support Cloudflare workers + +import WASM from "./rs_ucan_bg.wasm"; +import { initSync } from "./rs_ucan.js"; +initSync(WASM); +export * from "./rs_ucan.js"; diff --git a/tests/rs_ucan.test.js b/tests/rs_ucan.test.js new file mode 100644 index 00000000..3b00bf07 --- /dev/null +++ b/tests/rs_ucan.test.js @@ -0,0 +1,116 @@ +import assert from "assert"; +import { build, decode } from "../dist/bundler/rs_ucan.js"; + +describe("decode", async function () { + let ucan = await decode( + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJkaWQ6a2V5Ono2TWtmZkRaQ2tDVFdyZWc4ODY4ZkcxRkdGb2djSmo1WDZQWTkzcFBjV0RuOWJvYiIsImNhcCI6e30sImV4cCI6OTI0NjIxMTIwMCwiaXNzIjoiZGlkOmtleTp6Nk1razg5YkMzSnJWcUtpZTcxWUVjYzVNMVNNVnh1Q2dOeDZ6TFo4U1lKc3hBTGkiLCJ1Y3YiOiIwLjEwLjAifQ.pkJxQke-FDVB1Eg_7Jh2socNBKgo6_0OF1XXRfRMazmpXBG37tScYGAzJKB2Z4RFvSBpbBu29Sozrv4GQLFrDg", + ); + + it("decodes the signature", async function () { + let actual = ucan.signature; + let expected = new Uint8Array([ + 166, 66, 113, 66, 71, 190, 20, 53, 65, 212, 72, 63, 236, 152, 118, 178, + 135, 13, 4, 168, 40, 235, 253, 14, 23, 85, 215, 69, 244, 76, 107, 57, 169, + 92, 17, 183, 238, 212, 156, 96, 96, 51, 36, 160, 118, 103, 132, 69, 189, + 32, 105, 108, 27, 182, 245, 42, 51, 174, 254, 6, 64, 177, 107, 14, + ]); + + assert.equal(actual.byteLength, expected.byteLength); + assert.ok(actual.every((v, i) => v === expected[i])); + }); + + it("decodes the typ", async function () { + let actual = ucan.typ; + let expected = "JWT"; + + assert.equal(actual, expected); + }); + + it("decodes the alg", async function () { + let actual = ucan.algorithm; + let expected = "EdDSA"; + + assert.equal(actual, expected); + }); + + it("decodes the iss", async function () { + let actual = ucan.issuer; + let expected = "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi"; + + assert.equal(actual, expected); + }); + + it("decodes the aud", async function () { + let actual = ucan.audience; + let expected = "did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob"; + + assert.equal(actual, expected); + }); + + it("decodes the exp", async function () { + let actual = ucan.expiresAt.getTime(); + let expected = new Date(9246211200 * 1000).getTime(); + + assert.equal(actual, expected); + }); + + it("decodes the nbf", async function () { + let actual = ucan.notBefore; + let expected = null; + + assert.equal(actual, expected); + }); + + it("decodes the nnc", async function () { + let actual = ucan.nonce; + let expected = null; + + assert.equal(actual, expected); + }); + + it("decodes the facts", async function () { + let actual = ucan.facts; + let expected = null; + + assert.equal(actual, expected); + }); + + it("decodes the ucn", async function () { + let actual = ucan.version; + let expected = "0.10.0"; + + assert.equal(actual, expected); + }); + + it("preserves the CID", async function () { + let actual = ucan.cid(); + let expected = + "bafkreifmws7u5w6nluprxu5zcsun2wcp7rioxjy2qem6pjj5z367dp64li"; + + assert.equal(actual, expected); + }); +}); + +describe("build", async function () { + const RSA_ALG = "RSASSA-PKCS1-v1_5"; + const DEFAULT_KEY_SIZE = 2048; + const DEFAULT_HASH_ALG = "SHA-256"; + const SALT_LEGNTH = 128; + + it("builds the ucan", async function () { + let keypair = await crypto.subtle.generateKey( + { + name: RSA_ALG, + modulusLength: DEFAULT_KEY_SIZE, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: { name: DEFAULT_HASH_ALG }, + }, + false, + ["sign", "verify"], + ); + + let ucan = await build(keypair, "did:key:test", {}); + + assert.equal(ucan.signature, null); + }); +}); From edfefb128339a05468af61fb2632a487de0b0827 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Tue, 28 Nov 2023 14:56:54 -0800 Subject: [PATCH 060/234] fix: allow proofs to have unbounded lifetimes in subsumption checking --- src/ucan.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 224 insertions(+), 11 deletions(-) diff --git a/src/ucan.rs b/src/ucan.rs index 488c5b19..e0939932 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -198,11 +198,7 @@ where ), })?; - if ucan.expires_at() > proof_ucan.expires_at() { - continue; - } - - if ucan.not_before() < proof_ucan.not_before() { + if !proof_ucan.lifetime_encompasses(&ucan) { continue; } @@ -294,9 +290,6 @@ where } /// Returns true if this UCAN's lifetime begins no later than the other - /// Note that if a UCAN specifies an NBF but the other does not, the - /// other has an unbounded start time and this function will return - /// false. pub fn lifetime_begins_before(&self, other: &Ucan) -> bool where F2: DeserializeOwned, @@ -713,7 +706,77 @@ mod tests { } #[test] - fn test_capabilities_for_invocation() -> Result<(), anyhow::Error> { + fn test_capabilities_for_invocation_no_lifetime() -> Result<(), anyhow::Error> { + let mut store = InMemoryStore::::default(); + let did_verifier_map = DidVerifierMap::default(); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let root_ucan: Ucan = UcanBuilder::default() + .for_audience(aud_key.did()?) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat, + )) + .sign(&iss_key)?; + + store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; + + let invocation: Ucan = UcanBuilder::default() + .for_audience("did:web:fission.codes") + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + EmptyCaveat, + )) + .witnessed_by(&root_ucan, None) + .sign(&aud_key)?; + + let capabilities = invocation.capabilities_for( + iss_key.did()?, + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + time::now(), + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 1); + + assert_eq!( + capabilities[0].resource().downcast_ref::(), + Some(&WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }) + ); + + assert_eq!( + capabilities[0].ability().downcast_ref::(), + Some(&WnfsAbility::Revise) + ); + + assert_eq!( + capabilities[0].caveat().downcast_ref::(), + Some(&EmptyCaveat) + ); + + Ok(()) + } + + #[test] + fn test_capabilities_for_invocation_lifetime_encompassed() -> Result<(), anyhow::Error> { let mut store = InMemoryStore::::default(); let did_verifier_map = DidVerifierMap::default(); @@ -745,6 +808,7 @@ mod tests { WnfsAbility::Revise, EmptyCaveat, )) + .with_lifetime(30) .witnessed_by(&root_ucan, None) .sign(&aud_key)?; @@ -780,6 +844,156 @@ mod tests { Some(&EmptyCaveat) ); + Ok(()) + } + + #[test] + fn test_capabilities_for_invocation_nbf_exposed() -> Result<(), anyhow::Error> { + let mut store = InMemoryStore::::default(); + let did_verifier_map = DidVerifierMap::default(); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let root_ucan: Ucan = UcanBuilder::default() + .for_audience(aud_key.did()?) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat, + )) + .not_before(1) + .sign(&iss_key)?; + + store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; + + let invocation: Ucan = UcanBuilder::default() + .for_audience("did:web:fission.codes") + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + EmptyCaveat, + )) + .not_before(0) + .witnessed_by(&root_ucan, None) + .sign(&aud_key)?; + + let capabilities = invocation.capabilities_for( + iss_key.did()?, + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + 0, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 0); + + Ok(()) + } + + #[test] + fn test_capabilities_for_invocation_exp_exposed() -> Result<(), anyhow::Error> { + let mut store = InMemoryStore::::default(); + let did_verifier_map = DidVerifierMap::default(); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let root_ucan: Ucan = UcanBuilder::default() + .for_audience(aud_key.did()?) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat, + )) + .with_expiration(0) + .sign(&iss_key)?; + + store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; + + let invocation: Ucan = UcanBuilder::default() + .for_audience("did:web:fission.codes") + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + EmptyCaveat, + )) + .with_expiration(1) + .witnessed_by(&root_ucan, None) + .sign(&aud_key)?; + + let capabilities = invocation.capabilities_for( + iss_key.did()?, + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + 0, + &did_verifier_map, + &store, + )?; + + assert_eq!(capabilities.len(), 0); + + Ok(()) + } + + #[test] + fn test_capabilities_for_invocation_lifetime_disjoint() -> Result<(), anyhow::Error> { + let mut store = InMemoryStore::::default(); + let did_verifier_map = DidVerifierMap::default(); + + let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + + let root_ucan: Ucan = UcanBuilder::default() + .for_audience(aud_key.did()?) + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + TopAbility, + EmptyCaveat, + )) + .not_before(0) + .with_expiration(1) + .sign(&iss_key)?; + + store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; + + let invocation: Ucan = UcanBuilder::default() + .for_audience("did:web:fission.codes") + .claiming_capability(Capability::new( + WnfsResource::PublicPath { + user: "alice".to_string(), + path: vec!["photos".to_string()], + }, + WnfsAbility::Revise, + EmptyCaveat, + )) + .not_before(2) + .with_expiration(3) + .witnessed_by(&root_ucan, None) + .sign(&aud_key)?; + let capabilities = invocation.capabilities_for( iss_key.did()?, WnfsResource::PublicPath { @@ -787,8 +1001,7 @@ mod tests { path: vec!["photos".to_string()], }, WnfsAbility::Revise, - // Past the lifetime of the root UCAN - time::now() + 61, + 2, &did_verifier_map, &store, )?; From 274acc2ee770dff6587ec849f3f4992d87d182c9 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Tue, 28 Nov 2023 17:18:46 -0800 Subject: [PATCH 061/234] feat: add tracing to Ucan::capabilities_for --- Cargo.toml | 1 + src/ucan.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f19758f2..450ae834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" +tracing = "0.1.40" unsigned-varint = "0.7.2" url = "2.4.1" web-time = "0.2.3" diff --git a/src/ucan.rs b/src/ucan.rs index e0939932..ba5e6584 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -17,6 +17,7 @@ use cid::{ use libipld_core::{ipld::Ipld, raw::RawCodec}; use semver::Version; use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; +use tracing::{span, Level}; /// The current UCAN version pub const UCAN_VERSION: &str = "0.10.0"; @@ -136,6 +137,7 @@ where /// Returns true if the UCAN is authorized by the given issuer to /// perform the ability against the resource + #[tracing::instrument(level = "trace", skip_all, fields(issuer = issuer.as_ref(), %resource, %ability, %at_time, self = %self.to_cid(None)?))] pub fn capabilities_for( &self, issuer: impl AsRef, @@ -158,21 +160,38 @@ where self.validate(at_time, did_verifier_map)?; for capability in self.capabilities() { + let span = span!(Level::TRACE, "capability", ?capability); + let _enter = span.enter(); + let attenuated = Capability::clone_box(&resource, &ability, capability.caveat()); if !attenuated.is_subsumed_by(capability) { + tracing::trace!("skipping (not subsumed by)"); + continue; } if self.issuer() == issuer { + tracing::trace!("matched (by parenthood)"); + capabilities.push(attenuated.clone()) } proof_queue.push_back((self.clone(), capability.clone(), attenuated)); + + tracing::trace!("enqueued"); } while let Some((ucan, attenuated_cap, leaf_cap)) = proof_queue.pop_front() { + let span = + span!(Level::TRACE, "ucan", ucan = %ucan.to_cid(None)?, ?attenuated_cap, ?leaf_cap); + + let _enter = span.enter(); + for proof_cid in ucan.proofs().unwrap_or(vec![]) { + let span = span!(Level::TRACE, "proof", cid = %proof_cid); + let _enter = span.enter(); + match store .read::(proof_cid) .map_err(|e| Error::InternalUcanError { @@ -199,23 +218,33 @@ where })?; if !proof_ucan.lifetime_encompasses(&ucan) { + tracing::trace!("skipping (lifetime not encompassed)"); + continue; } if ucan.issuer() != proof_ucan.audience() { + tracing::trace!("skipping (issuer != audience)"); + continue; } if proof_ucan.validate(at_time, did_verifier_map).is_err() { + tracing::trace!("skipping (validation failed)"); + continue; } for capability in proof_ucan.capabilities() { if !attenuated_cap.is_subsumed_by(capability) { + tracing::trace!("skipping (not subsumed by)"); + continue; } if proof_ucan.issuer() == issuer { + tracing::trace!("matched (by parenthood)"); + capabilities.push(leaf_cap.clone()); } @@ -224,6 +253,8 @@ where capability.clone(), leaf_cap.clone(), )); + + tracing::trace!("enqueued"); } } Some(ipld) => { From 692a4d41bf54e65068709aa18c35becf5854089b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 09:55:42 -0800 Subject: [PATCH 062/234] Small change to get this up in a PR --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c36d8842..3cd168ff 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,6 @@
:warning: Work in progress :warning:
-## - ## Outline - [Usage](#usage) From c6e9e17764404e5ddb66fd7c12d932091c622821 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:40:58 -0800 Subject: [PATCH 063/234] feat: move flake.nix to numtide/devshell --- README.md | 2 +- flake.lock | 109 +++++++++++++++---- flake.nix | 288 ++++++++++++++++++++++++++++++++++++++++++------- pre-commit.nix | 9 ++ 4 files changed, 349 insertions(+), 59 deletions(-) create mode 100644 pre-commit.nix diff --git a/README.md b/README.md index 3cd168ff..d9e5b879 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "0.1.0" +rs-ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/flake.lock b/flake.lock index 29249c58..51184076 100644 --- a/flake.lock +++ b/flake.lock @@ -1,18 +1,21 @@ { "nodes": { - "flake-compat": { - "flake": false, + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "lastModified": 1705332421, + "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", + "owner": "numtide", + "repo": "devshell", + "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "numtide", + "repo": "devshell", "type": "github" } }, @@ -21,11 +24,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -34,13 +37,46 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixos-unstable": { + "locked": { + "lastModified": 1705556346, + "narHash": "sha256-2+ZUEFCKlctTsut81S84xkCccMsZLLX7DA/U3xZ3BqY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cefcf19e1c6d4255b2aede5535d04064f6917e9b", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable-small", + "type": "indirect" + } + }, "nixpkgs": { "locked": { - "lastModified": 1698266953, - "narHash": "sha256-jf72t7pC8+8h8fUslUYbWTX5rKsRwOzRMX8jJsGqDXA=", + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "75a52265bda7fd25e06e3a67dee3f0354e73243c", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", "type": "github" }, "original": { @@ -50,11 +86,27 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1705458851, + "narHash": "sha256-uQvEhiv33Zj/Pv364dTvnpPwFSptRZgVedDzoM+HqVg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8bf65f17d8070a0a490daf5f1c784b87ee73982c", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.11", + "type": "indirect" + } + }, "root": { "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", + "devshell": "devshell", + "flake-utils": "flake-utils_2", + "nixos-unstable": "nixos-unstable", + "nixpkgs": "nixpkgs_2", "rust-overlay": "rust-overlay" } }, @@ -68,11 +120,11 @@ ] }, "locked": { - "lastModified": 1698199907, - "narHash": "sha256-n8RtHBIb0rLuYs4RDehW6mj6r6Yam/ODY1af/VCcurw=", + "lastModified": 1705544242, + "narHash": "sha256-LIi5jGx7kwJjodpJlnQY+X/PZspRpbDO2ypNSmHwOGQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "22b8d29fd22cfaa2c311e0d6fd8a0ed9c2a1152b", + "rev": "ff3e4b3ee418009886848d48e4ba236a2f9de789", "type": "github" }, "original": { @@ -95,6 +147,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 71d7c6cd..9f7f8649 100644 --- a/flake.nix +++ b/flake.nix @@ -2,13 +2,11 @@ description = "rs-ucan"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "nixpkgs/nixos-23.11"; + nixos-unstable.url = "nixpkgs/nixos-unstable-small"; - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; + flake-utils.url = "github:numtide/flake-utils"; + devshell.url = "github:numtide/devshell"; rust-overlay = { url = "github:oxalica/rust-overlay"; @@ -20,28 +18,66 @@ outputs = { self, nixpkgs, - flake-compat, + nixos-unstable, flake-utils, + devshell, rust-overlay, } @ inputs: flake-utils.lib.eachDefaultSystem ( system: let - overlays = [(import rust-overlay)]; - pkgs = import nixpkgs {inherit system overlays;}; + pkgs = import nixpkgs { + inherit system; + overlays = [ + devshell.overlays.default + (import rust-overlay) + (final: prev: { + rustfmt = prev.rust-bin.nightly.latest.rustfmt; + }) + ]; + }; - rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - extensions = ["cargo" "clippy" "rustfmt" "rust-src" "rust-std"]; - targets = ["wasm32-unknown-unknown" "wasm32-wasi"]; + unstable = import nixos-unstable { + inherit system; }; - nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + # nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + + rust-toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; + + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" + + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" + + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt alejandra + taplo + ]; + + darwin-installs = with pkgs.darwin.apple_sdk.frameworks; [ + Security + CoreFoundation + Foundation ]; cargo-installs = with pkgs; [ + cargo-criterion cargo-deny cargo-expand cargo-nextest @@ -49,59 +85,237 @@ cargo-sort cargo-udeps cargo-watch - binaryen + llvmPackages.bintools + twiggy + unstable.cargo-component wasm-bindgen-cli + wasm-tools ]; - in rec - { - devShells.default = pkgs.mkShell { + in rec { + devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; - # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; + imports = [./pre-commit.nix]; - nativeBuildInputs = with pkgs; + packages = with pkgs; [ # The ordering of these two items is important. For nightly rustfmt to be used instead of # the rustfmt provided by `rust-toolchain`, it must appear first in the list. This is # because native build inputs are added to $PATH in the order they're listed here. - nightly-rustfmt + # nightly-rustfmt rust-toolchain - pre-commit - protobuf + direnv self.packages.${system}.irust - nodejs + + chromedriver nodePackages.pnpm + protobuf + unstable.nodejs_20 + unstable.wasmtime ] ++ format-pkgs ++ cargo-installs - ++ lib.optionals stdenv.isDarwin [ - darwin.apple_sdk.frameworks.Security - darwin.apple_sdk.frameworks.CoreFoundation - darwin.apple_sdk.frameworks.Foundation - ]; - - shellHook = '' - [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg - ''; + ++ lib.optionals stdenv.isDarwin darwin-installs; + + env = [ + { + name = "RUSTC_WRAPPER"; + value = "${pkgs.sccache}/bin/sccache"; + } + ]; + + commands = [ + # Release + { + name = "release"; + help = "[DEFAULT] Release (optimized build) for current host target"; + category = "release"; + command = "release:host"; + } + { + name = "release:host"; + help = "Release for current host target"; + category = "release"; + command = "${pkgs.cargo}/bin/cargo build --release"; + } + { + name = "release:wasm"; + help = "Release for current host target"; + category = "release"; + command = "${pkgs.cargo}/bin/cargo build --release --target=wasm32-unknown-unknown"; + } + # Build + { + name = "build"; + help = "[DEFAULT] Build for current host target"; + category = "build"; + command = "build:host"; + } + { + name = "build:host"; + help = "Build for current host target"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build"; + } + { + name = "build:wasm"; + help = "Build for wasm32-unknown-unknown"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build --target=wasm32-unknown-unknown"; + } + { + name = "build:wasi"; + help = "Build for WASI"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build --target wasm32-wasi"; + } + # Bench + { + name = "bench:host"; + help = "Run host Criterion benchmarks"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo criterion"; + } + { + name = "bench:host:open"; + help = "Open host Criterion benchmarks in browser"; + category = "dev"; + command = "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; + } + # Lint + { + name = "lint"; + help = "Run Clippy"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy"; + } + { + name = "lint:pedantic"; + help = "Run Clippy pedantically"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy -- -W clippy::pedantic"; + } + { + name = "lint:fix"; + help = "Apply non-pendantic Clippy suggestions"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy --fix"; + } + # Watch + { + name = "watch:build:host"; + help = "Rebuild host target on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear"; + } + { + name = "watch:build:wasm"; + help = "Rebuild host target on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; + } + { + name = "watch:lint"; + help = "Lint on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec clippy"; + } + { + name = "watch:lint:pedantic"; + help = "Pedantic lint on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec 'clippy -- -W clippy::pedantic'"; + } + { + name = "watch:test:host"; + help = "Run all tests on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + } + { + name = "watch:test:docs:host"; + help = "Run all tests on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + } + # Test + { + name = "test:all"; + help = "Run Cargo tests"; + category = "test"; + command = "test:host && test:docs && test:wasm"; + } + { + name = "test:host"; + help = "Run Cargo tests for host target"; + category = "test"; + command = "${pkgs.cargo}/bin/cargo test"; + } + { + name = "test:wasm"; + help = "Run wasm-pack tests on all targets"; + category = "test"; + command = "test:wasm:node && test:wasm:chrome"; + } + { + name = "test:wasm:nodejs"; + help = "Run wasm-pack tests in Node.js"; + category = "test"; + command = "${pkgs.wasm-pack}/bin/wasm-pack test --node"; + } + { + name = "test:wasm:chrome"; + help = "Run wasm-pack tests in headless Chrome"; + category = "test"; + command = "${pkgs.wasm-pack}/bin/wasm-pack test --headless --chrome"; + } + { + name = "test:docs"; + help = "Run Cargo doctests"; + category = "test"; + command = "${pkgs.cargo}/bin/cargo test --doc"; + } + # Docs + { + name = "docs"; + help = "[DEFAULT]: Open refreshed docs"; + category = "dev"; + command = "docs:open"; + } + { + name = "docs:build"; + help = "Refresh the docs"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo doc"; + } + { + name = "docs:open"; + help = "Open refreshed docs"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo doc --open"; + } + ]; }; packages.irust = pkgs.rustPlatform.buildRustPackage rec { pname = "irust"; - version = "1.70.0"; + version = "1.71.19"; src = pkgs.fetchFromGitHub { owner = "sigmaSd"; repo = "IRust"; - rev = "v${version}"; - sha256 = "sha256-chZKesbmvGHXwhnJRZbXyX7B8OwJL9dJh0O1Axz/n2E="; + rev = "irust@${version}"; + sha256 = "sha256-R3EAovCI5xDCQ5R69nMeE6v0cGVcY00O3kV8qHf0akc="; }; doCheck = false; - cargoSha256 = "sha256-FmsD3ajMqpPrTkXCX2anC+cmm0a2xuP+3FHqzj56Ma4="; + cargoSha256 = "sha256-2aVCNz/Lw7364B5dgGaloVPcQHm2E+b/BOxF6Qlc8Hs="; }; formatter = pkgs.alejandra; + + # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + stdenv = pkgs.clangStdenv; } ); } diff --git a/pre-commit.nix b/pre-commit.nix new file mode 100644 index 00000000..b4ed07d7 --- /dev/null +++ b/pre-commit.nix @@ -0,0 +1,9 @@ +{pkgs, ...}: let + pc = "${pkgs.pre-commit}/bin/pre-commit"; +in { + config = { + devshell.startup.pre-commit.text = '' + [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) + ''; + }; +} From 3177cae19c1b640f74494c6e6b7e241e2e490f14 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:58:07 -0800 Subject: [PATCH 064/234] Update README to use new GitHub markdown features --- README.md | 66 +++++++++++++++++++++++++++++++++---------------------- flake.nix | 17 +++++++------- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d9e5b879..d7a61f2f 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,6 @@
:warning: Work in progress :warning:
-## Outline - -- [Usage](#usage) -- [Testing the Project](#testing-the-project) -- [Benchmarking the Project](#benchmarking-the-project) -- [Contributing](#contributing) -- [Getting Help](#getting-help) -- [External Resources](#external-resources) -- [License](#license) - ## Usage Add the following to the `[dependencies]` section of your `Cargo.toml` file: @@ -49,11 +39,11 @@ rs-ucan = "1.0.0-rc.1" ## Testing the Project -- Run tests +Run tests - ```console - cargo test - ``` +| Cargo | Nix | +|--------------|------------| +| `cargo test` | `test:all` | ## Benchmarking the Project @@ -71,19 +61,23 @@ for integrating [proptest][proptest] within the the suite for working with ## Contributing :balloon: We're thankful for any feedback and help in improving our project! -We have a [contributing guide](./CONTRIBUTING.md) to help you get involved. We -also adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md). +We have a [contributing guide][CONTRIBUTING] to help you get involved. We +also adhere to our [Code of Conduct]. ### Nix -This repository contains a [Nix flake][nix-flake] that initiates both the Rust -toolchain set in [rust-toolchain.toml](./rust-toolchain.toml) and a -[pre-commit hook](#pre-commit-hook). It also installs helpful cargo binaries for -development. Please install [nix][nix] and [direnv][direnv] to get started. +This repository contains a [Nix flake] that initiates both the Rust +toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also +installs helpful cargo binaries for development. + +Please install [nix] to get started. We also recommend installing [direnv]. Run `nix develop` or `direnv allow` to load the `devShell` flake output, according to your preference. +The Nix shell also includes several helpful shortcut commands. +You can see a complete list of commands via the `menu` command. + ### Formatting For formatting Rust in particular, we automatically format on `nightly`, as it @@ -123,9 +117,9 @@ a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: ## Getting Help -For usage questions, usecases, or issues reach out to us in our [Discord channel](https://discord.gg/4UdeQhw7fv). +For usage questions, usecases, or issues reach out to us in the [UCAN Discord]. -We would be happy to try to answer your question or try opening a new issue on Github. +We would be happy to try to answer your question or try opening a new issue on GitHub. ## External Resources @@ -133,11 +127,30 @@ These are references to specifications, talks and presentations, etc. ## License -This project is licensed under the [Apache License 2.0](./LICENSE), or -[http://www.apache.org/licenses/LICENSE-2.0][apache]. +This project is licensed under the [Apache License 2.0][LICENSE], or +[http://www.apache.org/licenses/LICENSE-2.0][Apache]. + + + +[Benchmarking the Project]: #benchmarking-the-project +[Contributing]: #contributing +[External Resources]: #external-resources +[Getting Help]: #getting-help +[License]: #license +[Testing the Project]: #testing-the-project +[Usage]: #usage +[pre-commit hook]: #pre-commit-hook + + + +[CONTRIBUTING]: ./CONTRIBUTING.md +[LICENSE]: ./LICENSE +[Code of Conduct]: ./CODE_OF_CONDUCT.md +[`rust-toolchain.toml`]: ./rust-toolchain.toml + -[apache]: https://www.apache.org/licenses/LICENSE-2.0 +[Apache]: https://www.apache.org/licenses/LICENSE-2.0 [cargo-expand]: https://github.com/dtolnay/cargo-expand [cargo-udeps]: https://github.com/est31/cargo-udeps [cargo-watch]: https://github.com/watchexec/cargo-watch @@ -147,7 +160,8 @@ This project is licensed under the [Apache License 2.0](./LICENSE), or [direnv]:https://direnv.net/ [irust]: https://github.com/sigmaSd/IRust [nix]:https://nixos.org/download.html -[nix-flake]: https://nixos.wiki/wiki/Flakes +[Nix flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ [proptest]: https://github.com/proptest-rs/proptest [strategies]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html +[UCAN Discord]: https://discord.gg/4UdeQhw7fv diff --git a/flake.nix b/flake.nix index 9f7f8649..3a7558f8 100644 --- a/flake.nix +++ b/flake.nix @@ -17,10 +17,10 @@ outputs = { self, - nixpkgs, - nixos-unstable, - flake-utils, devshell, + flake-utils, + nixos-unstable, + nixpkgs, rust-overlay, } @ inputs: flake-utils.lib.eachDefaultSystem ( @@ -40,8 +40,6 @@ inherit system; }; - # nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; - rust-toolchain = pkgs.rust-bin.stable.latest.default.override { extensions = [ "cargo" @@ -99,9 +97,10 @@ packages = with pkgs; [ - # The ordering of these two items is important. For nightly rustfmt to be used instead of - # the rustfmt provided by `rust-toolchain`, it must appear first in the list. This is - # because native build inputs are added to $PATH in the order they're listed here. + # NOTE: The ordering of these two items is important. For nightly rustfmt to be used + # instead of the rustfmt provided by `rust-toolchain`, it must appear first in the list. + # This is because native build inputs are added to $PATH in the order they're listed here. + # # nightly-rustfmt rust-toolchain @@ -314,7 +313,7 @@ formatter = pkgs.alejandra; - # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide stdenv = pkgs.clangStdenv; } ); From a31445a1b4f6e7864ce3acdf2d8d1fa940f7ca41 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:58:51 -0800 Subject: [PATCH 065/234] Trim whitespace --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7a61f2f..3477406f 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ also adhere to our [Code of Conduct]. ### Nix This repository contains a [Nix flake] that initiates both the Rust -toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also +toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also installs helpful cargo binaries for development. Please install [nix] to get started. We also recommend installing [direnv]. From 3cda14fe16627d5909cf7b663f4e497cc8ca8542 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:11:31 -0800 Subject: [PATCH 066/234] feat: frustration --- README.md | 16 ++++++++-------- flake.nix | 52 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 3477406f..f9c56b2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- rs-ucan Logo + rs-ucan Logo

rs-ucan

@@ -41,9 +41,9 @@ rs-ucan = "1.0.0-rc.1" Run tests -| Cargo | Nix | -|--------------|------------| -| `cargo test` | `test:all` | +| Nix | Cargo | +|-----------|--------------| +| `test:all | `cargo test` | ## Benchmarking the Project @@ -52,11 +52,11 @@ For benchmarking and measuring performance, this project leverages for integrating [proptest][proptest] within the the suite for working with [strategies][strategies] and sampling from randomly generated values. -- Run benchmarks +## Benchmarks - ```console - cargo bench --features test_utils - ``` +| Nix | Cargo | +|---------|-------------------------------------| +| `bench` | `cargo bench --features test_utils` | ## Contributing diff --git a/flake.nix b/flake.nix index 3a7558f8..f9fb6703 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,9 @@ wasm-bindgen-cli wasm-tools ]; + + cargo = "${pkgs.cargo}/bin/cargo"; + wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; @@ -136,13 +139,13 @@ name = "release:host"; help = "Release for current host target"; category = "release"; - command = "${pkgs.cargo}/bin/cargo build --release"; + command = "${cargo} build --release"; } { name = "release:wasm"; help = "Release for current host target"; category = "release"; - command = "${pkgs.cargo}/bin/cargo build --release --target=wasm32-unknown-unknown"; + command = "${cargo} build --release --target=wasm32-unknown-unknown"; } # Build { @@ -155,26 +158,33 @@ name = "build:host"; help = "Build for current host target"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build"; + command = "${cargo} build"; } { name = "build:wasm"; help = "Build for wasm32-unknown-unknown"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build --target=wasm32-unknown-unknown"; + command = "${cargo} build --target=wasm32-unknown-unknown"; } { name = "build:wasi"; help = "Build for WASI"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build --target wasm32-wasi"; + command = "${cargo} build --target wasm32-wasi"; } # Bench + { + name = "bench"; + help = "Run benchmarks, including test utils"; + category = "dev"; + command = "${cargo} bench --features test_utils"; + } + # FIXME align with `bench`? { name = "bench:host"; help = "Run host Criterion benchmarks"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo criterion"; + command = "${cargo} criterion"; } { name = "bench:host:open"; @@ -187,56 +197,56 @@ name = "lint"; help = "Run Clippy"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy"; + command = "${cargo} clippy"; } { name = "lint:pedantic"; help = "Run Clippy pedantically"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy -- -W clippy::pedantic"; + command = "${cargo} clippy -- -W clippy::pedantic"; } { name = "lint:fix"; help = "Apply non-pendantic Clippy suggestions"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy --fix"; + command = "${cargo} clippy --fix"; } # Watch { name = "watch:build:host"; help = "Rebuild host target on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear"; + command = "${cargo} watch --clear"; } { name = "watch:build:wasm"; help = "Rebuild host target on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; + command = "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; } { name = "watch:lint"; help = "Lint on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec clippy"; + command = "${cargo} watch --clear --exec clippy"; } { name = "watch:lint:pedantic"; help = "Pedantic lint on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec 'clippy -- -W clippy::pedantic'"; + command = "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; } { name = "watch:test:host"; help = "Run all tests on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + command = "${cargo} watch --clear --exec test"; } { name = "watch:test:docs:host"; help = "Run all tests on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + command = "${cargo} watch --clear --exec test"; } # Test { @@ -249,7 +259,7 @@ name = "test:host"; help = "Run Cargo tests for host target"; category = "test"; - command = "${pkgs.cargo}/bin/cargo test"; + command = "${cargo} test"; } { name = "test:wasm"; @@ -261,19 +271,19 @@ name = "test:wasm:nodejs"; help = "Run wasm-pack tests in Node.js"; category = "test"; - command = "${pkgs.wasm-pack}/bin/wasm-pack test --node"; + command = "${wasm-pack} test --node"; } { name = "test:wasm:chrome"; help = "Run wasm-pack tests in headless Chrome"; category = "test"; - command = "${pkgs.wasm-pack}/bin/wasm-pack test --headless --chrome"; + command = "${wasm-pack} test --headless --chrome"; } { name = "test:docs"; help = "Run Cargo doctests"; category = "test"; - command = "${pkgs.cargo}/bin/cargo test --doc"; + command = "${cargo} test --doc"; } # Docs { @@ -286,13 +296,13 @@ name = "docs:build"; help = "Refresh the docs"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo doc"; + command = "${cargo} doc"; } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo doc --open"; + command = "${cargo} doc --open"; } ]; }; From d6a62fb227660e1f27c939ae31c1fc9c13c670c9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:12:11 -0800 Subject: [PATCH 067/234] fix: typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f9c56b2c..76ceb6cf 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ rs-ucan = "1.0.0-rc.1" Run tests -| Nix | Cargo | -|-----------|--------------| -| `test:all | `cargo test` | +| Nix | Cargo | +|------------|--------------| +| `test:all` | `cargo test` | ## Benchmarking the Project From 0e0efa25917544b7dcad03a5765543e883508e08 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:19:00 -0800 Subject: [PATCH 068/234] Adding some TODOs and FIXMEs to come back to --- Cargo.toml | 12 ++++++------ examples/counterparts.rs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 450ae834..23d96792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,16 +2,16 @@ name = "rs-ucan" version = "0.1.0" description = "Rust implementation of UCAN" -keywords = [] +keywords = ["capabilities", "authorization", "ucan"] categories = [] include = ["/src", "/examples", "/benches", "README.md", "LICENSE"] license = "Apache-2.0" readme = "README.md" edition = "2021" -rust-version = "1.67" +rust-version = "1.75" documentation = "https://docs.rs/rs-ucan" repository = "https://github.com/ucan-wg/rs-ucan" -authors = ["Quinn Wilton "] +authors = ["Quinn Wilton ", "Brooklyn Zelenka Result<(), Box> { println!("Alien Shore!"); Ok(()) From 77dafd9672da61e39c4c76cc04044a02966e56b0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 16:37:58 -0800 Subject: [PATCH 069/234] Exploratory programming --- Cargo.toml | 8 +- README.md | 26 +-- flake.nix | 83 ++++---- src/lib.rs | 577 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 636 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23d96792..8c6ad43c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +aquamarine = {version = "0.5", optional = true} async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } @@ -58,6 +59,7 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = "2.4.1" +void = "1.0" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -74,7 +76,7 @@ wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] -multihash = "0.18.0" +multihash = "0.18" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" @@ -105,11 +107,13 @@ es512-verifier = ["es512"] ps256-verifier = ["ps256"] rs256-verifier = ["rs256"] bls-verifier = ["bls"] +mermaid_docs = ["aquamarine"] -[metadata.docs.rs] # FIXME cargo complains that it doesn't understand this? +[package.metadata.docs.rs] all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] +cargo-args = ["--features='mermaid_docs'"] # See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. # [profile.release] diff --git a/README.md b/README.md index 76ceb6cf..26b9b3ea 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Run tests ## Benchmarking the Project For benchmarking and measuring performance, this project leverages -[criterion][criterion] and a `test_utils` feature flag -for integrating [proptest][proptest] within the the suite for working with -[strategies][strategies] and sampling from randomly generated values. +[Criterion] and a `test_utils` feature flag +for integrating [proptest] within the the suite for working with +[strategies] and sampling from randomly generated values. ## Benchmarks @@ -70,7 +70,7 @@ This repository contains a [Nix flake] that initiates both the Rust toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also installs helpful cargo binaries for development. -Please install [nix] to get started. We also recommend installing [direnv]. +Please install [Nix] to get started. We also recommend installing [direnv]. Run `nix develop` or `direnv allow` to load the `devShell` flake output, according to your preference. @@ -85,7 +85,7 @@ uses specific nightly features we recommend by default. ### Pre-commit Hook -This project recommends using [pre-commit][pre-commit] for running pre-commit +This project recommends using [pre-commit] for running pre-commit hooks. Please run this before every commit and/or push. - If you are doing interim commits locally, and for some reason if you _don't_ @@ -95,7 +95,7 @@ hooks. Please run this before every commit and/or push. ### Recommended Development Flow - We recommend leveraging [cargo-watch][cargo-watch], - [cargo-expand][cargo-expand] and [irust][irust] for Rust development. + [`cargo-expand`] and [IRust] for Rust development. - We recommend using [cargo-udeps][cargo-udeps] for removing unused dependencies before commits and pull-requests. @@ -127,7 +127,7 @@ These are references to specifications, talks and presentations, etc. ## License -This project is licensed under the [Apache License 2.0][LICENSE], or +This project is [licensed under the Apache License 2.0][LICENSE], or [http://www.apache.org/licenses/LICENSE-2.0][Apache]. @@ -151,15 +151,15 @@ This project is licensed under the [Apache License 2.0][LICENSE], or [Apache]: https://www.apache.org/licenses/LICENSE-2.0 -[cargo-expand]: https://github.com/dtolnay/cargo-expand -[cargo-udeps]: https://github.com/est31/cargo-udeps -[cargo-watch]: https://github.com/watchexec/cargo-watch +[`cargo-expand`]: https://github.com/dtolnay/cargo-expand +[`cargo-udeps`]: https://github.com/est31/cargo-udeps +[`cargo-watch`]: https://github.com/watchexec/cargo-watch [commit-spec]: https://www.conventionalcommits.org/en/v1.0.0/#specification [commit-spec-site]: https://www.conventionalcommits.org/ -[criterion]: https://github.com/bheisler/criterion.rs +[Criterion]: https://github.com/bheisler/criterion.rs [direnv]:https://direnv.net/ -[irust]: https://github.com/sigmaSd/IRust -[nix]:https://nixos.org/download.html +[IRust]: https://github.com/sigmaSd/IRust +[Nix]:https://nixos.org/download.html [Nix flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ [proptest]: https://github.com/proptest-rs/proptest diff --git a/flake.nix b/flake.nix index f9fb6703..ddfdabd2 100644 --- a/flake.nix +++ b/flake.nix @@ -23,44 +23,38 @@ nixpkgs, rust-overlay, } @ inputs: - flake-utils.lib.eachDefaultSystem ( - system: let - pkgs = import nixpkgs { - inherit system; - overlays = [ - devshell.overlays.default - (import rust-overlay) - (final: prev: { - rustfmt = prev.rust-bin.nightly.latest.rustfmt; - }) - ]; - }; + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ + devshell.overlays.default + (import rust-overlay) + ]; - unstable = import nixos-unstable { - inherit system; - }; + pkgs = import nixpkgs { inherit system overlays; }; + unstable = import nixos-unstable { inherit system overlays; }; - rust-toolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ - "cargo" - "clippy" - "llvm-tools-preview" - "rust-src" - "rust-std" - "rustfmt" - ]; + rust-toolchain = + (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; - targets = [ - "aarch64-apple-darwin" - "x86_64-apple-darwin" + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" - "x86_64-unknown-linux-musl" - "aarch64-unknown-linux-musl" + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" - "wasm32-unknown-unknown" - "wasm32-wasi" - ]; - }; + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt @@ -91,7 +85,9 @@ ]; cargo = "${pkgs.cargo}/bin/cargo"; + node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; + in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; @@ -100,20 +96,15 @@ packages = with pkgs; [ - # NOTE: The ordering of these two items is important. For nightly rustfmt to be used - # instead of the rustfmt provided by `rust-toolchain`, it must appear first in the list. - # This is because native build inputs are added to $PATH in the order they're listed here. - # - # nightly-rustfmt - rust-toolchain - direnv + rust-toolchain self.packages.${system}.irust + (pkgs.hiPrio pkgs.rust-bin.nightly.latest.rustfmt) chromedriver - nodePackages.pnpm protobuf unstable.nodejs_20 + unstable.nodePackages.pnpm unstable.wasmtime ] ++ format-pkgs @@ -166,6 +157,12 @@ category = "build"; command = "${cargo} build --target=wasm32-unknown-unknown"; } + { + name = "build:node"; + help = "Build JS-wrapped Wasm library"; + category = "build"; + command = "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; + } { name = "build:wasi"; help = "Build for WASI"; @@ -296,13 +293,13 @@ name = "docs:build"; help = "Refresh the docs"; category = "dev"; - command = "${cargo} doc"; + command = "${cargo} doc --features=mermaid_docs"; } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; - command = "${cargo} doc --open"; + command = "${cargo} doc --features=mermaid_docs --open"; } ]; }; diff --git a/src/lib.rs b/src/lib.rs index a7a023f4..75d898c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,583 @@ pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; /// A decentralized identifier. pub type Did = String; +use libipld_core::{ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; +use url::Url; +use void::Void; +use web_time::{Instant, SystemTime}; + +pub struct InvocationPayload { + pub issuer: Did, + pub subject: Did, + pub audience: Option, + + pub ability: Ability, // FIXME check name in spec + + // pub proofs: Vec>>, + // pub cause: Option>>, // FIXME? + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +pub struct DelegationPayload { + pub issuer: Did, + pub subject: Did, + pub audience: Did, + + pub capability_builder: A::Builder, // FIXME + pub conditions: Box<[Cond]>, // Worth it over a Vec? + + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From> for DelegationPayload { + fn from(invocation: InvocationPayload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + capability_builder: >::into(invocation.ability.clone()), + conditions: Box::new([]), + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} + +// impl DelegationPayload { +// fn check( +// &self, +// proof: &DelegationPayload + Capability, Cond>, +// now: SystemTime, +// ) -> Result<(), ()> { +// // FIXME heavily WIP +// // FIXME signature +// self.check_time(now).unwrap(); +// self.check_issuer(&proof.audience)?; // FIXME alignment +// self.check_subject(&proof.subject)?; +// self.check_conditions(&proof.conditions)?; +// +// proof.check_expiration(now)?; +// proof.check_not_before(now)?; +// +// self.check_ability(&proof.capability_builder)?; +// Ok(()) +// } +// } + +// FIXME add is_roo t + +pub trait TryProven { + type Proven1; + type Error1; + fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; +} + +impl TryProven for U +where + T: TryProve, +{ + type Proven1 = T::Proven; + type Error1 = T::Error; + + fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + candidate.try_prove(self) + } +} + +pub struct DelegateAny; + +// FIXME ToBuilder + +// FIXME Remove +// pub trait Prove { +// type Witness; +// // FIXME make sure that passing the top-level item through and not checking each +// // item in the chain against the next one is correct in the 1.0 semantics +// fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; +// } + +impl TryProve for T { + type Error = Void; + type Proven = T; + + fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + Ok(self) + } +} + +// impl Prove for T { +// type Witness = T; +// +// fn prove<'a>(&'a self, proof: &'a DelegateAny) -> &Self::Witness { +// self +// } +// } +// +// impl> TryProve for T { +// type Error = Void; +// type Proven = T; +// +// fn try_prove<'a>(&'a self, proof: &'a T) -> Result<&'a T, Void> { +// Ok(proof) +// } +// } + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// FIXME +/// +/// ```mermaid +/// flowchart LR +/// Invocation --> more --> Self --> Candidate --> more2 +/// more[...] +/// more2[...] +/// ``` +pub trait TryProve { + type Error; + type Proven; + + fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; +} + +///////////// + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Msg { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgSend { + to: Url, + from: Url, + message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceive { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceiveBuilder { + to: Option, + from: Option, +} + +impl From for MsgReceiveBuilder { + fn from(msg: MsgReceive) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for MsgReceive { + type Error = MsgReceiveBuilder; + + fn try_from(builder: MsgReceiveBuilder) -> Result { + // FIXME + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(builder.clone()) // FIXME + } + } +} + +impl From for Ipld { + fn from(msg: MsgReceive) -> Self { + let mut map = BTreeMap::new(); + map.insert("to".into(), msg.to.to_string().into()); + map.insert("from".into(), msg.from.to_string().into()); + map.into() + } +} + +impl TryFrom<&Ipld> for MsgReceiveBuilder { + type Error = (); + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("to") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { to, from }) + } + _ => Err(()), + } + } +} + +impl Capability for MsgReceive { + type Builder = MsgReceiveBuilder; + const COMMAND: &'static str = "msg/receive"; +} + +impl TryFrom<&Ipld> for MsgReceive { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + todo!() + } +} + +impl TryProve for Msg { + type Error = (); // FIXME + type Proven = Msg; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgSend { + type Error = (); // FIXME + type Proven = MsgSend; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME this needs to work on builders! +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +/////////// + +// FIXME remove +// impl> TryProve for P { +// type Error = Void; +// type Proven = P::Witness; +// +// fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Void> { +// Ok(self.prove(candidate)) +// } +// } + +impl TryProve for CrudDestroy { + type Error = (); // FIXME + type Proven = CrudDestroy; + fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME ProveWith? +impl TryProve for CrudDestroy { + type Error = (); // FIXME + type Proven = CrudDestroy; + + fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudRead { + type Error = (); + type Proven = CrudRead; + + fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + // FIXME contains & args + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudRead { + type Error = (); // FIXME + type Proven = CrudRead; + + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudMutate { + type Error = (); // FIXME + type Proven = CrudMutate; + + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME +impl> TryProve for C { + type Error = (); + type Proven = C; + + // FIXME + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { + match self.try_prove(&CrudMutate { + uri: candidate.uri.clone(), + }) { + Ok(_) => Ok(self), + Err(_) => Err(()), + } + } +} + +// FIXME lives etirely in bindgen +// https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html +// pub struct DynamicJs { +// pub command: Box, +// pub args: BTreeMap, Ipld>, +// } +// +// impl TryProve for DynamicJs { +// type Error = (); +// type Proven = DynamicJs; +// +// fn try_prove<'a>(&'a self, candidate: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// +// } +// } + +// impl ProveDelegaton for DynamicJs { +// type Error = anyhow::Error; +// +// fn prove(&self, proof: &DynamicJs) -> Result { +// todo!() +// } +// } +// + +pub struct Crud { + uri: Url, +} + +pub struct CrudMutate { + uri: Url, +} + +pub struct CrudCreate { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct CrudRead { + pub uri: Url, + pub args: BTreeMap, String>, // FIXME need these? +} + +pub struct CrudUpdate { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct CrudDestroy { + pub uri: Url, +} + +// impl Capabilty for CrudRead{ +// const COMMAND = "crud/read"; +// +// fn subject(&self) -> Did { +// todo!() +// } +// } +// +// pub enum Condition { +// Contains { field: &str, value: Vec }, +// MinLength { field: &str, value: u64 }, +// MaxLength { field: &str, value: u64 }, +// Equals { field: &str, value: Ipld }, +// Regex { field: &str }, // FIXME +// +// // Silly example +// OnDayOfWeek { day: Day }, +// } + +pub enum Day { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} + +// pub trait CapBuilder: Default { +// type Ability; +// fn build(self) -> Result; +// } + +// pub trait BuilderFor: CapBuilder { +// type Builder: CapBuilder; +// } + +pub trait Capability: Sized { + // pub trait Capability: Into { + // FIXME remove sized? + // pub trait Capability: TryFrom + Into { + type Builder: From + TryInto + PartialEq + Debug; + const COMMAND: &'static str; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JsTime { + time: SystemTime, +} + +// FIXME just lifting this from Elixir for now +pub struct OutOfRangeError { + pub tried: SystemTime, +} + +impl JsTime { + // FIXME value should be a system time? + pub fn new(time: SystemTime) -> Result { + if time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + > 0x1FFFFFFFFFFFFF + { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Timestamp { + Sending(JsTime), + Receiving(SystemTime), +} + +// pub struct ReceiptPayload { +// pub issuer: Did, +// pub ran: Link>, +// pub out: UcanResult, // FIXME? +// pub proofs: Vec>>, +// pub metadata: BTreeMap, // FIXME serde value instead? +// pub issued_at: u64, +// } +// +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap<&str, Ipld>), +// } +// +// pub struct Capability { +// command: String, +// payload: BTreeMap, +// } + +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// + /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct EmptyFact {} From 9c38b9b193acaf5df0d6fb8ad16851299649e34f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 16:38:19 -0800 Subject: [PATCH 070/234] Formatter --- Cargo.toml | 3 +-- flake.nix | 46 ++++++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c6ad43c..e3eba69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" -aquamarine = {version = "0.5", optional = true} +aquamarine = { version = "0.5", optional = true } async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } @@ -114,7 +114,6 @@ all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] cargo-args = ["--features='mermaid_docs'"] - # See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. # [profile.release] # Do not perform backtrace for panic on release builds. diff --git a/flake.nix b/flake.nix index ddfdabd2..947a2386 100644 --- a/flake.nix +++ b/flake.nix @@ -23,38 +23,37 @@ nixpkgs, rust-overlay, } @ inputs: - flake-utils.lib.eachDefaultSystem (system: - let + flake-utils.lib.eachDefaultSystem ( + system: let overlays = [ devshell.overlays.default (import rust-overlay) ]; - pkgs = import nixpkgs { inherit system overlays; }; - unstable = import nixos-unstable { inherit system overlays; }; + pkgs = import nixpkgs {inherit system overlays;}; + unstable = import nixos-unstable {inherit system overlays;}; - rust-toolchain = - (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - extensions = [ - "cargo" - "clippy" - "llvm-tools-preview" - "rust-src" - "rust-std" - "rustfmt" - ]; + rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; - targets = [ - "aarch64-apple-darwin" - "x86_64-apple-darwin" + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" - "x86_64-unknown-linux-musl" - "aarch64-unknown-linux-musl" + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" - "wasm32-unknown-unknown" - "wasm32-wasi" - ]; - }; + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt @@ -87,7 +86,6 @@ cargo = "${pkgs.cargo}/bin/cargo"; node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; - in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; From a5b5434c3be028e73afe7e023c1ffc40cdb4f160 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 22:22:39 -0800 Subject: [PATCH 071/234] Splitting out separate files --- Cargo.toml | 3 +- src/ability.rs | 4 + src/ability/any.rs | 13 ++ src/ability/crud.rs | 133 ++++++++++++ src/ability/msg.rs | 181 ++++++++++++++++ src/ability/traits.rs | 9 + src/condition.rs | 26 +++ src/delegation.rs | 20 ++ src/invocation.rs | 41 ++++ src/lib.rs | 484 +----------------------------------------- src/promise.rs | 12 ++ src/prove.rs | 33 +++ src/receipt.rs | 19 ++ src/time.rs | 45 ++++ 14 files changed, 546 insertions(+), 477 deletions(-) create mode 100644 src/ability.rs create mode 100644 src/ability/any.rs create mode 100644 src/ability/crud.rs create mode 100644 src/ability/msg.rs create mode 100644 src/ability/traits.rs create mode 100644 src/condition.rs create mode 100644 src/delegation.rs create mode 100644 src/invocation.rs create mode 100644 src/promise.rs create mode 100644 src/prove.rs create mode 100644 src/receipt.rs diff --git a/Cargo.toml b/Cargo.toml index e3eba69c..21422fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" cid = "0.10.1" +did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" ecdsa = { version = "0.16.8", optional = true, default-features = false } @@ -58,7 +59,7 @@ signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = "2.4.1" +url = "2.5" void = "1.0" web-time = "0.2.3" diff --git a/src/ability.rs b/src/ability.rs new file mode 100644 index 00000000..0ffcc9fd --- /dev/null +++ b/src/ability.rs @@ -0,0 +1,4 @@ +pub mod any; +pub mod crud; +pub mod msg; +pub mod traits; diff --git a/src/ability/any.rs b/src/ability/any.rs new file mode 100644 index 00000000..b2ba7533 --- /dev/null +++ b/src/ability/any.rs @@ -0,0 +1,13 @@ +use crate::prove::TryProve; +use void::Void; + +pub struct DelegateAny; + +impl TryProve for T { + type Error = Void; + type Proven = T; + + fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + Ok(self) + } +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs new file mode 100644 index 00000000..8eac2115 --- /dev/null +++ b/src/ability/crud.rs @@ -0,0 +1,133 @@ +use crate::{promise::Promise, prove::TryProve}; +use std::{collections::BTreeMap, fmt::Debug}; +use url::Url; + +#[derive(Debug, Clone, PartialEq)] +pub enum Field +where + T: Debug + Clone + PartialEq, +{ + Value(T), + Await(Promise), +} + +// FIXME macro to derive promise versions & delagted builder versions +// ... also maybe Ipld + +pub struct Crud { + uri: Field, +} + +pub struct CrudRead { + pub uri: Field, +} + +pub struct CrudMutate { + uri: Field, +} + +pub struct CrudCreate { + pub uri: Field, + pub args: BTreeMap, Field>, +} + +pub struct CrudUpdate { + pub uri: Field, + pub args: BTreeMap, Field>, +} + +pub struct CrudDestroy { + pub uri: Field, +} + +// FIXME these should probably be behind a feature flag + +// impl Capabilty for CrudRead{ +// const COMMAND = "crud/read"; +// +// fn subject(&self) -> Did { +// todo!() +// } +// } + +// impl TryProve for CrudDestroy { +// type Error = (); // FIXME +// type Proven = CrudDestroy; +// fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// // FIXME ProveWith? +// impl TryProve for CrudDestroy { +// type Error = (); // FIXME +// type Proven = CrudDestroy; +// +// fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudRead { +// type Error = (); +// type Proven = CrudRead; +// +// fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// // FIXME contains & args +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudRead { +// type Error = (); // FIXME +// type Proven = CrudRead; +// +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudMutate { +// type Error = (); // FIXME +// type Proven = CrudMutate; +// +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// // FIXME +// impl> TryProve for C { +// type Error = (); +// type Proven = C; +// +// // FIXME +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { +// match self.try_prove(&CrudMutate { +// uri: candidate.uri.clone(), +// }) { +// Ok(_) => Ok(self), +// Err(_) => Err(()), +// } +// } +// } diff --git a/src/ability/msg.rs b/src/ability/msg.rs new file mode 100644 index 00000000..f2167dd7 --- /dev/null +++ b/src/ability/msg.rs @@ -0,0 +1,181 @@ +use crate::{ability::traits::Ability, prove::TryProve}; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, str::FromStr}; +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Msg { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgSend { + to: Url, + from: Url, + message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceive { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceiveBuilder { + to: Option, + from: Option, +} + +impl From for MsgReceiveBuilder { + fn from(msg: MsgReceive) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for MsgReceive { + type Error = MsgReceiveBuilder; + + fn try_from(builder: MsgReceiveBuilder) -> Result { + // FIXME + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(builder.clone()) // FIXME + } + } +} + +impl From for Ipld { + fn from(msg: MsgReceive) -> Self { + let mut map = BTreeMap::new(); + map.insert("to".into(), msg.to.to_string().into()); + map.insert("from".into(), msg.from.to_string().into()); + map.into() + } +} + +impl TryFrom<&Ipld> for MsgReceiveBuilder { + type Error = (); + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("from") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { to, from }) + } + _ => Err(()), + } + } +} + +impl Ability for MsgReceive { + type Builder = MsgReceiveBuilder; + const COMMAND: &'static str = "msg/receive"; +} + +impl TryFrom<&Ipld> for MsgReceive { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("from") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { + to: to.unwrap(), + from: from.unwrap(), + }) + } + _ => Err(()), + } + } +} + +impl TryProve for Msg { + type Error = (); // FIXME + type Proven = Msg; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgSend { + type Error = (); // FIXME + type Proven = MsgSend; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME this needs to work on builders! +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} diff --git a/src/ability/traits.rs b/src/ability/traits.rs new file mode 100644 index 00000000..630a9ce0 --- /dev/null +++ b/src/ability/traits.rs @@ -0,0 +1,9 @@ +use std::fmt::Debug; + +pub trait Ability: Sized { + // pub trait Capability: Into { + // FIXME remove sized? + // pub trait Capability: TryFrom + Into { + type Builder: From + TryInto + PartialEq + Debug; // FIXME + const COMMAND: &'static str; +} diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 00000000..cd627be1 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1,26 @@ +use libipld_core::ipld::Ipld; + +pub struct FieldName { + name: Box, +} + +pub enum Condition { + Contains { field: FieldName, value: Vec }, + MinLength { field: FieldName, value: u64 }, + MaxLength { field: FieldName, value: u64 }, + Equals { field: FieldName, value: Ipld }, + Regex { field: FieldName }, // FIXME + + // Silly example + OnDayOfWeek { day: Day }, +} + +pub enum Day { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} diff --git a/src/delegation.rs b/src/delegation.rs new file mode 100644 index 00000000..7547dcd3 --- /dev/null +++ b/src/delegation.rs @@ -0,0 +1,20 @@ +use crate::{ability::traits::Ability, time::Timestamp}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: DID, + + pub capability_builder: A::Builder, // FIXME + pub conditions: Box<[Cond]>, // Worth it over a Vec? + + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} diff --git a/src/invocation.rs b/src/invocation.rs new file mode 100644 index 00000000..26c50a07 --- /dev/null +++ b/src/invocation.rs @@ -0,0 +1,41 @@ +use crate::{ability::traits::Ability, delegation, time::Timestamp}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, + + pub ability: Ability, // FIXME check name in spec + + // pub proofs: Vec>>, + // pub cause: Option>>, // FIXME? + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From> for delegation::Payload { + fn from(invocation: Payload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + capability_builder: >::into(invocation.ability.clone()), + conditions: Box::new([]), + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 75d898c6..773867f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,62 +36,7 @@ pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; /// A decentralized identifier. pub type Did = String; -use libipld_core::{ipld::Ipld, link::Link}; -use std::{collections::BTreeMap, fmt::Debug}; -use url::Url; -use void::Void; -use web_time::{Instant, SystemTime}; - -pub struct InvocationPayload { - pub issuer: Did, - pub subject: Did, - pub audience: Option, - - pub ability: Ability, // FIXME check name in spec - - // pub proofs: Vec>>, - // pub cause: Option>>, // FIXME? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -pub struct DelegationPayload { - pub issuer: Did, - pub subject: Did, - pub audience: Did, - - pub capability_builder: A::Builder, // FIXME - pub conditions: Box<[Cond]>, // Worth it over a Vec? - - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -// FIXME move that clone? -impl From> for DelegationPayload { - fn from(invocation: InvocationPayload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - capability_builder: >::into(invocation.ability.clone()), - conditions: Box::new([]), - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } -} +use std::fmt::Debug; // impl DelegationPayload { // fn check( @@ -114,30 +59,6 @@ impl From> for DelegationPaylo // } // } -// FIXME add is_roo t - -pub trait TryProven { - type Proven1; - type Error1; - fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; -} - -impl TryProven for U -where - T: TryProve, -{ - type Proven1 = T::Proven; - type Error1 = T::Error; - - fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { - candidate.try_prove(self) - } -} - -pub struct DelegateAny; - -// FIXME ToBuilder - // FIXME Remove // pub trait Prove { // type Witness; @@ -146,15 +67,6 @@ pub struct DelegateAny; // fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; // } -impl TryProve for T { - type Error = Void; - type Proven = T; - - fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { - Ok(self) - } -} - // impl Prove for T { // type Witness = T; // @@ -172,270 +84,6 @@ impl TryProve for T { // } // } -#[cfg_attr(doc, aquamarine::aquamarine)] -/// FIXME -/// -/// ```mermaid -/// flowchart LR -/// Invocation --> more --> Self --> Candidate --> more2 -/// more[...] -/// more2[...] -/// ``` -pub trait TryProve { - type Error; - type Proven; - - fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; -} - -///////////// - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Msg { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgSend { - to: Url, - from: Url, - message: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgReceive { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgReceiveBuilder { - to: Option, - from: Option, -} - -impl From for MsgReceiveBuilder { - fn from(msg: MsgReceive) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for MsgReceive { - type Error = MsgReceiveBuilder; - - fn try_from(builder: MsgReceiveBuilder) -> Result { - // FIXME - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(builder.clone()) // FIXME - } - } -} - -impl From for Ipld { - fn from(msg: MsgReceive) -> Self { - let mut map = BTreeMap::new(); - map.insert("to".into(), msg.to.to_string().into()); - map.insert("from".into(), msg.from.to_string().into()); - map.into() - } -} - -impl TryFrom<&Ipld> for MsgReceiveBuilder { - type Error = (); - - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("to") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { to, from }) - } - _ => Err(()), - } - } -} - -impl Capability for MsgReceive { - type Builder = MsgReceiveBuilder; - const COMMAND: &'static str = "msg/receive"; -} - -impl TryFrom<&Ipld> for MsgReceive { - type Error = (); // FIXME - - fn try_from(ipld: &Ipld) -> Result { - todo!() - } -} - -impl TryProve for Msg { - type Error = (); // FIXME - type Proven = Msg; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self == candidate { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for MsgSend { - type Error = (); // FIXME - type Proven = MsgSend; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self.to == candidate.to && self.from == candidate.from { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for MsgReceive { - type Error = (); // FIXME - type Proven = MsgReceive; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self.to == candidate.to && self.from == candidate.from { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME this needs to work on builders! -impl TryProve for MsgReceive { - type Error = (); // FIXME - type Proven = MsgReceive; - - fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { - if self == candidate { - Ok(self) - } else { - Err(()) - } - } -} - -/////////// - -// FIXME remove -// impl> TryProve for P { -// type Error = Void; -// type Proven = P::Witness; -// -// fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Void> { -// Ok(self.prove(candidate)) -// } -// } - -impl TryProve for CrudDestroy { - type Error = (); // FIXME - type Proven = CrudDestroy; - fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME ProveWith? -impl TryProve for CrudDestroy { - type Error = (); // FIXME - type Proven = CrudDestroy; - - fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudRead { - type Error = (); - type Proven = CrudRead; - - fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - // FIXME contains & args - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudRead { - type Error = (); // FIXME - type Proven = CrudRead; - - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudMutate { - type Error = (); // FIXME - type Proven = CrudMutate; - - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME -impl> TryProve for C { - type Error = (); - type Proven = C; - - // FIXME - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { - match self.try_prove(&CrudMutate { - uri: candidate.uri.clone(), - }) { - Ok(_) => Ok(self), - Err(_) => Err(()), - } - } -} - // FIXME lives etirely in bindgen // https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html // pub struct DynamicJs { @@ -461,129 +109,13 @@ impl> TryProve for C { // } // -pub struct Crud { - uri: Url, -} - -pub struct CrudMutate { - uri: Url, -} - -pub struct CrudCreate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -pub struct CrudRead { - pub uri: Url, - pub args: BTreeMap, String>, // FIXME need these? -} - -pub struct CrudUpdate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -pub struct CrudDestroy { - pub uri: Url, -} - -// impl Capabilty for CrudRead{ -// const COMMAND = "crud/read"; -// -// fn subject(&self) -> Did { -// todo!() -// } -// } -// -// pub enum Condition { -// Contains { field: &str, value: Vec }, -// MinLength { field: &str, value: u64 }, -// MaxLength { field: &str, value: u64 }, -// Equals { field: &str, value: Ipld }, -// Regex { field: &str }, // FIXME -// -// // Silly example -// OnDayOfWeek { day: Day }, -// } - -pub enum Day { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -} - -// pub trait CapBuilder: Default { -// type Ability; -// fn build(self) -> Result; -// } - -// pub trait BuilderFor: CapBuilder { -// type Builder: CapBuilder; -// } - -pub trait Capability: Sized { - // pub trait Capability: Into { - // FIXME remove sized? - // pub trait Capability: TryFrom + Into { - type Builder: From + TryInto + PartialEq + Debug; - const COMMAND: &'static str; -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct JsTime { - time: SystemTime, -} - -// FIXME just lifting this from Elixir for now -pub struct OutOfRangeError { - pub tried: SystemTime, -} - -impl JsTime { - // FIXME value should be a system time? - pub fn new(time: SystemTime) -> Result { - if time - .duration_since(std::time::UNIX_EPOCH) - .expect("FIXME") - .as_secs() - > 0x1FFFFFFFFFFFFF - { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Timestamp { - Sending(JsTime), - Receiving(SystemTime), -} - -// pub struct ReceiptPayload { -// pub issuer: Did, -// pub ran: Link>, -// pub out: UcanResult, // FIXME? -// pub proofs: Vec>>, -// pub metadata: BTreeMap, // FIXME serde value instead? -// pub issued_at: u64, -// } -// -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap<&str, Ipld>), -// } -// -// pub struct Capability { -// command: String, -// payload: BTreeMap, -// } +pub mod ability; +pub mod condition; +pub mod delegation; +pub mod invocation; +pub mod promise; +pub mod prove; +pub mod receipt; ////////////////////////////////////// ////////////////////////////////////// diff --git a/src/promise.rs b/src/promise.rs new file mode 100644 index 00000000..f7da8315 --- /dev/null +++ b/src/promise.rs @@ -0,0 +1,12 @@ +use libipld_core::link::Link; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +pub enum Promise +where + T: Debug + Clone + PartialEq, +{ + PromiseOk(Link), + PromiseErr(Link), + PromiseAny(Link), +} diff --git a/src/prove.rs b/src/prove.rs new file mode 100644 index 00000000..fcee2ff6 --- /dev/null +++ b/src/prove.rs @@ -0,0 +1,33 @@ +#[cfg_attr(doc, aquamarine::aquamarine)] +/// FIXME +/// +/// ```mermaid +/// flowchart LR +/// Invocation --> more --> Self --> Candidate --> more2 +/// more[...] +/// more2[...] +/// ``` +pub trait TryProve { + type Error; + type Proven; + + fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; +} + +pub trait TryProven { + type Proven1; + type Error1; + fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; +} + +impl TryProven for U +where + T: TryProve, +{ + type Proven1 = T::Proven; + type Error1 = T::Error; + + fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + candidate.try_prove(self) + } +} diff --git a/src/receipt.rs b/src/receipt.rs new file mode 100644 index 00000000..9c88aa8b --- /dev/null +++ b/src/receipt.rs @@ -0,0 +1,19 @@ +// pub struct ReceiptPayload { +// pub issuer: Did, +// pub ran: Link>, +// pub out: UcanResult, // FIXME? +// pub proofs: Vec>>, +// pub metadata: BTreeMap, // FIXME serde value instead? +// pub issued_at: u64, +// } + +// +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap<&str, Ipld>), +// } +// +// pub struct Capability { +// command: String, +// payload: BTreeMap, +// } diff --git a/src/time.rs b/src/time.rs index e57163a0..27c52998 100644 --- a/src/time.rs +++ b/src/time.rs @@ -9,3 +9,48 @@ pub fn now() -> u64 { .unwrap() .as_secs() } + +#[derive(Debug, Clone, PartialEq)] +pub enum Timestamp { + // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too + /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) + /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. + /// + /// This range can represent millions of years into the future, + /// and is thus sufficient for nearly all use cases. + Sending(JsTime), + + /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), + /// received timestamps may be parsed as regular [SystemTime] + Receiving(SystemTime), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JsTime { + time: SystemTime, +} + +// FIXME just lifting this from Elixir for now +pub struct OutOfRangeError { + pub tried: SystemTime, +} + +impl JsTime { + /// Create a [`JsTime`] from a [`SystemTime`] + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + > 0x1FFFFFFFFFFFFF + { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} From 6ec904cdbf7d143b5bd4d45bd09d59644dbc05dc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 23:21:24 -0800 Subject: [PATCH 072/234] Add Signature envelope and receipts --- src/ability/crud.rs | 2 +- src/ability/traits.rs | 3 ++- src/delegation.rs | 8 ++++++- src/invocation.rs | 8 ++++++- src/lib.rs | 1 + src/promise.rs | 49 ++++++++++++++++++++++++++++++++++++------- src/receipt.rs | 47 +++++++++++++++++++++++++---------------- src/signature.rs | 22 +++++++++++++++++++ 8 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 src/signature.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 8eac2115..d1c7775d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -8,7 +8,7 @@ where T: Debug + Clone + PartialEq, { Value(T), - Await(Promise), + Await(Promise), // FIXME } // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 630a9ce0..7406a020 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,9 +1,10 @@ use std::fmt::Debug; pub trait Ability: Sized { - // pub trait Capability: Into { // FIXME remove sized? // pub trait Capability: TryFrom + Into { type Builder: From + TryInto + PartialEq + Debug; // FIXME const COMMAND: &'static str; } + +// FIXME macro for Delegation (builder) and Promises diff --git a/src/delegation.rs b/src/delegation.rs index 7547dcd3..16862bc7 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,8 +1,10 @@ -use crate::{ability::traits::Ability, time::Timestamp}; +use crate::{ability::traits::Ability, signature, time::Timestamp}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +pub type Delegation = signature::Envelope>; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, @@ -18,3 +20,7 @@ pub struct Payload { pub expiration: Timestamp, pub not_before: Option, } + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +} diff --git a/src/invocation.rs b/src/invocation.rs index 26c50a07..bf5b228b 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,8 +1,10 @@ -use crate::{ability::traits::Ability, delegation, time::Timestamp}; +use crate::{ability::traits::Ability, delegation, signature, time::Timestamp}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +pub type Invocation = signature::Envelope>; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, @@ -39,3 +41,7 @@ impl From> for delegation::Payload } } } + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +} diff --git a/src/lib.rs b/src/lib.rs index 773867f2..79e618bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,7 @@ pub mod invocation; pub mod promise; pub mod prove; pub mod receipt; +pub mod signature; ////////////////////////////////////// ////////////////////////////////////// diff --git a/src/promise.rs b/src/promise.rs index f7da8315..37ed5bd0 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,12 +1,45 @@ -use libipld_core::link::Link; +use crate::{ability::traits::Ability, invocation, invocation::Invocation, signature::Capsule}; +use cid::Cid; +use libipld_core::{ipld::Ipld, link::Link}; use std::fmt::Debug; +// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. +// #[derive(Debug, Clone, PartialEq)] +// pub enum Promise +// where +// A: Ability, // FIXME MUST be an Invocation +// invocation::Payload: Capsule, +// { +// PromiseAny(Link>), // FIXME not sure about specifying the A here +// PromiseOk(Link>), +// PromiseErr(Link>), +// } + +/// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq)] -pub enum Promise -where - T: Debug + Clone + PartialEq, -{ - PromiseOk(Link), - PromiseErr(Link), - PromiseAny(Link), +pub enum Promise { + PromiseAny(Cid), // FIXME not sure about specifying the A here + PromiseOk(Cid), + PromiseErr(Cid), } + +// impl TryFrom for Promise +// where +// invocation::Payload: Capsule, +// { +// type Error = (); // FIXME +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(btree) = ipld { +// if let Some(Ipld::Link(link)) = btree.get("await/ok") { +// return Ok(Self::PromiseOk(link.clone().into())); +// } else if let Some(Ipld::Link(link)) = btree.get("await/err") { +// return Ok(Self::PromiseErr(link.clone().into())); +// } else if let Some(Ipld::Link(link)) = btree.get("await/*") { +// return Ok(Self::PromiseAny(link.clone().into())); +// } +// } +// +// Err(()) +// } +// } diff --git a/src/receipt.rs b/src/receipt.rs index 9c88aa8b..7ca0aab1 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,19 +1,30 @@ -// pub struct ReceiptPayload { -// pub issuer: Did, -// pub ran: Link>, -// pub out: UcanResult, // FIXME? -// pub proofs: Vec>>, -// pub metadata: BTreeMap, // FIXME serde value instead? -// pub issued_at: u64, -// } +use crate::{ + ability::traits::Ability, delegation::Delegation, invocation::Invocation, signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{ipld::Ipld, link::Link}; +use std::collections::BTreeMap; -// -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap<&str, Ipld>), -// } -// -// pub struct Capability { -// command: String, -// payload: BTreeMap, -// } +pub type Receipt = signature::Envelope>; + +pub struct Payload +where + T: Ability, +{ + pub issuer: DID, + pub ran: Link>, + pub out: UcanResult, // FIXME? + pub proofs: Vec>>, + pub metadata: BTreeMap, + pub issued_at: Option, +} + +pub enum UcanResult { + UcanOk(T), + UcanErr(BTreeMap), +} + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out versioh +} diff --git a/src/signature.rs b/src/signature.rs new file mode 100644 index 00000000..61be0815 --- /dev/null +++ b/src/signature.rs @@ -0,0 +1,22 @@ +use libipld_core::link::Link; + +pub struct Envelope +where + T: Capsule, +{ + pub sig: Box<[u8]>, + pub payload: Link, +} + +pub enum Signature { + One(Box<[u8]>), + Batch { + sig: Box<[u8]>, + merkle_proof: Box<[u32]>, + }, +} + +// TODO move to own module +pub trait Capsule { + const TAG: &'static str; +} From fb8336fcd3d51ecfcdd881621a4b394af8649b49 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 15:37:53 -0800 Subject: [PATCH 073/234] More exploration --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +- Cargo.toml | 6 +-- README.md | 24 +++++----- SECURITY.md | 6 +-- flake.nix | 4 +- package.json | 8 ++-- src/ability.rs | 4 ++ src/ability/any.rs | 5 +- src/ability/dynamic.rs | 78 ++++++++++++++++++++++++++++++++ src/ability/msg.rs | 35 +++++++++----- src/ability/traits.rs | 22 ++++++++- src/condition.rs | 14 ++---- src/delegation.rs | 83 ++++++++++++++++++++++++++++++---- src/error.rs | 2 +- src/invocation.rs | 42 +++++++++++------ src/ipld.rs | 48 ++++++++++++++++++++ src/lib.rs | 35 ++------------ src/prove.rs | 14 +++--- src/receipt.rs | 33 ++++++++------ src/signature.rs | 62 ++++++++++++++++++++++--- src/time.rs | 27 ++++++++++- 23 files changed, 425 insertions(+), 135 deletions(-) create mode 100644 src/ability/dynamic.rs create mode 100644 src/ipld.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 76052f66..e8a39b9e 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs_ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bbcf0c75..8197146b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "rs_ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c6d8dcf..c8922c18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs-ucan +# Contributing to rs_ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs-ucan look at our + - For a detailed look on how to build rs_ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index 21422fa8..ce38108a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rs-ucan" +name = "rs_ucan" version = "0.1.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs-ucan" -repository = "https://github.com/ucan-wg/rs-ucan" +documentation = "https://docs.rs/rs_ucan" +repository = "https://github.com/ucan-wg/rs_ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs-ucan Logo + + rs_ucan Logo -

rs-ucan

+

rs_ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "1.0.0-rc.1" +rs_ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index 777f0214..3d620e8e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The rs-ucan team welcomes security reports and is committed to +The `rs_ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs-ucan team announces security advisories through our +process. The rs_ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/rs_ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/flake.nix b/flake.nix index 947a2386..70e1f16c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs-ucan"; + description = "rs_ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs-ucan"; + name = "rs_ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index 26486d77..3e09a8bd 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs-ucan", + "description": "A UCAN library built from rs_ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs-ucan.git" + "url": "git+https://github.com/fission-codes/rs_ucan.git" }, "keywords": [ "authorization" @@ -12,9 +12,9 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs-ucan/issues" + "url": "https://github.com/fission-codes/rs_ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs-ucan#readme", + "homepage": "https://github.com/fission-codes/rs_ucan#readme", "module": "dist/bundler/rs_ucan.js", "types": "dist/nodejs/rs_ucan.d.ts", "exports": { diff --git a/src/ability.rs b/src/ability.rs index 0ffcc9fd..0b896ac0 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -2,3 +2,7 @@ pub mod any; pub mod crud; pub mod msg; pub mod traits; + +// TODO move to crate::wasm? +#[cfg(feature = "wasm")] +pub mod dynamic; diff --git a/src/ability/any.rs b/src/ability/any.rs index b2ba7533..ac3e4559 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,13 +1,14 @@ use crate::prove::TryProve; use void::Void; +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl TryProve for T { +impl<'a, T> TryProve<'a, DelegateAny> for T { type Error = Void; type Proven = T; - fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { Ok(self) } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs new file mode 100644 index 00000000..7f60a9e5 --- /dev/null +++ b/src/ability/dynamic.rs @@ -0,0 +1,78 @@ +//! This module is for dynamic abilities, especially for Wasm support + +use super::traits::{Ability, Command}; +use crate::prove::TryProve; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +use crate::ipld::WrappedIpld; + +use js_sys; +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Dynamic<'a> { + pub command: String, + pub args: BTreeMap, // FIXME consider this being just JsValue + pub validater: &'a js_sys::Function, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DynamicBuilder<'a> { + pub command: String, + pub args: Option>, + pub validater: &'a js_sys::Function, +} + +impl<'a> From> for DynamicBuilder<'a> { + fn from(dynamic: Dynamic<'a>) -> Self { + Self { + command: dynamic.command.clone(), + args: Some(dynamic.args), + validater: dynamic.validater, + } + } +} + +impl<'a> TryFrom> for Dynamic<'a> { + type Error = (); // FIXME + + fn try_from(builder: DynamicBuilder) -> Result { + if let Some(args) = builder.clone().args { + Ok(Self { + command: builder.command.clone(), + args, + }) + } else { + Err(()) + } + } +} + +impl<'a> Command for Dynamic<'a> { + fn command(&self) -> &'static str { + self.command + } +} + +impl<'a> Command for DynamicBuilder<'a> { + fn command(&self) -> &'static str { + self.command + } +} + +impl<'a> Ability for Dynamic<'a> { + type Builder = DynamicBuilder<'a>; +} + +impl<'a> TryProve> for DynamicBuilder<'a> { + type Error = JsError; + type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that + + fn try_prove(&'a self, candidate: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { + let js_self: JsValue = self.into().into(); + let js_candidate: JsValue = candidate.into().into(); + + self.validater.apply(js_self, js_candidate); + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index f2167dd7..b9e36086 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,4 +1,7 @@ -use crate::{ability::traits::Ability, prove::TryProve}; +use crate::{ + ability::traits::{Ability, Builder}, + prove::TryProve, +}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, str::FromStr}; use url::Url; @@ -16,6 +19,7 @@ pub struct MsgSend { message: String, } +// TODO is the to or from often also the subject? Shoudl that be accounted for? #[derive(Debug, Clone, PartialEq, Eq)] pub struct MsgReceive { to: Url, @@ -89,9 +93,16 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Ability for MsgReceive { - type Builder = MsgReceiveBuilder; - const COMMAND: &'static str = "msg/receive"; +impl Builder for MsgReceiveBuilder { + type Concrete = MsgReceive; + + fn command(&self) -> &'static str { + "msg/receive" + } + + fn try_build(&self) -> Result { + self.try_build().map_err(|_| ()) // FIXME + } } impl TryFrom<&Ipld> for MsgReceive { @@ -127,11 +138,11 @@ impl TryFrom<&Ipld> for MsgReceive { } } -impl TryProve for Msg { +impl<'a> TryProve<'a, Msg> for Msg { type Error = (); // FIXME type Proven = Msg; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self == candidate { Ok(self) } else { @@ -140,11 +151,11 @@ impl TryProve for Msg { } } -impl TryProve for MsgSend { +impl<'a> TryProve<'a, Msg> for MsgSend { type Error = (); // FIXME type Proven = MsgSend; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -153,11 +164,11 @@ impl TryProve for MsgSend { } } -impl TryProve for MsgReceive { +impl<'a> TryProve<'a, Msg> for MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -167,11 +178,11 @@ impl TryProve for MsgReceive { } // FIXME this needs to work on builders! -impl TryProve for MsgReceive { +impl<'a> TryProve<'a, MsgReceive> for MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; - fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { if self == candidate { Ok(self) } else { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 7406a020..c7e8f662 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,10 +1,30 @@ use std::fmt::Debug; +// FIXME this is always a builder, right? pub trait Ability: Sized { // FIXME remove sized? // pub trait Capability: TryFrom + Into { type Builder: From + TryInto + PartialEq + Debug; // FIXME - const COMMAND: &'static str; + + // fn command(builder: &Self::Builder) -> &'static str; } +pub trait Builder { + type Concrete; + + fn command(&self) -> &'static str; + fn try_build(&self) -> Result; // FIXME +} + +// pub trait Builds1 { +// type B; +// } +// +// impl Builds1 for B::Concrete +// where +// B: Builder, +// { +// type B = B; +// } + // FIXME macro for Delegation (builder) and Promises diff --git a/src/condition.rs b/src/condition.rs index cd627be1..e4d27ec3 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -1,15 +1,11 @@ use libipld_core::ipld::Ipld; -pub struct FieldName { - name: Box, -} - pub enum Condition { - Contains { field: FieldName, value: Vec }, - MinLength { field: FieldName, value: u64 }, - MaxLength { field: FieldName, value: u64 }, - Equals { field: FieldName, value: Ipld }, - Regex { field: FieldName }, // FIXME + Contains { field: String, value: Vec }, + MinLength { field: String, value: u64 }, + MaxLength { field: String, value: u64 }, + Equals { field: String, value: Ipld }, + Regex { field: String }, // FIXME // Silly example OnDayOfWeek { day: Day }, diff --git a/src/delegation.rs b/src/delegation.rs index 16862bc7..9bfb625e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,26 +1,93 @@ -use crate::{ability::traits::Ability, signature, time::Timestamp}; +use crate::{ + ability::{ + any::DelegateAny, + traits::{Ability, Builder}, + }, + signature, + time::Timestamp, +}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: DID, - pub capability_builder: A::Builder, // FIXME - pub conditions: Box<[Cond]>, // Worth it over a Vec? + pub capability_builder: Delegate, // FIXME + pub conditions: Vec, // Worth it over a Vec? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? + pub metadata: BTreeMap, // FIXME serde value instead? + pub nonce: Vec, // Better type? pub expiration: Timestamp, pub not_before: Option, } -impl signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } + +#[derive(Debug, Clone, PartialEq)] +pub enum Delegate { + Any, + Specific(T), +} + +impl From<&Payload> for Ipld +where + Ipld: From + From, +{ + fn from(payload: &Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.capability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().clone().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match &payload.capability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => builder.clone().into(), + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.clone().into()); + map.insert("exp".into(), payload.expiration.clone().into()); + + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); + } + + map.into() + } +} diff --git a/src/error.rs b/src/error.rs index 98d665b4..2fb4da11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] + #[error("An unexpected error occurred in rs_ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs_ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/invocation.rs b/src/invocation.rs index bf5b228b..b5213924 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,30 +1,43 @@ -use crate::{ability::traits::Ability, delegation, signature, time::Timestamp}; +use crate::{ + ability::traits::{Ability, Builder}, + delegation, + delegation::{Delegate, Delegation}, + receipt::Receipt, + signature, + time::Timestamp, +}; use did_url::DID; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, link::Link}; use std::collections::BTreeMap; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; +// FIXME figure out how to get rid of (AKA imply) that B param #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload +where + B: Builder, +{ pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: Ability, // FIXME check name in spec + pub ability: T, // TODO check name in spec - // pub proofs: Vec>>, - // pub cause: Option>>, // FIXME? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? + pub proofs: Vec>>, + pub cause: Option>>, + pub metadata: BTreeMap, // FIXME serde value instead? + pub nonce: Vec, // Better type? pub expiration: Timestamp, pub not_before: Option, } // FIXME move that clone? -impl From> for delegation::Payload { - fn from(invocation: Payload) -> Self { +impl + From, C> From<&Payload> + for delegation::Payload +{ + fn from(invocation: &Payload) -> Self { Self { issuer: invocation.issuer.clone(), subject: invocation.subject.clone(), @@ -32,16 +45,17 @@ impl From> for delegation::Payload .audience .clone() .unwrap_or(invocation.issuer.clone()), - capability_builder: >::into(invocation.ability.clone()), - conditions: Box::new([]), + capability_builder: Delegate::Specific(invocation.ability.clone().into()), + conditions: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), not_before: invocation.not_before.clone(), + // FIXME cause } } } -impl signature::Capsule for Payload { +impl, C> signature::Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } diff --git a/src/ipld.rs b/src/ipld.rs new file mode 100644 index 00000000..64a8ea5a --- /dev/null +++ b/src/ipld.rs @@ -0,0 +1,48 @@ +use libipld_core::ipld::Ipld; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; + +pub struct WrappedIpld(pub Ipld); + +impl From for WrappedIpld { + fn from(ipld: Ipld) -> Self { + Self(ipld) + } +} + +impl From for Ipld { + fn from(wrapped: WrappedIpld) -> Self { + wrapped.0 + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(wrapped: WrappedIpld) -> Self { + match wrapped.0 { + Ipld::Null => JsValue::Null, + Ipld::Bool(b) => JsValue::from(b), + Ipld::Integer(i) => JsValue::from(i), + Ipld::Float(f) => JsValue::from_f64(f), + Ipld::String(s) => JsValue::from_str(&s), + Ipld::Bytes(b) => JsValue::from(b), + Ipld::List(l) => { + let mut vec = Vec::new(); + for ipld in l { + vec.push(JsValue::from(ipld)); + } + JsValue::from(vec) + } + Ipld::Map(m) => { + let mut map = JsValue::new(); + for (k, v) in m { + map.set(&k, JsValue::from(v)); + } + map + } + Ipld::Link(l) => JsValue::from(Link::from(l)), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 79e618bb..f0733a82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs-ucan +//! rs_ucan use std::str::FromStr; @@ -38,6 +38,8 @@ pub type Did = String; use std::fmt::Debug; +// FIXME concrete abilitiy types in addition to promised version + // impl DelegationPayload { // fn check( // &self, @@ -113,38 +115,11 @@ pub mod ability; pub mod condition; pub mod delegation; pub mod invocation; +pub mod ipld; pub mod promise; pub mod prove; pub mod receipt; -pub mod signature; - -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// +pub mod signature; // FIXME /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/prove.rs b/src/prove.rs index fcee2ff6..c132b41c 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -7,27 +7,27 @@ /// more[...] /// more2[...] /// ``` -pub trait TryProve { +pub trait TryProve<'a, T> { type Error; type Proven; - fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; + fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; } -pub trait TryProven { +pub trait TryProven<'a, A> { type Proven1; type Error1; - fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; + fn try_proven(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; } -impl TryProven for U +impl<'a, T, U> TryProven<'a, T> for U where - T: TryProve, + T: TryProve<'a, U>, { type Proven1 = T::Proven; type Error1 = T::Error; - fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + fn try_proven(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { candidate.try_prove(self) } } diff --git a/src/receipt.rs b/src/receipt.rs index 7ca0aab1..71b053f2 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,30 +1,33 @@ use crate::{ - ability::traits::Ability, delegation::Delegation, invocation::Invocation, signature, + ability::traits::{Ability, Builder}, + delegation::Delegation, + invocation::Invocation, + signature, time::Timestamp, }; use did_url::DID; use libipld_core::{ipld::Ipld, link::Link}; use std::collections::BTreeMap; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; -pub struct Payload -where - T: Ability, -{ +#[derive(Debug, Clone, PartialEq)] +pub struct Payload, C> { pub issuer: DID, - pub ran: Link>, - pub out: UcanResult, // FIXME? - pub proofs: Vec>>, + pub ran: Link>, + pub out: Result>, + + // pub proofs: Vec>>, // FIXME these can only be executiojn proofs, right? pub metadata: BTreeMap, pub issued_at: Option, } -pub enum UcanResult { - UcanOk(T), - UcanErr(BTreeMap), -} +// #[derive(Debug, Clone, PartialEq)] +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap), +// } -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out versioh +impl, C> signature::Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } diff --git a/src/signature.rs b/src/signature.rs index 61be0815..57af2826 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,22 +1,70 @@ -use libipld_core::link::Link; +use libipld_core::{ipld::Ipld, link::Link}; +use std::collections::BTreeMap; +#[derive(Debug, Clone, PartialEq)] pub struct Envelope where T: Capsule, { - pub sig: Box<[u8]>, - pub payload: Link, + pub sig: Signature, + pub payload: T, } +// FIXME consider kicking Batch down the road for spec reasons? +#[derive(Debug, Clone, PartialEq)] pub enum Signature { - One(Box<[u8]>), + One(Vec), Batch { - sig: Box<[u8]>, - merkle_proof: Box<[u32]>, + sig: Vec, + merkle_proof: Vec>, }, } -// TODO move to own module +// TODO move to own module? pub trait Capsule { const TAG: &'static str; } + +impl From<&Signature> for Ipld { + fn from(sig: &Signature) -> Self { + match sig { + Signature::One(sig) => sig.clone().into(), + Signature::Batch { sig, merkle_proof } => { + let mut map = BTreeMap::new(); + let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); + map.insert("sig".into(), sig.clone().into()); + map.insert("prf".into(), proof.into()); + map.into() + } + } + } +} + +impl From for Ipld { + fn from(sig: Signature) -> Self { + match sig { + Signature::One(sig) => sig.into(), + Signature::Batch { sig, merkle_proof } => { + let mut map = BTreeMap::new(); + let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); + map.insert("sig".into(), sig.into()); + map.insert("prf".into(), proof.into()); + map.into() + } + } + } +} + +// FIXME Store or BTreeMap? Also eliminate that Clone constraint +impl + Clone> From<&Envelope> for Ipld { + fn from(Envelope { sig, payload }: &Envelope) -> Self { + let mut inner = BTreeMap::new(); + inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + + let mut map = BTreeMap::new(); + map.insert("sig".into(), sig.into()); + map.insert("pld".into(), Ipld::Map(inner)); + + Ipld::Map(map) + } +} diff --git a/src/time.rs b/src/time.rs index 27c52998..b55ce9f9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,7 @@ //! Time utilities -use web_time::SystemTime; +use libipld_core::ipld::Ipld; +use web_time::{SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { @@ -25,6 +26,19 @@ pub enum Timestamp { Receiving(SystemTime), } +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::Sending(js_time) => js_time.into(), + Timestamp::Receiving(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct JsTime { time: SystemTime, @@ -54,3 +68,14 @@ impl JsTime { } } } + +impl From for Ipld { + fn from(js_time: JsTime) -> Self { + js_time + .time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into() + } +} From aaa2a7906836b6d4abb3f714466416f33b7dc032 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 17:29:26 -0800 Subject: [PATCH 074/234] WIP --- src/ability.rs | 1 + src/ability/traits.rs | 5 +++-- src/ability/wasm.rs | 14 ++++++++++++++ src/delegation.rs | 12 ++++++------ src/invocation.rs | 3 +-- 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 src/ability/wasm.rs diff --git a/src/ability.rs b/src/ability.rs index 0b896ac0..eda773d7 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -2,6 +2,7 @@ pub mod any; pub mod crud; pub mod msg; pub mod traits; +pub mod wasm; // TODO move to crate::wasm? #[cfg(feature = "wasm")] diff --git a/src/ability/traits.rs b/src/ability/traits.rs index c7e8f662..091f5424 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,10 +1,11 @@ use std::fmt::Debug; // FIXME this is always a builder, right? -pub trait Ability: Sized { +pub trait Ability { + // FIXME no Sizd // FIXME remove sized? // pub trait Capability: TryFrom + Into { - type Builder: From + TryInto + PartialEq + Debug; // FIXME + type Builder; // FIXME // fn command(builder: &Self::Builder) -> &'static str; } diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs new file mode 100644 index 00000000..4ccd51b5 --- /dev/null +++ b/src/ability/wasm.rs @@ -0,0 +1,14 @@ +use libipld_core::{ipld::Ipld, link::Link}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Run { + pub module: Module, + pub function: String, + pub args: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Module { + Inline(Vec), + Cid(Link>), +} diff --git a/src/delegation.rs b/src/delegation.rs index 9bfb625e..2673e53f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -18,11 +18,11 @@ pub struct Payload { pub subject: DID, pub audience: DID, - pub capability_builder: Delegate, // FIXME - pub conditions: Vec, // Worth it over a Vec? + pub ability_builder: Delegate, // FIXME + pub conditions: Vec, - pub metadata: BTreeMap, // FIXME serde value instead? - pub nonce: Vec, // Better type? + pub metadata: BTreeMap, + pub nonce: Vec, // FIXME Better type? pub expiration: Timestamp, pub not_before: Option, @@ -48,7 +48,7 @@ where map.insert("sub".into(), payload.subject.to_string().into()); map.insert("aud".into(), payload.audience.to_string().into()); - let can = match &payload.capability_builder { + let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), Delegate::Specific(builder) => builder.command().clone().into(), }; @@ -57,7 +57,7 @@ where map.insert( "args".into(), - match &payload.capability_builder { + match &payload.ability_builder { Delegate::Any => Ipld::Map(BTreeMap::new()), Delegate::Specific(builder) => builder.clone().into(), }, diff --git a/src/invocation.rs b/src/invocation.rs index b5213924..1940d9c2 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -45,13 +45,12 @@ impl + From, C> From<&Payload> .audience .clone() .unwrap_or(invocation.issuer.clone()), - capability_builder: Delegate::Specific(invocation.ability.clone().into()), + ability_builder: Delegate::Specific(invocation.ability.clone().into()), conditions: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), not_before: invocation.not_before.clone(), - // FIXME cause } } } From ad94b97a28eee112e6a1a6e3aa2e16231edb409a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 23:56:40 -0800 Subject: [PATCH 075/234] More exploratory programming, cleanup, and conds --- Cargo.toml | 2 +- src/ability/any.rs | 6 +- src/ability/msg.rs | 13 +- src/ability/traits.rs | 47 +++--- src/condition.rs | 22 --- src/condition/common.rs | 1 + src/delegation.rs | 106 ++------------ src/delegation/condition.rs | 7 + src/delegation/condition/common.rs | 220 +++++++++++++++++++++++++++++ src/delegation/delegate.rs | 39 +++++ src/delegation/payload.rs | 137 ++++++++++++++++++ src/invocation.rs | 95 ++++++++++--- src/lib.rs | 1 - src/promise.rs | 2 +- src/prove.rs | 18 --- src/receipt.rs | 64 ++++++--- src/signature.rs | 13 ++ 17 files changed, 587 insertions(+), 206 deletions(-) delete mode 100644 src/condition.rs create mode 100644 src/condition/common.rs create mode 100644 src/delegation/condition.rs create mode 100644 src/delegation/condition/common.rs create mode 100644 src/delegation/delegate.rs create mode 100644 src/delegation/payload.rs diff --git a/Cargo.toml b/Cargo.toml index ce38108a..289b4adc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-feat p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } proptest = { version = "1.1", optional = true } +regex = { version = "1.10" } rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } @@ -60,7 +61,6 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = "2.5" -void = "1.0" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/ability/any.rs b/src/ability/any.rs index ac3e4559..bac76d01 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,14 +1,14 @@ use crate::prove::TryProve; -use void::Void; +use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; impl<'a, T> TryProve<'a, DelegateAny> for T { - type Error = Void; + type Error = Infallible; type Proven = T; - fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Infallible> { Ok(self) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index b9e36086..005cca90 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,7 +1,4 @@ -use crate::{ - ability::traits::{Ability, Builder}, - prove::TryProve, -}; +use crate::{ability::traits::Command, prove::TryProve}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, str::FromStr}; use url::Url; @@ -93,16 +90,10 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Builder for MsgReceiveBuilder { - type Concrete = MsgReceive; - +impl Command for MsgReceiveBuilder { fn command(&self) -> &'static str { "msg/receive" } - - fn try_build(&self) -> Result { - self.try_build().map_err(|_| ()) // FIXME - } } impl TryFrom<&Ipld> for MsgReceive { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 091f5424..ff01dc09 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,31 +1,34 @@ use std::fmt::Debug; -// FIXME this is always a builder, right? -pub trait Ability { - // FIXME no Sizd - // FIXME remove sized? - // pub trait Capability: TryFrom + Into { - type Builder; // FIXME - - // fn command(builder: &Self::Builder) -> &'static str; +pub trait Command { + fn command(&self) -> &'static str; } -pub trait Builder { - type Concrete; +// FIXME this is always a builder, right? ToBuilder? Builder? Buildable? +// FIXME Delegable and make it proven? +pub trait Buildable { + type Builder: Command + Debug; // FIXME - fn command(&self) -> &'static str; - fn try_build(&self) -> Result; // FIXME + fn to_builder(&self) -> Self::Builder; + fn try_build(builder: Self::Builder) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +} + +pub trait Resolvable: Buildable { + type Awaiting: Command + Debug; // FIXME + + fn to_awaitable(&self) -> Self::Awaiting; + fn try_into_resolved(promise: Self::Awaiting) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +} + +impl Command for T { + fn command(&self) -> &'static str { + self.to_builder().command() + } } -// pub trait Builds1 { -// type B; -// } -// -// impl Builds1 for B::Concrete -// where -// B: Builder, -// { -// type B = B; -// } +pub trait Runnable { + type Output; +} +pub trait Ability: Buildable + Runnable {} // FIXME macro for Delegation (builder) and Promises diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index e4d27ec3..00000000 --- a/src/condition.rs +++ /dev/null @@ -1,22 +0,0 @@ -use libipld_core::ipld::Ipld; - -pub enum Condition { - Contains { field: String, value: Vec }, - MinLength { field: String, value: u64 }, - MaxLength { field: String, value: u64 }, - Equals { field: String, value: Ipld }, - Regex { field: String }, // FIXME - - // Silly example - OnDayOfWeek { day: Day }, -} - -pub enum Day { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -} diff --git a/src/condition/common.rs b/src/condition/common.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/condition/common.rs @@ -0,0 +1 @@ + diff --git a/src/delegation.rs b/src/delegation.rs index 2673e53f..5c96d411 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,93 +1,15 @@ -use crate::{ - ability::{ - any::DelegateAny, - traits::{Ability, Builder}, - }, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - +pub mod condition; +pub mod delegate; +pub mod payload; + +use crate::signature; +pub use delegate::Delegate; +pub use payload::Payload; + +/// A [`Delegation`] is a signed delegation [`Payload`] +/// +/// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. +/// +/// # Examples +/// FIXME pub type Delegation = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: DID, - - pub ability_builder: Delegate, // FIXME - pub conditions: Vec, - - pub metadata: BTreeMap, - pub nonce: Vec, // FIXME Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0.0-rc.1"; -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Delegate { - Any, - Specific(T), -} - -impl From<&Payload> for Ipld -where - Ipld: From + From, -{ - fn from(payload: &Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().clone().into(), - }; - - map.insert("can".into(), can); - - map.insert( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => builder.clone().into(), - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.clone().into()); - map.insert("exp".into(), payload.expiration.clone().into()); - - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); - } - - map.into() - } -} diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs new file mode 100644 index 00000000..b03b07b6 --- /dev/null +++ b/src/delegation/condition.rs @@ -0,0 +1,7 @@ +use libipld_core::ipld::Ipld; + +pub mod common; + +pub trait Condition { + fn validate(&self, ipld: &Ipld) -> bool; +} diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs new file mode 100644 index 00000000..bd568c0b --- /dev/null +++ b/src/delegation/condition/common.rs @@ -0,0 +1,220 @@ +use super::Condition; +use libipld_core::ipld::Ipld; +use regex::Regex; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum Common { + ContainsAll(ContainsAll), + ContainsAny(ContainsAny), + ExcludesAll(ExcludesAll), + MaxLength(MaxLength), + MinNumber(MinNumber), + MaxNumber(MaxNumber), + Matches(Matches), +} + +// FIXME dynamic js version? + +#[derive(Debug, Clone, PartialEq)] +pub struct ContainsAll { + field: String, + values: Vec, +} + +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + let mut map = BTreeMap::new(); + map.insert("field".into(), contains_all.field.into()); + map.insert("values".into(), contains_all.values.into()); + map.into() + } +} + +impl TryFrom<&Ipld> for ContainsAll { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + if let Ipld::Map(map) = ipld { + if map.len() != 2 { + return Err(()); + } + + if let Some(Ipld::String(field)) = map.get("field") { + let values = match map.get("values") { + None => Ok(vec![]), + Some(Ipld::List(values)) => Ok(values.to_vec()), + _ => Err(()), + }?; + + Ok(Self { + field: field.to_string(), + values: values.to_vec(), + }) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +impl Condition for ContainsAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.values.iter().all(|ipld| array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.values.iter().all(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ContainsAny { + field: String, + value: Vec, +} + +impl Condition for ContainsAny { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => array.iter().any(|ipld| self.value.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.value.iter().any(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ExcludesAll { + field: String, + value: Vec, +} + +impl Condition for ExcludesAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.value.iter().all(|ipld| !array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.value.iter().all(|ipld| !vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Numeric { + Float(f64), + Integer(i128), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MinNumber { + field: String, + value: Numeric, +} + +impl Condition for MinNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.value { + Numeric::Float(float) => *integer as f64 >= float, + Numeric::Integer(integer) => integer >= integer, + }, + Ipld::Float(float) => match self.value { + Numeric::Float(float) => float >= float, + Numeric::Integer(integer) => *float >= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MaxNumber { + field: String, + value: Numeric, +} + +impl Condition for MaxNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.value { + Numeric::Float(float) => *integer as f64 <= float, + Numeric::Integer(integer) => integer <= integer, + }, + Ipld::Float(float) => match self.value { + Numeric::Float(float) => float <= float, + Numeric::Integer(integer) => *float <= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MinLength { + field: String, + value: u64, +} + +impl Condition for MinLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() >= self.value as usize, + Ipld::List(list) => list.len() >= self.value as usize, + Ipld::Map(btree) => btree.len() >= self.value as usize, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MaxLength { + field: String, + value: u64, +} + +impl Condition for MaxLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() <= self.value as usize, + Ipld::List(list) => list.len() <= self.value as usize, + Ipld::Map(btree) => btree.len() <= self.value as usize, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Matches { + field: String, + matcher: Matcher, +} + +#[derive(Debug, Clone)] +pub struct Matcher(Regex); + +impl PartialEq for Matcher { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Condition for Matcher { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => self.0.is_match(string), + _ => false, + } + } +} diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs new file mode 100644 index 00000000..1b645f58 --- /dev/null +++ b/src/delegation/delegate.rs @@ -0,0 +1,39 @@ +use libipld_core::ipld::Ipld; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +pub enum Delegate { + Any, + Specific(T), +} + +impl<'a, T> From<&'a Delegate> for Ipld +where + Ipld: From<&'a T>, +{ + fn from(delegate: &'a Delegate) -> Self { + match delegate { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(command) => command.into(), + } + } +} + +impl<'a, T: TryFrom<&'a Ipld>> TryFrom<&'a Ipld> for Delegate { + type Error = (); // FIXME + + fn try_from(ipld: &'a Ipld) -> Result { + if let ipld_string @ Ipld::String(st) = ipld { + if st == "ucan/*" { + Ok(Self::Any) + } else { + match T::try_from(ipld_string) { + Err(_) => Err(()), + Ok(cmd) => Ok(Self::Specific(cmd)), + } + } + } else { + Err(()) + } + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs new file mode 100644 index 00000000..c78f4bca --- /dev/null +++ b/src/delegation/payload.rs @@ -0,0 +1,137 @@ +use super::{condition::Condition, delegate::Delegate}; +use crate::{ + ability::traits::{Buildable, Command}, + signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: DID, + + pub ability_builder: Delegate, + pub conditions: Vec, + // pub fixme: Vec>, + pub metadata: BTreeMap, + pub nonce: Vec, + + pub expiration: Timestamp, + pub not_before: Option, +} + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +} + +impl + Clone> From<&Payload> for Ipld +where + Ipld: From, + B::Builder: Clone, // FIXME +{ + fn from(payload: &Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.ability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match &payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => (*builder).clone().into(), // FIXME + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.clone().into()); + map.insert("exp".into(), payload.expiration.clone().into()); + + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); + } + + map.into() + } +} + +impl + Clone> From> for Ipld +where + Ipld: From, +{ + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.ability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => builder.into(), + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .into_iter() + .map(|condition| condition.into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.into()); + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} diff --git a/src/invocation.rs b/src/invocation.rs index 1940d9c2..0d94da63 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,31 +1,28 @@ use crate::{ - ability::traits::{Ability, Builder}, + ability::traits::{Buildable, Command}, delegation, - delegation::{Delegate, Delegation}, + delegation::{condition::Condition, Delegate, Delegation}, receipt::Receipt, signature, time::Timestamp, }; use did_url::DID; -use libipld_core::{ipld::Ipld, link::Link}; -use std::collections::BTreeMap; +use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; -// FIXME figure out how to get rid of (AKA imply) that B param #[derive(Debug, Clone, PartialEq)] -pub struct Payload -where - B: Builder, -{ +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: T, // TODO check name in spec + pub ability: B, - pub proofs: Vec>>, - pub cause: Option>>, + // pub proofs: Vec>>, // FIXME just use Cid? + pub proofs: Vec, // FIXME just use Cid? + pub cause: Option, pub metadata: BTreeMap, // FIXME serde value instead? pub nonce: Vec, // Better type? @@ -34,10 +31,8 @@ where } // FIXME move that clone? -impl + From, C> From<&Payload> - for delegation::Payload -{ - fn from(invocation: &Payload) -> Self { +impl From<&Payload> for delegation::Payload { + fn from(invocation: &Payload) -> Self { Self { issuer: invocation.issuer.clone(), subject: invocation.subject.clone(), @@ -45,8 +40,9 @@ impl + From, C> From<&Payload> .audience .clone() .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().into()), + ability_builder: Delegate::Specific(invocation.ability.clone().to_builder()), conditions: vec![], + // fixme: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), @@ -55,6 +51,67 @@ impl + From, C> From<&Payload> } } -impl, C> signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } + +impl> From> for Ipld { + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert( + "aud".into(), + payload + .audience + .map(|audience| audience.to_string()) + .unwrap_or(payload.issuer.to_string()) + .into(), + ); + + map.insert("can".into(), payload.ability.command().into()); + map.insert("args".into(), payload.ability.into()); + + map.insert( + "proofs".into(), + Ipld::List( + payload + .proofs + .into_iter() + .map(|cid| cid.into()) + // .map(|link| link.cid().into()) + .collect::>(), + ), + ); + + map.insert( + "cause".into(), + payload + .cause + // .map(|link| link.cid().into()) + .map(|cid| cid.into()) + .unwrap_or(Ipld::Null), + ); + + map.insert( + "metadata".into(), + Ipld::Map( + payload + .metadata + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + ); + + map.insert("nonce".into(), payload.nonce.into()); + + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} diff --git a/src/lib.rs b/src/lib.rs index f0733a82..3d8f83d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,6 @@ use std::fmt::Debug; // pub mod ability; -pub mod condition; pub mod delegation; pub mod invocation; pub mod ipld; diff --git a/src/promise.rs b/src/promise.rs index 37ed5bd0..5bf38a28 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,4 +1,4 @@ -use crate::{ability::traits::Ability, invocation, invocation::Invocation, signature::Capsule}; +use crate::{ability::traits::Buildable, invocation, invocation::Invocation, signature::Capsule}; use cid::Cid; use libipld_core::{ipld::Ipld, link::Link}; use std::fmt::Debug; diff --git a/src/prove.rs b/src/prove.rs index c132b41c..e6962fbb 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -13,21 +13,3 @@ pub trait TryProve<'a, T> { fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; } - -pub trait TryProven<'a, A> { - type Proven1; - type Error1; - fn try_proven(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; -} - -impl<'a, T, U> TryProven<'a, T> for U -where - T: TryProve<'a, U>, -{ - type Proven1 = T::Proven; - type Error1 = T::Error; - - fn try_proven(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { - candidate.try_prove(self) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index 71b053f2..f8df1435 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,33 +1,65 @@ use crate::{ - ability::traits::{Ability, Builder}, - delegation::Delegation, + ability::traits::{Buildable, Command, Runnable}, + delegation::{condition::Condition, Delegation}, invocation::Invocation, signature, time::Timestamp, }; use did_url::DID; -use libipld_core::{ipld::Ipld, link::Link}; -use std::collections::BTreeMap; +use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; #[derive(Debug, Clone, PartialEq)] -pub struct Payload, C> { +pub struct Payload { pub issuer: DID, - pub ran: Link>, - pub out: Result>, + pub ran: Cid, + pub out: Result>, - // pub proofs: Vec>>, // FIXME these can only be executiojn proofs, right? + pub proofs: Vec, pub metadata: BTreeMap, pub issued_at: Option, } -// #[derive(Debug, Clone, PartialEq)] -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap), -// } - -impl, C> signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } + +// FIXME +#[derive(Debug, Clone, PartialEq)] +pub struct ProxyExecute { + pub command: String, + pub args: BTreeMap, +} + +impl Buildable for ProxyExecute { + type Builder = ProxyExecuteBuilder; + + fn to_builder(&self) -> Self::Builder { + ProxyExecuteBuilder { + command: Some(self.command.clone()), + args: self.args.clone(), + } + } + + fn try_build(ProxyExecuteBuilder { command, args }: Self::Builder) -> Result, ()> { + match command { + None => Err(()), + Some(command) => Ok(Box::new(Self { command, args })), + } + } +} + +// FIXME hmmm +#[derive(Debug, Clone, PartialEq)] +pub struct ProxyExecuteBuilder { + pub command: Option, + pub args: BTreeMap, +} + +impl Command for ProxyExecuteBuilder { + fn command(&self) -> &'static str { + "ucan/proxy" // FIXME check spec + } +} diff --git a/src/signature.rs b/src/signature.rs index 57af2826..1966742f 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -68,3 +68,16 @@ impl + Clone> From<&Envelope> for Ipld { Ipld::Map(map) } } + +impl + Clone> From> for Ipld { + fn from(Envelope { sig, payload }: Envelope) -> Self { + let mut inner = BTreeMap::new(); + inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + + let mut map = BTreeMap::new(); + map.insert("sig".into(), sig.into()); + map.insert("pld".into(), Ipld::Map(inner)); + + Ipld::Map(map) + } +} From e72b6d8680da4493499ea7525c146f6e73ebcbbd Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 00:54:31 -0800 Subject: [PATCH 076/234] WIP --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +- Cargo.toml | 13 ++-- README.md | 24 +++--- SECURITY.md | 6 +- benches/a_benchmark.rs | 4 +- flake.nix | 4 +- package.json | 48 ++++++------ src/ability/crud.rs | 1 + src/ability/dynamic.rs | 8 +- src/ability/msg.rs | 6 +- src/ability/traits.rs | 65 +++++++++++----- src/capsule.rs | 3 + src/delegation/payload.rs | 27 ++++--- src/error.rs | 2 +- src/invocation.rs | 119 +--------------------------- src/invocation/payload.rs | 137 +++++++++++++++++++++++++++++++++ src/lib.rs | 6 +- src/nonce.rs | 135 ++++++++++++++++++++++++++++++++ src/promise.rs | 60 +++++++-------- src/receipt.rs | 66 +++++++--------- src/receipt/payload.rs | 29 +++++++ src/signature.rs | 13 +--- src/test_utils/rvg.rs | 4 +- src/workerd.js | 6 +- tests/conformance.rs | 6 +- tests/rs_ucan.test.js | 2 +- 28 files changed, 503 insertions(+), 299 deletions(-) create mode 100644 src/capsule.rs create mode 100644 src/invocation/payload.rs create mode 100644 src/nonce.rs create mode 100644 src/receipt/payload.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index e8a39b9e..76052f66 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs_ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8197146b..bbcf0c75 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs_ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8922c18..2c6d8dcf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs_ucan +# Contributing to rs-ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs_ucan look at our + - For a detailed look on how to build rs-ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index 289b4adc..d9da4d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "rs_ucan" -version = "0.1.0" +name = "rs-ucan" +version = "0.2.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] categories = [] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs_ucan" -repository = "https://github.com/ucan-wg/rs_ucan" +documentation = "https://docs.rs/rs-ucan" +repository = "https://github.com/ucan-wg/rs-ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs_ucan Logo + + rs-ucan Logo -

rs_ucan

+

rs-ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs_ucan = "1.0.0-rc.1" +rs-ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index 3d620e8e..c74cc826 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The `rs_ucan` team welcomes security reports and is committed to +The `rs-ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs_ucan team announces security advisories through our +process. The rs-ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs_ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs index 6169ded5..76f63eb8 100644 --- a/benches/a_benchmark.rs +++ b/benches/a_benchmark.rs @@ -1,13 +1,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; pub fn add_benchmark(c: &mut Criterion) { - let mut rvg = rs_ucan::test_utils::Rvg::deterministic(); + let mut rvg = rs-ucan::test_utils::Rvg::deterministic(); let int_val_1 = rvg.sample(&(0..100i32)); let int_val_2 = rvg.sample(&(0..100i32)); c.bench_function("add", |b| { b.iter(|| { - rs_ucan::add(int_val_1, int_val_2); + rs-ucan::add(int_val_1, int_val_2); }) }); } diff --git a/flake.nix b/flake.nix index 70e1f16c..947a2386 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs_ucan"; + description = "rs-ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs_ucan"; + name = "rs-ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index 3e09a8bd..b657427b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs_ucan", + "description": "A UCAN library built from rs-ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs_ucan.git" + "url": "git+https://github.com/fission-codes/rs-ucan.git" }, "keywords": [ "authorization" @@ -12,30 +12,30 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs_ucan/issues" + "url": "https://github.com/fission-codes/rs-ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs_ucan#readme", - "module": "dist/bundler/rs_ucan.js", - "types": "dist/nodejs/rs_ucan.d.ts", + "homepage": "https://github.com/fission-codes/rs-ucan#readme", + "module": "dist/bundler/rs-ucan.js", + "types": "dist/nodejs/rs-ucan.d.ts", "exports": { ".": { "workerd": "./dist/web/workerd.js", - "browser": "./dist/bundler/rs_ucan.js", - "node": "./dist/nodejs/rs_ucan.cjs", - "default": "./dist/bundler/rs_ucan.js", - "types": "./dist/nodejs/rs_ucan.d.ts" + "browser": "./dist/bundler/rs-ucan.js", + "node": "./dist/nodejs/rs-ucan.cjs", + "default": "./dist/bundler/rs-ucan.js", + "types": "./dist/nodejs/rs-ucan.d.ts" }, "./nodejs": { - "default": "./dist/nodejs/rs_ucan.cjs", - "types": "./dist/nodejs/rs_ucan.d.ts" + "default": "./dist/nodejs/rs-ucan.cjs", + "types": "./dist/nodejs/rs-ucan.d.ts" }, "./web": { - "default": "./dist/web/rs_ucan.js", - "types": "./dist/web/rs_ucan.d.ts" + "default": "./dist/web/rs-ucan.js", + "types": "./dist/web/rs-ucan.d.ts" }, "./workerd": { "default": "./dist/web/workerd.js", - "types": "./dist/web/rs_ucan.d.ts" + "types": "./dist/web/rs-ucan.d.ts" } }, "files": [ @@ -59,7 +59,7 @@ } }, "opt": { - "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -70,7 +70,7 @@ ] }, "bindgen:bundler": { - "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -84,7 +84,7 @@ ] }, "bindgen:nodejs": { - "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && move-file dist/nodejs/rs_ucan.js dist/nodejs/rs_ucan.cjs", + "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && move-file dist/nodejs/rs-ucan.js dist/nodejs/rs-ucan.cjs", "env": { "TARGET_DIR": { "external": true @@ -98,7 +98,7 @@ ] }, "bindgen:web": { - "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && cpy --flat src/workerd.js dist/web", + "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && cpy --flat src/workerd.js dist/web", "env": { "TARGET_DIR": { "external": true @@ -112,7 +112,7 @@ ] }, "bindgen:deno": { - "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -143,7 +143,7 @@ ] }, "test:chromium": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", "dependencies": [ "build", "test:prepare" @@ -153,7 +153,7 @@ ] }, "test:firefox": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", "dependencies": [ "build", "test:prepare" @@ -163,7 +163,7 @@ ] }, "test:webkit": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", "dependencies": [ "build", "test:prepare" @@ -180,7 +180,7 @@ ] }, "test:node": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", "dependencies": [ "build", "test:prepare" diff --git a/src/ability/crud.rs b/src/ability/crud.rs index d1c7775d..2c77c4da 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -2,6 +2,7 @@ use crate::{promise::Promise, prove::TryProve}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; +// FIXME move to promise.rs #[derive(Debug, Clone, PartialEq)] pub enum Field where diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 7f60a9e5..a456704a 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -14,14 +14,14 @@ use wasm_bindgen::prelude::*; pub struct Dynamic<'a> { pub command: String, pub args: BTreeMap, // FIXME consider this being just JsValue - pub validater: &'a js_sys::Function, + pub validator: &'a js_sys::Function, } #[derive(Debug, Clone, PartialEq)] pub struct DynamicBuilder<'a> { pub command: String, pub args: Option>, - pub validater: &'a js_sys::Function, + pub validator: &'a js_sys::Function, } impl<'a> From> for DynamicBuilder<'a> { @@ -29,7 +29,7 @@ impl<'a> From> for DynamicBuilder<'a> { Self { command: dynamic.command.clone(), args: Some(dynamic.args), - validater: dynamic.validater, + validator: dynamic.validator, } } } @@ -73,6 +73,6 @@ impl<'a> TryProve> for DynamicBuilder<'a> { let js_self: JsValue = self.into().into(); let js_candidate: JsValue = candidate.into().into(); - self.validater.apply(js_self, js_candidate); + self.validator.apply(js_self, js_candidate); } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 005cca90..08f9d41d 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -90,10 +90,8 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Command for MsgReceiveBuilder { - fn command(&self) -> &'static str { - "msg/receive" - } +impl Command for MsgReceive { + const COMMAND: &'static str = "msg/receive"; } impl TryFrom<&Ipld> for MsgReceive { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index ff01dc09..c04c0a7a 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,34 +1,63 @@ -use std::fmt::Debug; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt::Debug}; pub trait Command { - fn command(&self) -> &'static str; + const COMMAND: &'static str; } -// FIXME this is always a builder, right? ToBuilder? Builder? Buildable? // FIXME Delegable and make it proven? -pub trait Buildable { - type Builder: Command + Debug; // FIXME +pub trait Delegatable: Sized { + type Builder: Debug + TryInto + From; +} - fn to_builder(&self) -> Self::Builder; - fn try_build(builder: Self::Builder) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +pub trait Resolvable: Sized { + type Awaiting: Command + Debug + TryInto + From; } -pub trait Resolvable: Buildable { - type Awaiting: Command + Debug; // FIXME +// FIXME Delegatable? +pub trait Runnable { + type Output; +} - fn to_awaitable(&self) -> Self::Awaiting; - fn try_into_resolved(promise: Self::Awaiting) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +#[derive(Debug, Clone, PartialEq)] +pub struct DynJs { + pub cmd: &'static str, + pub args: BTreeMap, } -impl Command for T { - fn command(&self) -> &'static str { - self.to_builder().command() +impl Delegatable for DynJs { + type Builder = Self; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct JsHack(pub DynJs); + +impl From for JsHack { + fn from(dyn_js: DynJs) -> Self { + Self(dyn_js) } } -pub trait Runnable { - type Output; +impl From for Ipld { + fn from(js_hack: JsHack) -> Self { + let mut map = BTreeMap::new(); + map.insert("command".into(), js_hack.0.cmd.into()); + map.into() + } +} + +impl TryFrom for DynJs { + type Error = (); // FIXME + + fn try_from(_ipld: Ipld) -> Result { + todo!() + } +} + +impl Command for JsHack { + const COMMAND: &'static str = "ucan/dyn/js"; } -pub trait Ability: Buildable + Runnable {} -// FIXME macro for Delegation (builder) and Promises +impl Delegatable for JsHack { + type Builder = Self; +} diff --git a/src/capsule.rs b/src/capsule.rs new file mode 100644 index 00000000..357bc5ec --- /dev/null +++ b/src/capsule.rs @@ -0,0 +1,3 @@ +pub trait Capsule { + const TAG: &'static str; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c78f4bca..81f07b26 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,7 +1,8 @@ use super::{condition::Condition, delegate::Delegate}; use crate::{ - ability::traits::{Buildable, Command}, - signature, + ability::traits::{Command, Delegatable}, + capsule::Capsule, + nonce::Nonce, time::Timestamp, }; use did_url::DID; @@ -9,7 +10,7 @@ use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: DID, @@ -18,20 +19,21 @@ pub struct Payload { pub conditions: Vec, // pub fixme: Vec>, pub metadata: BTreeMap, - pub nonce: Vec, + pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl signature::Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl + Clone> From<&Payload> for Ipld +impl + Clone> From<&Payload> + for Ipld where Ipld: From, - B::Builder: Clone, // FIXME + B::Builder: Command + Clone, // FIXME { fn from(payload: &Payload) -> Self { let mut map = BTreeMap::new(); @@ -41,10 +43,10 @@ where let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().into(), + Delegate::Specific(builder) => B::COMMAND.into(), }; - map.insert("can".into(), can); + map.insert("cmd".into(), can); map.insert( "args".into(), @@ -83,7 +85,8 @@ where } } -impl + Clone> From> for Ipld +impl + Clone> From> + for Ipld where Ipld: From, { @@ -95,10 +98,10 @@ where let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().into(), + Delegate::Specific(builder) => B::COMMAND.into(), }; - map.insert("can".into(), can); + map.insert("cmd".into(), can); map.insert( "args".into(), diff --git a/src/error.rs b/src/error.rs index 2fb4da11..98d665b4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs_ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs_ucan/issues")] + #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/invocation.rs b/src/invocation.rs index 0d94da63..b58d5465 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,117 +1,6 @@ -use crate::{ - ability::traits::{Buildable, Command}, - delegation, - delegation::{condition::Condition, Delegate, Delegation}, - receipt::Receipt, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; -use std::{collections::BTreeMap, fmt::Debug}; +pub mod payload; -pub type Invocation = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: Option, - - pub ability: B, - - // pub proofs: Vec>>, // FIXME just use Cid? - pub proofs: Vec, // FIXME just use Cid? - pub cause: Option, - pub metadata: BTreeMap, // FIXME serde value instead? - pub nonce: Vec, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -// FIXME move that clone? -impl From<&Payload> for delegation::Payload { - fn from(invocation: &Payload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().to_builder()), - conditions: vec![], - // fixme: vec![], - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } -} - -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; -} - -impl> From> for Ipld { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert( - "aud".into(), - payload - .audience - .map(|audience| audience.to_string()) - .unwrap_or(payload.issuer.to_string()) - .into(), - ); +use crate::signature; +use payload::Payload; - map.insert("can".into(), payload.ability.command().into()); - map.insert("args".into(), payload.ability.into()); - - map.insert( - "proofs".into(), - Ipld::List( - payload - .proofs - .into_iter() - .map(|cid| cid.into()) - // .map(|link| link.cid().into()) - .collect::>(), - ), - ); - - map.insert( - "cause".into(), - payload - .cause - // .map(|link| link.cid().into()) - .map(|cid| cid.into()) - .unwrap_or(Ipld::Null), - ); - - map.insert( - "metadata".into(), - Ipld::Map( - payload - .metadata - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>(), - ), - ); - - map.insert("nonce".into(), payload.nonce.into()); - - map.insert("exp".into(), payload.expiration.into()); - - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); - } - - map.into() - } -} +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs new file mode 100644 index 00000000..2f445d37 --- /dev/null +++ b/src/invocation/payload.rs @@ -0,0 +1,137 @@ +use crate::{ + ability::traits::{Command, Delegatable, DynJs, JsHack}, + capsule::Capsule, + delegation, + delegation::{condition::Condition, Delegate}, + nonce::Nonce, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, + + pub ability: B, + + pub proofs: Vec, + pub cause: Option, + pub metadata: BTreeMap, // FIXME parameterize? + pub nonce: Nonce, + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From<&Payload> for delegation::Payload { + fn from(invocation: &Payload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + ability_builder: Delegate::Specific(invocation.ability.clone().into()), + conditions: vec![], + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +} + +impl> From> for Ipld { + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert( + "aud".into(), + payload + .audience + .map(|audience| audience.to_string()) + .unwrap_or(payload.issuer.to_string()) + .into(), + ); + + map.insert("cmd".into(), B::COMMAND.into()); + map.insert("args".into(), payload.ability.into()); + + map.insert( + "proofs".into(), + Ipld::List( + payload + .proofs + .into_iter() + .map(|cid| cid.into()) + .collect::>(), + ), + ); + + map.insert( + "cause".into(), + payload + .cause + .map(|cid| cid.into()) + .unwrap_or(Ipld::Null), + ); + + map.insert( + "metadata".into(), + Ipld::Map( + payload + .metadata + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + ); + + map.insert("nonce".into(), payload.nonce.into()); + + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} + +// FIXME TEMPORARY HACK to prove out proof of concept +impl From> for Ipld { + fn from(payload: Payload) -> Self { + let hack_payload = Payload { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + ability: JsHack(payload.ability.clone()), + proofs: payload.proofs, + cause: payload.cause, + metadata: payload.metadata, + nonce: payload.nonce, + expiration: payload.expiration, + not_before: payload.not_before, + }; + + if let Ipld::Map(mut map) = hack_payload.into() { + map.insert("cmd".into(), payload.ability.cmd.into()); + Ipld::Map(map) + } else { + // FIXME bleh this code + unreachable!() + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3d8f83d5..31d888b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs_ucan +//! rs-ucan use std::str::FromStr; @@ -112,13 +112,15 @@ use std::fmt::Debug; // pub mod ability; +pub mod capsule; pub mod delegation; pub mod invocation; pub mod ipld; +pub mod nonce; pub mod promise; pub mod prove; pub mod receipt; -pub mod signature; // FIXME +pub mod signature; /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/nonce.rs b/src/nonce.rs new file mode 100644 index 00000000..74355690 --- /dev/null +++ b/src/nonce.rs @@ -0,0 +1,135 @@ +// use crate::{Error, Unit}; +use enum_as_inner::EnumAsInner; +use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use uuid::Uuid; + +// FIXME +pub struct Unit; +// FIXME +pub struct Error(T); + +/// Enumeration over allowed `nonce` types. +#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] +pub enum Nonce { + /// 96-bit, 12-byte nonce, e.g. [`xid`]. + Nonce96([u8; 12]), + /// 128-bit, 16-byte nonce. + Nonce128([u8; 16]), + /// No Nonce attributed. + Custom(Vec), +} + +impl Nonce { + /// Default generator, outputting an [`xid`] nonce, + /// which is a 96-bit (12-byte) nonce. + pub fn generate() -> Self { + Nonce::Nonce96(*xid::new().as_bytes()) + } + + /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. + pub fn generate_128() -> Self { + Nonce::Nonce128(*Uuid::new_v4().as_bytes()) + } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Nonce::Nonce96(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + Nonce::Nonce128(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + Nonce::Custom(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + } + } +} + +impl From for Ipld { + fn from(nonce: Nonce) -> Self { + match nonce { + Nonce::Nonce96(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce128(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Custom(nonce) => Ipld::Bytes(nonce), + } + } +} + +impl TryFrom for Nonce { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Bytes(v) = ipld { + match v.len() { + 12 => Ok(Nonce::Nonce96( + v.try_into() + .expect("12 bytes because we checked in the match"), + )), + 16 => Ok(Nonce::Nonce128( + v.try_into() + .expect("16 bytes because we checked in the match"), + )), + _ => Ok(Nonce::Custom(v)), + } + } else { + Err(()) + } + } +} + +impl TryFrom<&Ipld> for Nonce { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + TryFrom::try_from(ipld.to_owned()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn ipld_roundtrip_12() { + let gen = Nonce::generate(); + let ipld = Ipld::from(gen.clone()); + + let inner = if let Nonce::Nonce96(nonce) = gen { + Ipld::Bytes(nonce.to_vec()) + } else { + panic!("No conversion!") + }; + + assert_eq!(ipld, inner); + assert_eq!(gen, ipld.try_into().unwrap()); + } + + #[test] + fn ipld_roundtrip_16() { + let gen = Nonce::generate_128(); + let ipld = Ipld::from(gen.clone()); + + let inner = if let Nonce::Nonce128(nonce) = gen { + Ipld::Bytes(nonce.to_vec()) + } else { + panic!("No conversion!") + }; + + assert_eq!(ipld, inner); + assert_eq!(gen, ipld.try_into().unwrap()); + } + + #[test] + fn ser_de() { + let gen = Nonce::generate_128(); + let ser = serde_json::to_string(&gen).unwrap(); + let de = serde_json::from_str(&ser).unwrap(); + + assert_eq!(gen, de); + } +} diff --git a/src/promise.rs b/src/promise.rs index 5bf38a28..9d23ff51 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,20 +1,7 @@ -use crate::{ability::traits::Buildable, invocation, invocation::Invocation, signature::Capsule}; use cid::Cid; -use libipld_core::{ipld::Ipld, link::Link}; +use libipld_core::ipld::Ipld; use std::fmt::Debug; -// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -// #[derive(Debug, Clone, PartialEq)] -// pub enum Promise -// where -// A: Ability, // FIXME MUST be an Invocation -// invocation::Payload: Capsule, -// { -// PromiseAny(Link>), // FIXME not sure about specifying the A here -// PromiseOk(Link>), -// PromiseErr(Link>), -// } - /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq)] pub enum Promise { @@ -23,23 +10,28 @@ pub enum Promise { PromiseErr(Cid), } -// impl TryFrom for Promise -// where -// invocation::Payload: Capsule, -// { -// type Error = (); // FIXME -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(btree) = ipld { -// if let Some(Ipld::Link(link)) = btree.get("await/ok") { -// return Ok(Self::PromiseOk(link.clone().into())); -// } else if let Some(Ipld::Link(link)) = btree.get("await/err") { -// return Ok(Self::PromiseErr(link.clone().into())); -// } else if let Some(Ipld::Link(link)) = btree.get("await/*") { -// return Ok(Self::PromiseAny(link.clone().into())); -// } -// } -// -// Err(()) -// } -// } +impl TryFrom for Promise { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(btree) = ipld { + if btree.len() != 1 { + return Err(()); + } + + if let Some(Ipld::Link(link)) = btree.get("await/ok") { + return Ok(Self::PromiseOk(link.clone().into())); + } + + if let Some(Ipld::Link(link)) = btree.get("await/err") { + return Ok(Self::PromiseErr(link.clone().into())); + } + + if let Some(Ipld::Link(link)) = btree.get("await/*") { + return Ok(Self::PromiseAny(link.clone().into())); + } + } + + Err(()) + } +} diff --git a/src/receipt.rs b/src/receipt.rs index f8df1435..8f921123 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,30 +1,16 @@ use crate::{ - ability::traits::{Buildable, Command, Runnable}, - delegation::{condition::Condition, Delegation}, - invocation::Invocation, + ability::traits::{Command, Delegatable}, signature, - time::Timestamp, }; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; -pub type Receipt = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub ran: Cid, - pub out: Result>, +pub mod payload; +use payload::Payload; - pub proofs: Vec, - pub metadata: BTreeMap, - pub issued_at: Option, -} +pub type Receipt = signature::Envelope>; -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version -} +// FIXME show piping ability // FIXME #[derive(Debug, Clone, PartialEq)] @@ -33,22 +19,8 @@ pub struct ProxyExecute { pub args: BTreeMap, } -impl Buildable for ProxyExecute { +impl Delegatable for ProxyExecute { type Builder = ProxyExecuteBuilder; - - fn to_builder(&self) -> Self::Builder { - ProxyExecuteBuilder { - command: Some(self.command.clone()), - args: self.args.clone(), - } - } - - fn try_build(ProxyExecuteBuilder { command, args }: Self::Builder) -> Result, ()> { - match command { - None => Err(()), - Some(command) => Ok(Box::new(Self { command, args })), - } - } } // FIXME hmmm @@ -58,8 +30,26 @@ pub struct ProxyExecuteBuilder { pub args: BTreeMap, } -impl Command for ProxyExecuteBuilder { - fn command(&self) -> &'static str { - "ucan/proxy" // FIXME check spec +impl Command for ProxyExecute { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl From for ProxyExecuteBuilder { + fn from(proxy: ProxyExecute) -> Self { + ProxyExecuteBuilder { + command: Some(ProxyExecute::COMMAND.into()), + args: proxy.args.clone(), + } + } +} + +impl TryFrom for ProxyExecute { + type Error = (); // FIXME + + fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { + match command { + None => Err(()), + Some(command) => Ok(Self { command, args }), + } } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs new file mode 100644 index 00000000..ad8588cf --- /dev/null +++ b/src/receipt/payload.rs @@ -0,0 +1,29 @@ +use crate::{ + ability::traits::{Command, Delegatable, Runnable}, + capsule::Capsule, + nonce::Nonce, + signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + + pub ran: Cid, + pub out: Result>, + pub next: Vec, + + pub proofs: Vec, + pub metadata: BTreeMap, + + pub nonce: Nonce, + pub issued_at: Option, +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version +} diff --git a/src/signature.rs b/src/signature.rs index 1966742f..12ceda4d 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,11 +1,9 @@ -use libipld_core::{ipld::Ipld, link::Link}; +use crate::capsule::Capsule; +use libipld_core::ipld::Ipld; use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] -pub struct Envelope -where - T: Capsule, -{ +pub struct Envelope { pub sig: Signature, pub payload: T, } @@ -20,11 +18,6 @@ pub enum Signature { }, } -// TODO move to own module? -pub trait Capsule { - const TAG: &'static str; -} - impl From<&Signature> for Ipld { fn from(sig: &Signature) -> Self { match sig { diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs index 30e5c3ef..37b96b12 100644 --- a/src/test_utils/rvg.rs +++ b/src/test_utils/rvg.rs @@ -32,7 +32,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs_ucan::test_utils::Rvg; + /// use rs-ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let int = rvg.sample(&(0..100i32)); @@ -49,7 +49,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs_ucan::test_utils::Rvg; + /// use rs-ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let ints = rvg.sample_vec(&(0..100i32), 10); diff --git a/src/workerd.js b/src/workerd.js index ebd2009d..606cfdbb 100644 --- a/src/workerd.js +++ b/src/workerd.js @@ -1,6 +1,6 @@ // This entry point is inserted into ./lib/workerd to support Cloudflare workers -import WASM from "./rs_ucan_bg.wasm"; -import { initSync } from "./rs_ucan.js"; +import WASM from "./rs-ucan_bg.wasm"; +import { initSync } from "./rs-ucan.js"; initSync(WASM); -export * from "./rs_ucan.js"; +export * from "./rs-ucan.js"; diff --git a/tests/conformance.rs b/tests/conformance.rs index a69552fa..49973143 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -2,7 +2,7 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; -use rs_ucan::{ +use rs-ucan::{ capability::DefaultCapabilityParser, did_verifier::DidVerifierMap, store::{self, Store}, @@ -297,7 +297,7 @@ impl TestTask for VerifyTest { return; } - if let Err(err) = ucan.validate(rs_ucan::time::now(), &did_verifier_map) { + if let Err(err) = ucan.validate(rs-ucan::time::now(), &did_verifier_map) { report.register_failure(name, err.to_string()); return; @@ -319,7 +319,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan - .validate(rs_ucan::time::now(), &did_verifier_map) + .validate(rs-ucan::time::now(), &did_verifier_map) .is_ok() { report.register_failure( diff --git a/tests/rs_ucan.test.js b/tests/rs_ucan.test.js index 3b00bf07..f1201309 100644 --- a/tests/rs_ucan.test.js +++ b/tests/rs_ucan.test.js @@ -1,5 +1,5 @@ import assert from "assert"; -import { build, decode } from "../dist/bundler/rs_ucan.js"; +import { build, decode } from "../dist/bundler/rs-ucan.js"; describe("decode", async function () { let ucan = await decode( From 9cb4126585f0e0019ed69a15d1606e5098011378 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 09:48:03 -0800 Subject: [PATCH 077/234] Remove -rs from package name --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +-- Cargo.toml | 6 ++--- README.md | 24 ++++++++--------- SECURITY.md | 6 ++--- benches/a_benchmark.rs | 4 +-- flake.nix | 4 +-- package.json | 48 +++++++++++++++++----------------- src/error.rs | 2 +- src/lib.rs | 2 +- src/test_utils/rvg.rs | 4 +-- src/workerd.js | 6 ++--- tests/conformance.rs | 6 ++--- tests/rs_ucan.test.js | 2 +- 15 files changed, 61 insertions(+), 61 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 76052f66..f7b2cc74 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bbcf0c75..db763fcb 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c6d8dcf..397b0e3f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs-ucan +# Contributing to ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs-ucan look at our + - For a detailed look on how to build ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index d9da4d64..cc9606be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rs-ucan" +name = "ucan" version = "0.2.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs-ucan" -repository = "https://github.com/ucan-wg/rs-ucan" +documentation = "https://docs.rs/ucan" +repository = "https://github.com/ucan-wg/ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs-ucan Logo + + ucan Logo -

rs-ucan

+

ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "1.0.0-rc.1" +ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index c74cc826..a7a3276b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The `rs-ucan` team welcomes security reports and is committed to +The `ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs-ucan team announces security advisories through our +process. The ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs index 76f63eb8..fccc419e 100644 --- a/benches/a_benchmark.rs +++ b/benches/a_benchmark.rs @@ -1,13 +1,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; pub fn add_benchmark(c: &mut Criterion) { - let mut rvg = rs-ucan::test_utils::Rvg::deterministic(); + let mut rvg = ucan::test_utils::Rvg::deterministic(); let int_val_1 = rvg.sample(&(0..100i32)); let int_val_2 = rvg.sample(&(0..100i32)); c.bench_function("add", |b| { b.iter(|| { - rs-ucan::add(int_val_1, int_val_2); + ucan::add(int_val_1, int_val_2); }) }); } diff --git a/flake.nix b/flake.nix index 947a2386..3e573152 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs-ucan"; + description = "ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs-ucan"; + name = "ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index b657427b..93317fbb 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs-ucan", + "description": "A UCAN library built from ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs-ucan.git" + "url": "git+https://github.com/fission-codes/ucan.git" }, "keywords": [ "authorization" @@ -12,30 +12,30 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs-ucan/issues" + "url": "https://github.com/fission-codes/ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs-ucan#readme", - "module": "dist/bundler/rs-ucan.js", - "types": "dist/nodejs/rs-ucan.d.ts", + "homepage": "https://github.com/fission-codes/ucan#readme", + "module": "dist/bundler/ucan.js", + "types": "dist/nodejs/ucan.d.ts", "exports": { ".": { "workerd": "./dist/web/workerd.js", - "browser": "./dist/bundler/rs-ucan.js", - "node": "./dist/nodejs/rs-ucan.cjs", - "default": "./dist/bundler/rs-ucan.js", - "types": "./dist/nodejs/rs-ucan.d.ts" + "browser": "./dist/bundler/ucan.js", + "node": "./dist/nodejs/ucan.cjs", + "default": "./dist/bundler/ucan.js", + "types": "./dist/nodejs/ucan.d.ts" }, "./nodejs": { - "default": "./dist/nodejs/rs-ucan.cjs", - "types": "./dist/nodejs/rs-ucan.d.ts" + "default": "./dist/nodejs/ucan.cjs", + "types": "./dist/nodejs/ucan.d.ts" }, "./web": { - "default": "./dist/web/rs-ucan.js", - "types": "./dist/web/rs-ucan.d.ts" + "default": "./dist/web/ucan.js", + "types": "./dist/web/ucan.d.ts" }, "./workerd": { "default": "./dist/web/workerd.js", - "types": "./dist/web/rs-ucan.d.ts" + "types": "./dist/web/ucan.d.ts" } }, "files": [ @@ -59,7 +59,7 @@ } }, "opt": { - "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -70,7 +70,7 @@ ] }, "bindgen:bundler": { - "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -84,7 +84,7 @@ ] }, "bindgen:nodejs": { - "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && move-file dist/nodejs/rs-ucan.js dist/nodejs/rs-ucan.cjs", + "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm && move-file dist/nodejs/ucan.js dist/nodejs/ucan.cjs", "env": { "TARGET_DIR": { "external": true @@ -98,7 +98,7 @@ ] }, "bindgen:web": { - "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && cpy --flat src/workerd.js dist/web", + "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm && cpy --flat src/workerd.js dist/web", "env": { "TARGET_DIR": { "external": true @@ -112,7 +112,7 @@ ] }, "bindgen:deno": { - "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -143,7 +143,7 @@ ] }, "test:chromium": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", "dependencies": [ "build", "test:prepare" @@ -153,7 +153,7 @@ ] }, "test:firefox": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", "dependencies": [ "build", "test:prepare" @@ -163,7 +163,7 @@ ] }, "test:webkit": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", "dependencies": [ "build", "test:prepare" @@ -180,7 +180,7 @@ ] }, "test:node": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", "dependencies": [ "build", "test:prepare" diff --git a/src/error.rs b/src/error.rs index 98d665b4..a67fb364 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] + #[error("An unexpected error occurred in ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/lib.rs b/src/lib.rs index 31d888b5..c827a85f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs-ucan +//! ucan use std::str::FromStr; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs index 37b96b12..834c437e 100644 --- a/src/test_utils/rvg.rs +++ b/src/test_utils/rvg.rs @@ -32,7 +32,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs-ucan::test_utils::Rvg; + /// use ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let int = rvg.sample(&(0..100i32)); @@ -49,7 +49,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs-ucan::test_utils::Rvg; + /// use ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let ints = rvg.sample_vec(&(0..100i32), 10); diff --git a/src/workerd.js b/src/workerd.js index 606cfdbb..fad5ae1d 100644 --- a/src/workerd.js +++ b/src/workerd.js @@ -1,6 +1,6 @@ // This entry point is inserted into ./lib/workerd to support Cloudflare workers -import WASM from "./rs-ucan_bg.wasm"; -import { initSync } from "./rs-ucan.js"; +import WASM from "./ucan_bg.wasm"; +import { initSync } from "./ucan.js"; initSync(WASM); -export * from "./rs-ucan.js"; +export * from "./ucan.js"; diff --git a/tests/conformance.rs b/tests/conformance.rs index 49973143..46aa3a65 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -2,7 +2,7 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; -use rs-ucan::{ +use ucan::{ capability::DefaultCapabilityParser, did_verifier::DidVerifierMap, store::{self, Store}, @@ -297,7 +297,7 @@ impl TestTask for VerifyTest { return; } - if let Err(err) = ucan.validate(rs-ucan::time::now(), &did_verifier_map) { + if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { report.register_failure(name, err.to_string()); return; @@ -319,7 +319,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan - .validate(rs-ucan::time::now(), &did_verifier_map) + .validate(ucan::time::now(), &did_verifier_map) .is_ok() { report.register_failure( diff --git a/tests/rs_ucan.test.js b/tests/rs_ucan.test.js index f1201309..f8385e26 100644 --- a/tests/rs_ucan.test.js +++ b/tests/rs_ucan.test.js @@ -1,5 +1,5 @@ import assert from "assert"; -import { build, decode } from "../dist/bundler/rs-ucan.js"; +import { build, decode } from "../dist/bundler/ucan.js"; describe("decode", async function () { let ucan = await decode( From 7a5452f9b76c57b4bd4fba767de2e34c58235094 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 12:45:58 -0800 Subject: [PATCH 078/234] Thanks for the suggestions everyone! :tada: --- Cargo.toml | 7 +- src/ability/crud.rs | 6 +- src/ability/msg.rs | 82 ++++-------------- src/delegation/condition/common.rs | 132 +++++++++++++++++++---------- src/promise.rs | 41 ++++----- 5 files changed, 132 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cc9606be..58d0b05d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,22 +46,23 @@ erased-serde = "0.3.31" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" -libipld-core = "0.16.0" +libipld-core = { version = "0.16", features = ["serde-codec"] } multibase = "0.9.1" p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } proptest = { version = "1.1", optional = true } -regex = { version = "1.10" } +regex = "1.10" rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } +serde_derive = "1.0" serde_json = "1.0.107" signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = "2.5" +url = {version = "2.5", features = ["serde"]} web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 2c77c4da..6f69d2f3 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -16,7 +16,7 @@ where // ... also maybe Ipld pub struct Crud { - uri: Field, + pub uri: Field, } pub struct CrudRead { @@ -24,7 +24,7 @@ pub struct CrudRead { } pub struct CrudMutate { - uri: Field, + pub uri: Field, } pub struct CrudCreate { @@ -32,11 +32,13 @@ pub struct CrudCreate { pub args: BTreeMap, Field>, } +#[derive(Debug, Clone, PartialEq)] pub struct CrudUpdate { pub uri: Field, pub args: BTreeMap, Field>, } +#[derive(Debug, Clone, PartialEq)] pub struct CrudDestroy { pub uri: Field, } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 08f9d41d..b7a12de8 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,15 +1,17 @@ use crate::{ability::traits::Command, prove::TryProve}; -use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, str::FromStr}; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use url::Url; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Msg { to: Url, from: Url, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgSend { to: Url, from: Url, @@ -17,13 +19,15 @@ pub struct MsgSend { } // TODO is the to or from often also the subject? Shoudl that be accounted for? -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgReceive { to: Url, from: Url, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgReceiveBuilder { to: Option, from: Option, @@ -52,41 +56,16 @@ impl TryFrom for MsgReceive { } impl From for Ipld { - fn from(msg: MsgReceive) -> Self { - let mut map = BTreeMap::new(); - map.insert("to".into(), msg.to.to_string().into()); - map.insert("from".into(), msg.from.to_string().into()); - map.into() + fn from(msg_rcv: MsgReceive) -> Self { + msg_rcv.into() } } -impl TryFrom<&Ipld> for MsgReceiveBuilder { +impl TryFrom for MsgReceiveBuilder { type Error = (); - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("from") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { to, from }) - } - _ => Err(()), - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } @@ -94,36 +73,11 @@ impl Command for MsgReceive { const COMMAND: &'static str = "msg/receive"; } -impl TryFrom<&Ipld> for MsgReceive { +impl TryFrom for MsgReceive { type Error = (); // FIXME - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("from") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { - to: to.unwrap(), - from: from.unwrap(), - }) - } - _ => Err(()), - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs index bd568c0b..8588b22b 100644 --- a/src/delegation/condition/common.rs +++ b/src/delegation/condition/common.rs @@ -1,7 +1,8 @@ use super::Condition; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; use regex::Regex; -use std::collections::BTreeMap; +use serde; +use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] pub enum Common { @@ -16,47 +17,24 @@ pub enum Common { // FIXME dynamic js version? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainsAll { field: String, values: Vec, } -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - let mut map = BTreeMap::new(); - map.insert("field".into(), contains_all.field.into()); - map.insert("values".into(), contains_all.values.into()); - map.into() - } -} +// impl From for Ipld { +// fn from(contains_all: ContainsAll) -> Self { +// contains_all.into() +// } +// } -impl TryFrom<&Ipld> for ContainsAll { +impl TryFrom for ContainsAll { type Error = (); // FIXME - fn try_from(ipld: &Ipld) -> Result { - if let Ipld::Map(map) = ipld { - if map.len() != 2 { - return Err(()); - } - - if let Some(Ipld::String(field)) = map.get("field") { - let values = match map.get("values") { - None => Ok(vec![]), - Some(Ipld::List(values)) => Ok(values.to_vec()), - _ => Err(()), - }?; - - Ok(Self { - field: field.to_string(), - values: values.to_vec(), - }) - } else { - Err(()) - } - } else { - Err(()) - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } @@ -73,7 +51,8 @@ impl Condition for ContainsAll { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainsAny { field: String, value: Vec, @@ -92,7 +71,8 @@ impl Condition for ContainsAny { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ExcludesAll { field: String, value: Vec, @@ -111,18 +91,45 @@ impl Condition for ExcludesAll { } } -#[derive(Debug, Clone, PartialEq)] +impl TryFrom for ExcludesAll { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +// FIXME serialization? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum Numeric { Float(f64), Integer(i128), } -#[derive(Debug, Clone, PartialEq)] +impl TryFrom for Numeric { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MinNumber { field: String, value: Numeric, } +impl TryFrom for MinNumber { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + impl Condition for MinNumber { fn validate(&self, ipld: &Ipld) -> bool { match ipld { @@ -139,7 +146,8 @@ impl Condition for MinNumber { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MaxNumber { field: String, value: Numeric, @@ -161,7 +169,16 @@ impl Condition for MaxNumber { } } -#[derive(Debug, Clone, PartialEq, Eq)] +impl TryFrom for MaxNumber { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MinLength { field: String, value: u64, @@ -178,7 +195,8 @@ impl Condition for MinLength { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MaxLength { field: String, value: u64, @@ -195,7 +213,8 @@ impl Condition for MaxLength { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Matches { field: String, matcher: Matcher, @@ -210,6 +229,33 @@ impl PartialEq for Matcher { } } +impl Eq for Matcher {} + +impl serde::Serialize for Matcher { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Matcher { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + match Regex::new(s) { + Ok(regex) => Ok(Matcher(regex)), + Err(_) => { + // FIXME + todo!() + } + } + } +} + impl Condition for Matcher { fn validate(&self, ipld: &Ipld) -> bool { match ipld { diff --git a/src/promise.rs b/src/promise.rs index 9d23ff51..231be586 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,37 +1,30 @@ use cid::Cid; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { - PromiseAny(Cid), // FIXME not sure about specifying the A here - PromiseOk(Cid), - PromiseErr(Cid), + PromiseAny { + #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? + await_any: Cid, + }, + PromiseOk { + #[serde(rename = "ucan/ok")] + await_ok: Cid, + }, + PromiseErr { + #[serde(rename = "ucan/err")] + await_err: Cid, + }, } impl TryFrom for Promise { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(btree) = ipld { - if btree.len() != 1 { - return Err(()); - } - - if let Some(Ipld::Link(link)) = btree.get("await/ok") { - return Ok(Self::PromiseOk(link.clone().into())); - } - - if let Some(Ipld::Link(link)) = btree.get("await/err") { - return Ok(Self::PromiseErr(link.clone().into())); - } - - if let Some(Ipld::Link(link)) = btree.get("await/*") { - return Ok(Self::PromiseAny(link.clone().into())); - } - } - - Err(()) + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } From 454670cc806a82028c990256be3fbc09b061e0cb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 12:46:21 -0800 Subject: [PATCH 079/234] Update cargo --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 58d0b05d..cb865d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = {version = "2.5", features = ["serde"]} +url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] From a22b0c2147b4325fac83c83555c1542de933a879 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 22:27:10 -0800 Subject: [PATCH 080/234] Feels like a hack, but that's a workable serializaion strategy --- src/ability/any.rs | 2 +- src/ability/crud.rs | 26 ++-- src/ability/msg.rs | 115 ++++++++++++++++- src/ability/traits.rs | 41 +++--- src/delegation/delegate.rs | 36 ++---- src/delegation/payload.rs | 176 +++++++++++-------------- src/did.rs | 44 +++++++ src/invocation.rs | 3 +- src/invocation/payload.rs | 254 ++++++++++++++++++++++--------------- src/lib.rs | 1 + src/promise.rs | 16 ++- src/prove.rs | 4 +- src/time.rs | 25 ++-- 13 files changed, 448 insertions(+), 295 deletions(-) create mode 100644 src/did.rs diff --git a/src/ability/any.rs b/src/ability/any.rs index bac76d01..e6ff78ee 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -4,7 +4,7 @@ use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl<'a, T> TryProve<'a, DelegateAny> for T { +impl<'a, T> TryProve<&'a DelegateAny> for &'a T { type Error = Infallible; type Proven = T; diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 6f69d2f3..5bf15133 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -2,45 +2,35 @@ use crate::{promise::Promise, prove::TryProve}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; -// FIXME move to promise.rs -#[derive(Debug, Clone, PartialEq)] -pub enum Field -where - T: Debug + Clone + PartialEq, -{ - Value(T), - Await(Promise), // FIXME -} - // FIXME macro to derive promise versions & delagted builder versions // ... also maybe Ipld pub struct Crud { - pub uri: Field, + pub uri: Url, } pub struct CrudRead { - pub uri: Field, + pub uri: Url, } pub struct CrudMutate { - pub uri: Field, + pub uri: Url, } pub struct CrudCreate { - pub uri: Field, - pub args: BTreeMap, Field>, + pub uri: Url, + pub args: BTreeMap, String>, } #[derive(Debug, Clone, PartialEq)] pub struct CrudUpdate { - pub uri: Field, - pub args: BTreeMap, Field>, + pub uri: Url, + pub args: BTreeMap, String>, } #[derive(Debug, Clone, PartialEq)] pub struct CrudDestroy { - pub uri: Field, + pub uri: Url, } // FIXME these should probably be behind a feature flag diff --git a/src/ability/msg.rs b/src/ability/msg.rs index b7a12de8..dda6eb85 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,4 +1,8 @@ -use crate::{ability::traits::Command, prove::TryProve}; +use crate::{ + ability::traits::{Command, Delegatable, Resolvable}, + promise::Deferrable, + prove::TryProve, +}; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use url::Url; @@ -10,6 +14,79 @@ pub struct Msg { from: Url, } +impl Command for Msg { + const COMMAND: &'static str = "msg/*"; +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgBuilder { + #[serde(skip_serializing_if = "Option::is_none")] + to: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + from: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgDeferrable { + to: Deferrable, + from: Deferrable, +} + +impl Delegatable for Msg { + type Builder = MsgBuilder; +} + +impl Resolvable for Msg { + type Awaiting = MsgDeferrable; +} + +impl From for MsgBuilder { + fn from(msg: Msg) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for Msg { + type Error = (); + + fn try_from(builder: MsgBuilder) -> Result { + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(()) // FIXME + } + } +} + +impl From for MsgDeferrable { + fn from(msg: Msg) -> Self { + Self { + to: Deferrable::Resolved(msg.to), + from: Deferrable::Resolved(msg.from), + } + } +} + +impl TryFrom for Msg { + type Error = (); + + fn try_from(deferable: MsgDeferrable) -> Result { + if let (Deferrable::Resolved(to), Deferrable::Resolved(from)) = + (deferable.to, deferable.from) + { + Ok(Self { to, from }) + } else { + Err(()) // FIXME + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MsgSend { @@ -18,6 +95,25 @@ pub struct MsgSend { message: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgSendBuilder { + #[serde(skip_serializing_if = "Option::is_none")] + to: Option, + #[serde(skip_serializing_if = "Option::is_none")] + from: Option, + #[serde(skip_serializing_if = "Option::is_none")] + message: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgSendDeferrable { + to: Deferrable, + from: Deferrable, + message: Deferrable, +} + // TODO is the to or from often also the subject? Shoudl that be accounted for? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -29,7 +125,9 @@ pub struct MsgReceive { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MsgReceiveBuilder { + #[serde(skip_serializing_if = "Option::is_none")] to: Option, + #[serde(skip_serializing_if = "Option::is_none")] from: Option, } @@ -69,6 +167,13 @@ impl TryFrom for MsgReceiveBuilder { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgReceiveDeferrable { + to: Deferrable, + from: Deferrable, +} + impl Command for MsgReceive { const COMMAND: &'static str = "msg/receive"; } @@ -81,7 +186,7 @@ impl TryFrom for MsgReceive { } } -impl<'a> TryProve<'a, Msg> for Msg { +impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME type Proven = Msg; @@ -94,7 +199,7 @@ impl<'a> TryProve<'a, Msg> for Msg { } } -impl<'a> TryProve<'a, Msg> for MsgSend { +impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME type Proven = MsgSend; @@ -107,7 +212,7 @@ impl<'a> TryProve<'a, Msg> for MsgSend { } } -impl<'a> TryProve<'a, Msg> for MsgReceive { +impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; @@ -121,7 +226,7 @@ impl<'a> TryProve<'a, Msg> for MsgReceive { } // FIXME this needs to work on builders! -impl<'a> TryProve<'a, MsgReceive> for MsgReceive { +impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; diff --git a/src/ability/traits.rs b/src/ability/traits.rs index c04c0a7a..775db6cf 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,6 @@ -use libipld_core::ipld::Ipld; +use crate::prove::TryProve; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; pub trait Command { @@ -8,10 +10,11 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; + // type Builder: Debug + /*TryProve FIXME */ + TryInto + From; } pub trait Resolvable: Sized { - type Awaiting: Command + Debug + TryInto + From; + type Awaiting: Debug + TryInto + From; } // FIXME Delegatable? @@ -19,9 +22,10 @@ pub trait Runnable { type Output; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DynJs { - pub cmd: &'static str, + pub cmd: String, pub args: BTreeMap, } @@ -29,35 +33,20 @@ impl Delegatable for DynJs { type Builder = Self; } -#[derive(Debug, Clone, PartialEq)] -pub struct JsHack(pub DynJs); - -impl From for JsHack { - fn from(dyn_js: DynJs) -> Self { - Self(dyn_js) - } +impl Resolvable for DynJs { + type Awaiting = Self; } -impl From for Ipld { - fn from(js_hack: JsHack) -> Self { - let mut map = BTreeMap::new(); - map.insert("command".into(), js_hack.0.cmd.into()); - map.into() +impl From for Ipld { + fn from(js: DynJs) -> Self { + js.into() } } impl TryFrom for DynJs { type Error = (); // FIXME - fn try_from(_ipld: Ipld) -> Result { - todo!() + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } - -impl Command for JsHack { - const COMMAND: &'static str = "ucan/dyn/js"; -} - -impl Delegatable for JsHack { - type Builder = Self; -} diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs index 1b645f58..0046484a 100644 --- a/src/delegation/delegate.rs +++ b/src/delegation/delegate.rs @@ -1,39 +1,19 @@ -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] pub enum Delegate { + #[serde(rename = "ucan/*")] Any, Specific(T), } -impl<'a, T> From<&'a Delegate> for Ipld -where - Ipld: From<&'a T>, -{ - fn from(delegate: &'a Delegate) -> Self { - match delegate { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(command) => command.into(), - } - } -} - -impl<'a, T: TryFrom<&'a Ipld>> TryFrom<&'a Ipld> for Delegate { +impl + serde::de::DeserializeOwned> TryFrom for Delegate { type Error = (); // FIXME - fn try_from(ipld: &'a Ipld) -> Result { - if let ipld_string @ Ipld::String(st) = ipld { - if st == "ucan/*" { - Ok(Self::Any) - } else { - match T::try_from(ipld_string) { - Err(_) => Err(()), - Ok(cmd) => Ok(Self::Specific(cmd)), - } - } - } else { - Err(()) - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 81f07b26..f5316e8e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,23 +1,27 @@ use super::{condition::Condition, delegate::Delegate}; use crate::{ - ability::traits::{Command, Delegatable}, + ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, + did::Did, nonce::Nonce, + prove::TryProve, time::Timestamp, }; use did_url::DID; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +use web_time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: DID, +pub struct Payload { + pub issuer: Did, + pub subject: Did, + pub audience: Did, - pub ability_builder: Delegate, + pub ability_builder: T::Builder, pub conditions: Vec, - // pub fixme: Vec>, + pub metadata: BTreeMap, pub nonce: Nonce, @@ -25,114 +29,86 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload +where + T::Builder: serde::Serialize + serde::de::DeserializeOwned, +{ const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl + Clone> From<&Payload> - for Ipld +// FIXME +impl TryFrom + for Payload where - Ipld: From, - B::Builder: Command + Clone, // FIXME + T::Builder: serde::Serialize + serde::de::DeserializeOwned, { - fn from(payload: &Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => B::COMMAND.into(), - }; - - map.insert("cmd".into(), can); - - map.insert( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => (*builder).clone().into(), // FIXME - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.clone().into()); - map.insert("exp".into(), payload.expiration.clone().into()); + type Error = (); - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); - } - - map.into() + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).unwrap() // FIXME } } -impl + Clone> From> - for Ipld +// impl + Clone> From> for Ipld { +// fn from(payload: Payload) -> Self { +// // FIXME I bet this clone can be removed by switcing to &DynJs +// if let Ipld::Map(mut map) = payload.clone().into() { +// map.insert("cmd".into(), payload.ability_builder.cmd.into()); +// map.insert("args".into(), payload.ability_builder.args.into()); +// map.into() +// } else { +// panic!("FIXME") +// } +// } +// } + +impl + Clone> From> for Ipld where - Ipld: From, + Ipld: From, { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - + fn from(payload: Payload) -> Self { let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => B::COMMAND.into(), + Delegate::Specific(builder) => T::COMMAND.into(), }; - map.insert("cmd".into(), can); - - map.insert( - "args".into(), - match payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => builder.into(), - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .into_iter() - .map(|condition| condition.into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.into()); - map.insert("exp".into(), payload.expiration.into()); + let mut map = BTreeMap::from_iter([ + ("iss".into(), payload.issuer.to_string().into()), + ("sub".into(), payload.subject.to_string().into()), + ("aud".into(), payload.audience.to_string().into()), + ( + "args".into(), + match &payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => (*builder).clone().into(), // FIXME + }, + ), + ("cmd".into(), can), + ( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ), + ( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ), + ("nonce".into(), payload.nonce.clone().into()), + ("exp".into(), payload.expiration.clone().into()), + ]); - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); } map.into() diff --git a/src/did.rs b/src/did.rs new file mode 100644 index 00000000..8b856104 --- /dev/null +++ b/src/did.rs @@ -0,0 +1,44 @@ +use did_url::DID; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(into = "String", try_from = "String")] +pub struct Did(DID); + +impl From for String { + fn from(did: Did) -> Self { + did.0.to_string() + } +} + +impl TryFrom for Did { + type Error = String; // FIXME + + fn try_from(string: String) -> Result { + DID::parse(&string) + .map_err(|err| format!("Failed to parse DID: {}", err)) + .map(Self) + } +} + +impl fmt::Display for Did { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl From for Ipld { + fn from(did: Did) -> Self { + did.into() + } +} + +impl TryFrom for Did { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + Self::try_from(ipld) + } +} diff --git a/src/invocation.rs b/src/invocation.rs index b58d5465..33842a80 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,6 +1,5 @@ pub mod payload; use crate::signature; -use payload::Payload; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2f445d37..a8a0966d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,137 +1,193 @@ use crate::{ - ability::traits::{Command, Delegatable, DynJs, JsHack}, + ability::traits::{Command, DynJs, Resolvable}, capsule::Capsule, - delegation, - delegation::{condition::Condition, Delegate}, + did::Did, nonce::Nonce, time::Timestamp, }; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: Option, +pub struct Payload { + pub issuer: Did, + pub subject: Did, + pub audience: Option, - pub ability: B, + pub ability: T::Awaiting, pub proofs: Vec, pub cause: Option, pub metadata: BTreeMap, // FIXME parameterize? pub nonce: Nonce, - pub expiration: Timestamp, pub not_before: Option, + pub expiration: Timestamp, } -// FIXME move that clone? -impl From<&Payload> for delegation::Payload { - fn from(invocation: &Payload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().into()), - conditions: vec![], - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } +impl Capsule + for Payload +{ + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +impl Serialize for Payload +where + InternalSerializer: From>, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(*self); + Serialize::serialize(&s, serializer) + } } -impl> From> for Ipld { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert( - "aud".into(), - payload - .audience - .map(|audience| audience.to_string()) - .unwrap_or(payload.issuer.to_string()) - .into(), - ); - - map.insert("cmd".into(), B::COMMAND.into()); - map.insert("args".into(), payload.ability.into()); - - map.insert( - "proofs".into(), - Ipld::List( - payload - .proofs - .into_iter() - .map(|cid| cid.into()) - .collect::>(), - ), - ); - - map.insert( - "cause".into(), - payload - .cause - .map(|cid| cid.into()) - .unwrap_or(Ipld::Null), - ); - - map.insert( - "metadata".into(), - Ipld::Map( - payload - .metadata - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>(), - ), - ); - - map.insert("nonce".into(), payload.nonce.into()); - - map.insert("exp".into(), payload.expiration.into()); - - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); +impl<'de, T: Resolvable + Debug> Deserialize<'de> for Payload +where + Payload: TryFrom, + as TryFrom>::Error: Debug, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => s + .try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error } + } +} + +impl TryFrom for Payload +where + Payload: TryFrom, +{ + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + s.try_into().map_err(|_| ()) // FIXME + } +} - map.into() +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() } } -// FIXME TEMPORARY HACK to prove out proof of concept -impl From> for Ipld { - fn from(payload: Payload) -> Self { - let hack_payload = Payload { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct InternalSerializer { + #[serde(rename = "iss")] + pub issuer: Did, + #[serde(rename = "sub")] + pub subject: Did, + #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] + pub audience: Option, + + #[serde(rename = "cmd")] + pub command: String, + #[serde(rename = "args")] + pub arguments: BTreeMap, + + #[serde(rename = "prf")] + pub proofs: Vec, + #[serde(rename = "nonce")] + pub nonce: Nonce, + + #[serde(rename = "cause")] + pub cause: Option, + #[serde(rename = "meta")] + pub metadata: BTreeMap, + + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] + pub not_before: Option, + #[serde(rename = "exp")] + pub expiration: Timestamp, +} + +impl From<&Payload> for InternalSerializer +where + BTreeMap: From, +{ + fn from(payload: &Payload) -> Self { + InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - ability: JsHack(payload.ability.clone()), + + command: T::COMMAND.into(), + arguments: payload.ability.into(), + proofs: payload.proofs, cause: payload.cause, metadata: payload.metadata, + nonce: payload.nonce, - expiration: payload.expiration, + not_before: payload.not_before, - }; - - if let Ipld::Map(mut map) = hack_payload.into() { - map.insert("cmd".into(), payload.ability.cmd.into()); - Ipld::Map(map) - } else { - // FIXME bleh this code - unreachable!() + expiration: payload.expiration, + } + } +} + +impl TryFrom for InternalSerializer { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl From for Payload { + fn from(s: InternalSerializer) -> Self { + Payload { + issuer: s.issuer, + subject: s.subject, + audience: s.audience, + + ability: DynJs { + cmd: s.command, + args: s.arguments, + }, + + proofs: s.proofs, + cause: s.cause, + metadata: s.metadata, + + nonce: s.nonce, + + not_before: s.not_before, + expiration: s.expiration, } } } + +impl TryFrom> for InternalSerializer { + type Error = (); // FIXME + + fn try_from(p: Payload) -> Result { + Ok(InternalSerializer { + issuer: p.issuer, + subject: p.subject, + audience: p.audience, + + command: p.ability.cmd, + arguments: p.ability.args, + + proofs: p.proofs, + cause: p.cause, + metadata: p.metadata, + + nonce: p.nonce, + + not_before: p.not_before, + expiration: p.expiration, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index c827a85f..43a25a86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use serde::{de, Deserialize, Deserializer, Serialize}; pub mod builder; pub mod capability; pub mod crypto; +pub mod did; pub mod did_verifier; pub mod error; pub mod plugins; diff --git a/src/promise.rs b/src/promise.rs index 231be586..55962441 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -3,9 +3,19 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Deferrable +where + T: Debug + Clone + PartialEq, +{ + Resolved(T), + Await(Promise), +} + /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] // FIXME check that this is right, also +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { PromiseAny { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? @@ -22,7 +32,7 @@ pub enum Promise { } impl TryFrom for Promise { - type Error = (); // FIXME + type Error = (); fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld).map_err(|_| ()) diff --git a/src/prove.rs b/src/prove.rs index e6962fbb..e9ba4f2c 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -7,9 +7,9 @@ /// more[...] /// more2[...] /// ``` -pub trait TryProve<'a, T> { +pub trait TryProve { type Error; type Proven; - fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; + fn try_prove(self, candidate: T) -> Result; } diff --git a/src/time.rs b/src/time.rs index b55ce9f9..09be5c5b 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,7 @@ //! Time utilities -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use web_time::{SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH @@ -11,7 +12,8 @@ pub fn now() -> u64 { .as_secs() } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Timestamp { // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) @@ -28,18 +30,19 @@ pub enum Timestamp { impl From for Ipld { fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::Sending(js_time) => js_time.into(), - Timestamp::Receiving(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } + timestamp.into() + } +} + +impl TryFrom for Timestamp { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct JsTime { time: SystemTime, } From c69c7ba8e1a3ab50da1b4645961c442992bf1189 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 23:52:50 -0800 Subject: [PATCH 081/234] Ahead of proven contraint --- src/ability/any.rs | 4 +- src/ability/msg.rs | 16 +-- src/ability/traits.rs | 7 +- src/delegation/payload.rs | 241 +++++++++++++++++++++++++++----------- src/invocation/payload.rs | 24 ++-- src/prove.rs | 2 +- src/receipt/payload.rs | 48 +++++--- 7 files changed, 230 insertions(+), 112 deletions(-) diff --git a/src/ability/any.rs b/src/ability/any.rs index e6ff78ee..91f6b03e 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -6,9 +6,9 @@ pub struct DelegateAny; impl<'a, T> TryProve<&'a DelegateAny> for &'a T { type Error = Infallible; - type Proven = T; + type Proven = &'a T; - fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Infallible> { + fn try_prove(self, _proof: &'a DelegateAny) -> Result { Ok(self) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index dda6eb85..18da8489 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -188,9 +188,9 @@ impl TryFrom for MsgReceive { impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME - type Proven = Msg; + type Proven = &'a Msg; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self == candidate { Ok(self) } else { @@ -201,9 +201,9 @@ impl<'a> TryProve<&'a Msg> for &'a Msg { impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME - type Proven = MsgSend; + type Proven = &'a MsgSend; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -214,9 +214,9 @@ impl<'a> TryProve<&'a Msg> for &'a MsgSend { impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME - type Proven = MsgReceive; + type Proven = &'a MsgReceive; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -228,9 +228,9 @@ impl<'a> TryProve<&'a Msg> for &'a MsgReceive { // FIXME this needs to work on builders! impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME - type Proven = MsgReceive; + type Proven = &'a MsgReceive; - fn try_prove(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a MsgReceive) -> Result { if self == candidate { Ok(self) } else { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 775db6cf..b4e46341 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,4 @@ -use crate::prove::TryProve; +use crate::{did::Did, nonce::Nonce, prove::TryProve}; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -10,16 +10,15 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; - // type Builder: Debug + /*TryProve FIXME */ + TryInto + From; } pub trait Resolvable: Sized { type Awaiting: Debug + TryInto + From; } -// FIXME Delegatable? pub trait Runnable { - type Output; + type Output: Debug; + fn task_id(self, subject: &Did, nonce: &Nonce) -> String; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f5316e8e..94c1b7a2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,19 +1,16 @@ -use super::{condition::Condition, delegate::Delegate}; +use super::condition::Condition; use crate::{ ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, did::Did, nonce::Nonce, - prove::TryProve, time::Timestamp, }; -use did_url::DID; use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; -use web_time::{SystemTime, UNIX_EPOCH}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, @@ -29,88 +26,190 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload +impl Debug for Payload where - T::Builder: serde::Serialize + serde::de::DeserializeOwned, + T::Builder: Debug, { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Payload") + .field("issuer", &self.issuer) + .field("subject", &self.subject) + .field("audience", &self.audience) + .field("ability_builder", &self.ability_builder) + .field("conditions", &self.conditions) + .field("metadata", &self.metadata) + .field("nonce", &self.nonce) + .field("expiration", &self.expiration) + .field("not_before", &self.not_before) + .finish() + } +} + +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -// FIXME -impl TryFrom +impl Serialize for Payload +where + InternalSerializer: From>, + Payload: Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(self.clone()); + Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Delegatable + Debug, C: Condition + DeserializeOwned> Deserialize<'de> + for Payload +where + Payload: TryFrom, + as TryFrom>::Error: Debug, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => s + .try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error + } + } +} + +impl TryFrom for Payload where - T::Builder: serde::Serialize + serde::de::DeserializeOwned, + Payload: TryFrom, { - type Error = (); + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).unwrap() // FIXME + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + s.try_into().map_err(|_| ()) // FIXME } } -// impl + Clone> From> for Ipld { -// fn from(payload: Payload) -> Self { -// // FIXME I bet this clone can be removed by switcing to &DynJs -// if let Ipld::Map(mut map) = payload.clone().into() { -// map.insert("cmd".into(), payload.ability_builder.cmd.into()); -// map.insert("args".into(), payload.ability_builder.args.into()); -// map.into() -// } else { -// panic!("FIXME") -// } -// } -// } - -impl + Clone> From> for Ipld +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct InternalSerializer { + #[serde(rename = "iss")] + pub issuer: Did, + #[serde(rename = "sub")] + pub subject: Did, + #[serde(rename = "aud")] + pub audience: Did, + + #[serde(rename = "can")] + pub command: String, + #[serde(rename = "args")] + pub arguments: BTreeMap, + #[serde(rename = "cond")] + pub conditions: Vec, + + #[serde(rename = "nonce")] + pub nonce: Nonce, + #[serde(rename = "meta")] + pub metadata: BTreeMap, + + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] + pub not_before: Option, + #[serde(rename = "exp")] + pub expiration: Timestamp, +} + +impl> From> + for InternalSerializer where - Ipld: From, + BTreeMap: From, { fn from(payload: Payload) -> Self { - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => T::COMMAND.into(), - }; - - let mut map = BTreeMap::from_iter([ - ("iss".into(), payload.issuer.to_string().into()), - ("sub".into(), payload.subject.to_string().into()), - ("aud".into(), payload.audience.to_string().into()), - ( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => (*builder).clone().into(), // FIXME - }, - ), - ("cmd".into(), can), - ( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ), - ( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ), - ("nonce".into(), payload.nonce.clone().into()), - ("exp".into(), payload.expiration.clone().into()), - ]); - - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); + InternalSerializer { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + + command: T::COMMAND.into(), + arguments: payload.ability_builder.into(), + conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), + + metadata: payload.metadata, + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, } + } +} - map.into() +impl TryFrom for InternalSerializer { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl> TryFrom for Payload { + type Error = (); // FIXME + + fn try_from(s: InternalSerializer) -> Result, ()> { + Ok(Payload { + issuer: s.issuer, + subject: s.subject, + audience: s.audience, + + ability_builder: DynJs { + cmd: s.command, + args: s.arguments, + }, + conditions: s + .conditions + .iter() + .try_fold(Vec::new(), |mut acc, c| { + C::try_from(c.clone()).map(|x| { + acc.push(x); + acc + }) + }) + .map_err(|_| ())?, // FIXME better error (collect all errors + + metadata: s.metadata, + nonce: s.nonce, + + not_before: s.not_before, + expiration: s.expiration, + }) + } +} + +impl> From> for InternalSerializer { + fn from(p: Payload) -> Self { + InternalSerializer { + issuer: p.issuer, + subject: p.subject, + audience: p.audience, + + command: p.ability_builder.cmd, + arguments: p.ability_builder.args, + conditions: p.conditions.into_iter().map(|c| c.into()).collect(), + + metadata: p.metadata, + nonce: p.nonce, + + not_before: p.not_before, + expiration: p.expiration, + } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a8a0966d..a85716f6 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -26,26 +26,25 @@ pub struct Payload { pub expiration: Timestamp, } -impl Capsule - for Payload -{ +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where + Payload: Clone, InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(*self); + let s = InternalSerializer::from(self.clone()); Serialize::serialize(&s, serializer) } } -impl<'de, T: Resolvable + Debug> Deserialize<'de> for Payload +impl<'de, T: Resolvable> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -63,7 +62,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -75,13 +74,14 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] pub issuer: Did, @@ -90,7 +90,7 @@ struct InternalSerializer { #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] pub audience: Option, - #[serde(rename = "cmd")] + #[serde(rename = "do")] pub command: String, #[serde(rename = "args")] pub arguments: BTreeMap, @@ -111,11 +111,11 @@ struct InternalSerializer { pub expiration: Timestamp, } -impl From<&Payload> for InternalSerializer +impl From> for InternalSerializer where BTreeMap: From, { - fn from(payload: &Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, diff --git a/src/prove.rs b/src/prove.rs index e9ba4f2c..65284d16 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -8,8 +8,8 @@ /// more2[...] /// ``` pub trait TryProve { - type Error; type Proven; + type Error; fn try_prove(self, candidate: T) -> Result; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ad8588cf..ca496986 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,20 +1,17 @@ -use crate::{ - ability::traits::{Command, Delegatable, Runnable}, - capsule::Capsule, - nonce::Nonce, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld}; +use crate::{ability::traits::Runnable, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Payload +where + T::Output: Serialize + DeserializeOwned, +{ + pub issuer: Did, pub ran: Cid, - pub out: Result>, + pub out: Result>, pub next: Vec, pub proofs: Vec, @@ -24,6 +21,29 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } + +impl TryFrom for Payload +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl From> for Ipld +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ + fn from(payload: Payload) -> Self { + payload.into() + } +} From f9c712d351f1097a73069cba12c7a0c15c347176 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 00:53:27 -0800 Subject: [PATCH 082/234] Actually on checking code afetr getting stuck with serde --- Cargo.toml | 2 + src/ability/traits.rs | 32 +++++++++++++++- src/delegation/payload.rs | 77 ++++++++++++++++++++++++++------------- src/time.rs | 9 +++++ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb865d2e..a58dba08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,9 @@ jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } +libipld-cbor = "0.16" multibase = "0.9.1" +multihash = {version = "0.19", features = ["serde"]} p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } diff --git a/src/ability/traits.rs b/src/ability/traits.rs index b4e46341..ebc18cf7 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,5 +1,12 @@ use crate::{did::Did, nonce::Nonce, prove::TryProve}; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + ipld::Ipld, + multihash::{Code::Sha2_256, MultihashDigest}, + serde as ipld_serde, +}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -18,7 +25,7 @@ pub trait Resolvable: Sized { pub trait Runnable { type Output: Debug; - fn task_id(self, subject: &Did, nonce: &Nonce) -> String; + fn task_id(self, subject: Did, nonce: Nonce) -> Cid; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -36,6 +43,27 @@ impl Resolvable for DynJs { type Awaiting = Self; } +impl Runnable for DynJs { + type Output = Ipld; + + fn task_id(self, subject: Did, nonce: Nonce) -> Cid { + let ipld: Ipld = BTreeMap::from_iter([ + ("sub".into(), subject.into()), + ("do".into(), self.cmd.clone().into()), + ("args".into(), self.cmd.clone().into()), + ("nonce".into(), nonce.into()), + ]) + .into(); + + let mut encoded = vec![]; + ipld.encode(DagCborCodec, &mut encoded) + .expect("should never fail if `encodable_as` is implemented correctly"); + + let multihash = Sha2_256.digest(encoded.as_slice()); + CidGeneric::new_v1(DagCborCodec.into(), multihash) + } +} + impl From for Ipld { fn from(js: DynJs) -> Self { js.into() diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 94c1b7a2..bed7f0e6 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -4,13 +4,15 @@ use crate::{ capsule::Capsule, did::Did, nonce::Nonce, + prove::TryProve, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; +use web_time::SystemTime; -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, @@ -26,30 +28,11 @@ pub struct Payload { pub not_before: Option, } -impl Debug for Payload -where - T::Builder: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Payload") - .field("issuer", &self.issuer) - .field("subject", &self.subject) - .field("audience", &self.audience) - .field("ability_builder", &self.ability_builder) - .field("conditions", &self.conditions) - .field("metadata", &self.metadata) - .field("nonce", &self.nonce) - .field("expiration", &self.expiration) - .field("not_before", &self.not_before) - .finish() - } -} - impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where InternalSerializer: From>, Payload: Clone, @@ -63,8 +46,7 @@ where } } -impl<'de, T: Delegatable + Debug, C: Condition + DeserializeOwned> Deserialize<'de> - for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -82,8 +64,7 @@ where } } -impl TryFrom - for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -101,6 +82,52 @@ impl From> for Ipld { } } +impl<'a, T: Delegatable, C: Condition> Payload { + fn check( + &'a self, + proof: &'a Payload, + now: SystemTime, + ) -> Result< + // FIXME should return the entrue payload, unless we want to extract the fields + >::Proven, + >::Error, + > + where + T::Builder: TryProve<::Builder>, + { + if self.issuer != proof.audience { + todo!() + // return Err(()); + } + + if self.subject != proof.subject { + todo!() + // return Err(()); + } + + // FIXME that into needs to work on both sides + if let Some(nbf) = self.not_before.clone() { + if SystemTime::from(nbf) > now { + todo!() + // return Err(()); + } + } + + // FIXME that into needs to work on both sides + if SystemTime::from(self.expiration.clone()) > now { + todo!() + // return Err(()); + } + + // FIXME + // if self.conditions != proof.conditions { + // return Err(()); + // } + + self.ability_builder.try_prove(proof.ability_builder) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct InternalSerializer { diff --git a/src/time.rs b/src/time.rs index 09be5c5b..29500062 100644 --- a/src/time.rs +++ b/src/time.rs @@ -28,6 +28,15 @@ pub enum Timestamp { Receiving(SystemTime), } +impl From for SystemTime { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::Sending(js_time) => js_time.time, + Timestamp::Receiving(sys_time) => sys_time, + } + } +} + impl From for Ipld { fn from(timestamp: Timestamp) -> Self { timestamp.into() From 2ff88a1f952499f24c99140a82cadc2ff3bf4520 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 15:08:57 -0800 Subject: [PATCH 083/234] LOL wow the hacks, but honestly these will clean up nicely --- src/ability.rs | 6 +- src/ability/any.rs | 17 ++-- src/ability/crud.rs | 24 ++--- src/ability/dynamic.rs | 6 +- src/ability/msg.rs | 16 +-- src/ability/traits.rs | 4 +- src/condition.rs | 1 + src/delegation/payload.rs | 204 ++++++++++++++++++++++++++++++-------- src/invocation/payload.rs | 32 +++--- src/lib.rs | 2 +- src/prove.rs | 24 ++++- 11 files changed, 242 insertions(+), 94 deletions(-) create mode 100644 src/condition.rs diff --git a/src/ability.rs b/src/ability.rs index eda773d7..c9645a96 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,8 +1,8 @@ pub mod any; -pub mod crud; -pub mod msg; +// pub mod crud; +// pub mod msg; pub mod traits; -pub mod wasm; +// pub mod wasm; // TODO move to crate::wasm? #[cfg(feature = "wasm")] diff --git a/src/ability/any.rs b/src/ability/any.rs index 91f6b03e..5d970cad 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,14 +1,13 @@ -use crate::prove::TryProve; +// use crate::prove::Prove; use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl<'a, T> TryProve<&'a DelegateAny> for &'a T { - type Error = Infallible; - type Proven = &'a T; - - fn try_prove(self, _proof: &'a DelegateAny) -> Result { - Ok(self) - } -} +// impl Prove for DelegateAny { +// type Proven = Self; +// +// fn prove(self, _proof: Self) -> Self::Proven { +// self +// } +// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 5bf15133..6fed603a 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -46,8 +46,8 @@ pub struct CrudDestroy { // impl TryProve for CrudDestroy { // type Error = (); // FIXME // type Proven = CrudDestroy; -// fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -60,8 +60,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudDestroy; // -// fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -73,8 +73,8 @@ pub struct CrudDestroy { // type Error = (); // type Proven = CrudRead; // -// fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudRead) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // // FIXME contains & args // Ok(self) // } else { @@ -87,8 +87,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudRead; // -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -100,8 +100,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudMutate; // -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -115,9 +115,9 @@ pub struct CrudDestroy { // type Proven = C; // // // FIXME -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a C, ()> { // match self.try_prove(&CrudMutate { -// uri: candidate.uri.clone(), +// uri: proof.uri.clone(), // }) { // Ok(_) => Ok(self), // Err(_) => Err(()), diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a456704a..b55a9e62 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -69,10 +69,10 @@ impl<'a> TryProve> for DynamicBuilder<'a> { type Error = JsError; type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that - fn try_prove(&'a self, candidate: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, proof: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { let js_self: JsValue = self.into().into(); - let js_candidate: JsValue = candidate.into().into(); + let js_proof: JsValue = proof.into().into(); - self.validator.apply(js_self, js_candidate); + self.validator.apply(js_self, js_proof); } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 18da8489..8b21f673 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -190,8 +190,8 @@ impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME type Proven = &'a Msg; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self == candidate { + fn try_prove(self, proof: &'a Msg) -> Result { + if self == proof { Ok(self) } else { Err(()) @@ -203,8 +203,8 @@ impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME type Proven = &'a MsgSend; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self.to == candidate.to && self.from == candidate.from { + fn try_prove(self, proof: &'a Msg) -> Result { + if self.to == proof.to && self.from == proof.from { Ok(self) } else { Err(()) @@ -216,8 +216,8 @@ impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME type Proven = &'a MsgReceive; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self.to == candidate.to && self.from == candidate.from { + fn try_prove(self, proof: &'a Msg) -> Result { + if self.to == proof.to && self.from == proof.from { Ok(self) } else { Err(()) @@ -230,8 +230,8 @@ impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME type Proven = &'a MsgReceive; - fn try_prove(self, candidate: &'a MsgReceive) -> Result { - if self == candidate { + fn try_prove(self, proof: &'a MsgReceive) -> Result { + if self == proof { Ok(self) } else { Err(()) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index ebc18cf7..cf909ed7 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -19,8 +19,8 @@ pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; } -pub trait Resolvable: Sized { - type Awaiting: Debug + TryInto + From; +pub trait Resolvable: Delegatable { + type Awaiting: Debug + TryInto + From + Into; } pub trait Runnable { diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1 @@ + diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bed7f0e6..39a8ff89 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -82,78 +82,200 @@ impl From> for Ipld { } } -impl<'a, T: Delegatable, C: Condition> Payload { - fn check( - &'a self, - proof: &'a Payload, +use crate::{ability::traits::Resolvable, invocation::payload as invocation}; + +impl<'a, T: ?Sized + Delegatable + Resolvable + Clone, C: Condition> Payload { + pub fn check( + invoked: invocation::Payload, // FIXME promiroy version + proofs: Vec>, now: SystemTime, - ) -> Result< - // FIXME should return the entrue payload, unless we want to extract the fields - >::Proven, - >::Error, - > + ) -> Result<(), ()> where - T::Builder: TryProve<::Builder>, + Ipld: From + From, + U: TryProve, + U::Builder: Clone + Delegatable, + ::Builder: TryProve + TryProve<::Builder>, + <::Builder as TryProve<::Builder>>::Error: Clone, + Prev: From<::Builder>, + Prev<::Builder>: From<::Builder>, + T: TryProve + TryProve<::Builder> + Clone, + ::Builder: From>, + ::Builder: From<::Builder>, + <::Builder as TryProve>::Error: Clone, + ::Builder: Clone + TryProve + TryProve, + >::Error: Clone, + ::Builder: TryProve< + <::Builder as TryProve<::Builder>>::Proven, + >, + T::Builder: TryProve, { - if self.issuer != proof.audience { - todo!() - // return Err(()); + let builder: T::Builder = invoked.into(); + let start: Prev = Prev { + issuer: invoked.issuer, + subject: invoked.subject, + ability_builder: Box::new(builder), + }; + + let ipld: Ipld = invoked.into(); + + // let result: Result, ()> = proofs.iter().fold(Ok(start), |prev, proof| { + // if let Ok(to_check) = prev { + // // FIXME check conditions against ipldified invoked + // match step(&to_check, &proof, &ipld, now) { + // Err(_) => Err(()), + // Ok(next) => Ok(Prev { + // issuer: proof.issuer, + // subject: proof.subject, + // ability_builder: Box::new(next), + // }), + // } + // } else { + // prev + // } + // }); + // + // match result { + // Ok(_) => Ok(()), + // Err(_) => Err(()), + // } + todo!() + } +} + +enum Either { + Left(Box), + Right(Box), +} + +// FIXME "CanProve" +trait ProofHack { + fn try_prove1(&self, proof: U) -> Result, ()>; +} + +impl ProofHack for T +where + T: TryProve, +{ + fn try_prove1(&self, proof: U) -> Result, ()> { + match self.try_prove(proof) { + Ok(_) => Ok(Either::Left(Box::new(()))), + Err(_) => Ok(Either::Right(Box::new(proof))), } + } +} - if self.subject != proof.subject { - todo!() - // return Err(()); +struct Prev { + issuer: Did, + subject: Did, + ability_builder: Box>, +} + +impl From> for Prev +where + T::Builder: ProofHack, +{ + fn from(invoked: invocation::Payload) -> Self { + Prev { + issuer: invoked.issuer, + subject: invoked.subject, + ability_builder: Box::new(invoked.ability.into()), } + } +} - // FIXME that into needs to work on both sides - if let Some(nbf) = self.not_before.clone() { - if SystemTime::from(nbf) > now { - todo!() - // return Err(()); - } +impl From> for Prev +where + T::Builder: ProofHack, +{ + fn from(delegation: Payload) -> Self { + Prev { + issuer: delegation.issuer, + subject: delegation.subject, + ability_builder: Box::new(delegation.ability_builder), } + } +} + +// FIXME this needs to move to Delegatable +fn step<'a, T, U: Delegatable, C: Condition>( + prev: &'a Prev, + proof: &'a Payload, + invoked_ipld: &'a Ipld, + now: SystemTime, +) -> () +// FIXME +where + T: TryProve<::Builder> + Clone, + U::Builder: Clone, + Ipld: From, + >::Error: Clone, +{ + if prev.issuer != proof.audience { + todo!() + } - // FIXME that into needs to work on both sides - if SystemTime::from(self.expiration.clone()) > now { + if prev.subject != proof.subject { + todo!() + } + + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > now { todo!() - // return Err(()); } + } - // FIXME - // if self.conditions != proof.conditions { - // return Err(()); - // } - - self.ability_builder.try_prove(proof.ability_builder) + if SystemTime::from(proof.expiration.clone()) > now { + todo!() } + + // FIXME check the spec + // if self.conditions != proof.conditions { + // return Err(()); + // } + + proof + .conditions + .iter() + .try_fold((), |_acc, c| { + if c.validate(&invoked_ipld) { + Ok(()) + } else { + Err(()) + } + }) + .expect("FIXME"); + + Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + + () } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - pub issuer: Did, + issuer: Did, #[serde(rename = "sub")] - pub subject: Did, + subject: Did, #[serde(rename = "aud")] - pub audience: Did, + audience: Did, #[serde(rename = "can")] - pub command: String, + command: String, #[serde(rename = "args")] - pub arguments: BTreeMap, + arguments: BTreeMap, #[serde(rename = "cond")] - pub conditions: Vec, + conditions: Vec, #[serde(rename = "nonce")] - pub nonce: Nonce, + nonce: Nonce, #[serde(rename = "meta")] - pub metadata: BTreeMap, + metadata: BTreeMap, #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - pub not_before: Option, + not_before: Option, #[serde(rename = "exp")] - pub expiration: Timestamp, + expiration: Timestamp, } impl> From> diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a85716f6..e19e7b91 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -84,45 +84,53 @@ impl From> for Ipld { #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - pub issuer: Did, + issuer: Did, #[serde(rename = "sub")] - pub subject: Did, + subject: Did, #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - pub audience: Option, + audience: Option, #[serde(rename = "do")] - pub command: String, + command: String, #[serde(rename = "args")] - pub arguments: BTreeMap, + arguments: BTreeMap, #[serde(rename = "prf")] - pub proofs: Vec, + proofs: Vec, #[serde(rename = "nonce")] - pub nonce: Nonce, + nonce: Nonce, #[serde(rename = "cause")] - pub cause: Option, + cause: Option, #[serde(rename = "meta")] - pub metadata: BTreeMap, + metadata: BTreeMap, #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - pub not_before: Option, + not_before: Option, #[serde(rename = "exp")] - pub expiration: Timestamp, + expiration: Timestamp, } impl From> for InternalSerializer where BTreeMap: From, + Ipld: From, { fn from(payload: Payload) -> Self { + let bar: T::Awaiting = payload.ability; + let foo: Ipld = Ipld::from(payload.ability); + let arguments: BTreeMap = match foo { + Ipld::Map(btree) => btree, + _ => panic!("FIXME"), + }; + InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, command: T::COMMAND.into(), - arguments: payload.ability.into(), + arguments, proofs: payload.proofs, cause: payload.cause, diff --git a/src/lib.rs b/src/lib.rs index 43a25a86..fae01988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ use std::fmt::Debug; // type Error = (); // type Proven = DynamicJs; // -// fn try_prove<'a>(&'a self, candidate: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// fn try_prove<'a>(&'a self, proof: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { // // } // } diff --git a/src/prove.rs b/src/prove.rs index 65284d16..b6ddc45d 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -1,15 +1,33 @@ +use std::convert::Infallible; + #[cfg_attr(doc, aquamarine::aquamarine)] /// FIXME /// /// ```mermaid /// flowchart LR -/// Invocation --> more --> Self --> Candidate --> more2 +/// Invocation --> more --> Self --> Proof --> more2 /// more[...] /// more2[...] /// ``` -pub trait TryProve { +pub trait TryProve { type Proven; type Error; - fn try_prove(self, candidate: T) -> Result; + // FIXME rename to proof? + fn try_prove(&self, proof: T) -> Result; } + +// pub trait Prove { +// type Proven; +// +// fn prove(self, proof: T) -> Self::Proven; +// } +// +// impl + ?Sized, U> TryProve for T { +// type Proven = T::Proven; +// type Error = Infallible; +// +// fn try_prove(&self, proof: U) -> Result { +// Ok(self.prove(proof)) +// } +// } From 7bfc238378a640f67779ebbe408884cebd03bc6f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 23:52:46 -0800 Subject: [PATCH 084/234] Promising direction! --- src/ability/traits.rs | 263 ++++++++++++++++++++++++++++++++++++++ src/delegation/payload.rs | 131 +++++++++---------- src/invocation/payload.rs | 4 +- 3 files changed, 324 insertions(+), 74 deletions(-) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index cf909ed7..58478d2c 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -77,3 +77,266 @@ impl TryFrom for DynJs { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } + +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// + +// trait IntoCheckable { +// type Checkable; +// fn to_checkable(&self) -> Self::Checkable; +// } +// +// trait Checkable { +// type Checker; +// fn check(me: &Self::Checker, other: &Self::Checker) -> bool; +// } + +struct CrudAny; +struct CrudMutate; +struct CrudCreate; +struct CrudUpdate; +struct CrudDestroy; + +enum Either { + Left(Box), + Right(Box), +} + +enum Wrapper<'a, T: ?Sized, P> { + Any, + Parents(&'a P), + Me(&'a T), +} + +// NOTE must remain unexported! +trait IsChecker {} + +impl IsChecker for Parentful {} +impl IsChecker for Parentless {} + +pub trait HasChecker { + type CheckAs: IsChecker; +} + +trait CheckSelf { + fn check_against_self(&self, other: &Self) -> bool; +} + +impl HasChecker for CrudCreate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudCreate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl CheckSelf for CrudUpdate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudUpdate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudDestroy { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudDestroy { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudMutate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudMutate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudAny { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudAny { + type CheckAs = Parentless; +} + +pub enum Parentful { + Any, + Parents(T::Parents), + Me(T), +} + +pub enum Parentless { + Any, + Me(T), +} + +pub trait CheckParents: CheckSelf { + type Parents; + + fn check_against_parents(&self, other: &Self::Parents) -> bool; +} + +pub trait JustCheck { + fn check<'a>(&'a self, other: &'a T) -> bool; +} + +impl JustCheck> for T { + fn check<'a>(&'a self, other: &'a Parentless) -> bool { + match other { + Parentless::Any => true, + Parentless::Me(me) => self.check_against_self(&me), + } + } +} + +impl JustCheck> for T { + fn check<'a>(&'a self, other: &'a Parentful) -> bool { + match other { + Parentful::Any => true, + Parentful::Parents(parents) => self.check_against_parents(parents), + Parentful::Me(me) => self.check_against_self(&me), + } + } +} + +// trait JustCheckNormalized {} +// +// impl JustCheckNormalized for T {} + +// impl JustCheck for T { +// type ToCheck = Parentful; +// } +// +// trait EvenMoreCheck: JustCheck { +// fn check<'a>(&'a self, other: Self::ToCheck) -> bool; +// } +// +// impl>> EvenMoreCheck for T { +// fn check<'a>(&'a self, other: Parentless) -> bool { +// match other { +// Parentless::Any => true, +// Parentless::Me(me) => self.check_against_self(&me), +// } +// } +// } +// +// impl>> EvenMoreCheck for T { +// fn check<'a>(&'a self, other: Parentless) -> bool { +// match other { +// Parentful::Any => true, +// Parentful::Parents(parents) => self.check_against_parents(parents), +// Parentful::Me(me) => self.check_against_self(&me), +// } +// } +// } + +// impl JustCheck for T {} + +// impl Parentless for CrudAny {} + +// TODO note to self, this is effectively a partial order +impl CheckParents for CrudMutate { + type Parents = CrudAny; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + true + } +} + +impl CheckSelf for Either { + fn check_against_self(&self, other: &Self) -> bool { + match self { + Either::Left(mutate) => match other { + Either::Left(other_mutate) => mutate.check_against_self(other_mutate), + Either::Right(any) => mutate.check_against_parents(any), + }, + _ => false, + } + } +} + +impl CheckParents for CrudCreate { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +impl CheckParents for CrudUpdate { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +impl CheckParents for CrudDestroy { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +trait IntoParentsFor { + fn into_parents(self) -> T::Parents; +} + +// impl IntoParentsFor for CrudAny { +// fn into_parents(self) -> Either { +// todo!() +// } +// } + +enum Void {} + +// impl SuperParentalChecker for CrudAny { +// type SuperParents = Void; +// +// fn check_against_self(self, other: Self) -> bool { +// self == other +// } +// +// fn check_against_parents(self, other: Self::SuperParents) -> bool { +// match other { +// Either::Left(create) => self == create, +// Either::Right(update) => self == update, +// } +// } +// } + +enum CrudChecker { + Create, + Read, + Update, + Delete, +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 39a8ff89..f2a8c68c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,6 @@ use super::condition::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs}, + ability::traits::{Command, Delegatable, DynJs, HasChecker, JustCheck}, capsule::Capsule, did::Did, nonce::Nonce, @@ -84,36 +84,21 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: ?Sized + Delegatable + Resolvable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Clone, C: Condition> Payload { pub fn check( - invoked: invocation::Payload, // FIXME promiroy version + invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - Ipld: From + From, - U: TryProve, - U::Builder: Clone + Delegatable, - ::Builder: TryProve + TryProve<::Builder>, - <::Builder as TryProve<::Builder>>::Error: Clone, - Prev: From<::Builder>, - Prev<::Builder>: From<::Builder>, - T: TryProve + TryProve<::Builder> + Clone, + invocation::Payload: Clone, ::Builder: From>, - ::Builder: From<::Builder>, - <::Builder as TryProve>::Error: Clone, - ::Builder: Clone + TryProve + TryProve, - >::Error: Clone, - ::Builder: TryProve< - <::Builder as TryProve<::Builder>>::Proven, - >, - T::Builder: TryProve, { - let builder: T::Builder = invoked.into(); - let start: Prev = Prev { - issuer: invoked.issuer, - subject: invoked.subject, - ability_builder: Box::new(builder), + let builder: T::Builder = invoked.clone().into(); + let start: Acc = Acc { + issuer: invoked.issuer.clone(), + subject: invoked.subject.clone(), + check_chain: builder, }; let ipld: Ipld = invoked.into(); @@ -152,63 +137,66 @@ trait ProofHack { fn try_prove1(&self, proof: U) -> Result, ()>; } -impl ProofHack for T -where - T: TryProve, -{ - fn try_prove1(&self, proof: U) -> Result, ()> { - match self.try_prove(proof) { - Ok(_) => Ok(Either::Left(Box::new(()))), - Err(_) => Ok(Either::Right(Box::new(proof))), - } - } -} - -struct Prev { +// impl ProofHack for T +// where +// T: TryProve, +// { +// fn try_prove1(&self, proof: U) -> Result, ()> { +// match self.try_prove(proof) { +// Ok(_) => Ok(Either::Left(Box::new(()))), +// Err(_) => Ok(Either::Right(Box::new(proof))), +// } +// } +// } + +// struct Prev { +// issuer: Did, +// subject: Did, +// ability_builder: Box>, +// } + +// impl From> for Prev +// where +// T::Builder: ProofHack, +// { +// fn from(invoked: invocation::Payload) -> Self { +// Prev { +// issuer: invoked.issuer, +// subject: invoked.subject, +// ability_builder: Box::new(invoked.ability.into()), +// } +// } +// } + +// impl From> for Prev +// where +// T::Builder: ProofHack, +// { +// fn from(delegation: Payload) -> Self { +// Prev { +// issuer: delegation.issuer, +// subject: delegation.subject, +// ability_builder: Box::new(delegation.ability_builder), +// } +// } +// } + +struct Acc { issuer: Did, subject: Did, - ability_builder: Box>, -} - -impl From> for Prev -where - T::Builder: ProofHack, -{ - fn from(invoked: invocation::Payload) -> Self { - Prev { - issuer: invoked.issuer, - subject: invoked.subject, - ability_builder: Box::new(invoked.ability.into()), - } - } -} - -impl From> for Prev -where - T::Builder: ProofHack, -{ - fn from(delegation: Payload) -> Self { - Prev { - issuer: delegation.issuer, - subject: delegation.subject, - ability_builder: Box::new(delegation.ability_builder), - } - } + check_chain: T, } // FIXME this needs to move to Delegatable -fn step<'a, T, U: Delegatable, C: Condition>( - prev: &'a Prev, +fn step<'a, T: JustCheck, U: Delegatable, C: Condition>( + prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, ) -> () // FIXME where - T: TryProve<::Builder> + Clone, - U::Builder: Clone, - Ipld: From, - >::Error: Clone, + U::Builder: HasChecker, { if prev.issuer != proof.audience { todo!() @@ -245,7 +233,8 @@ where }) .expect("FIXME"); - Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + // Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + JustCheck::check(&prev.check_chain, &proof.ability_builder); () } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index e19e7b91..d2f0ba2c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -117,9 +117,7 @@ where Ipld: From, { fn from(payload: Payload) -> Self { - let bar: T::Awaiting = payload.ability; - let foo: Ipld = Ipld::from(payload.ability); - let arguments: BTreeMap = match foo { + let arguments: BTreeMap = match Ipld::from(payload.ability) { Ipld::Map(btree) => btree, _ => panic!("FIXME"), }; From f0a5bf4a03808f89107e5a264be56a88e95f8813 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 00:53:29 -0800 Subject: [PATCH 085/234] Making headway! Still not 1000% convinced about the parenful stuff due to the indirection (vs a Void type), but hey it's not terrible! --- src/ability/traits.rs | 49 ++++++++++++++++ src/delegation/payload.rs | 115 +++++++++----------------------------- 2 files changed, 76 insertions(+), 88 deletions(-) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 58478d2c..244fa091 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -184,11 +184,60 @@ pub enum Parentful { Me(T), } +impl CheckSelf for Parentful +where + T::Parents: CheckSelf, +{ + fn check_against_self(&self, other: &Self) -> bool { + match self { + Parentful::Any => true, + Parentful::Parents(parents) => match other { + Parentful::Any => true, + Parentful::Parents(other_parents) => parents.check_against_self(other_parents), + Parentful::Me(_other_me) => false, + }, + Parentful::Me(me) => match other { + Parentful::Any => true, + Parentful::Parents(_other_parents) => false, + Parentful::Me(other_me) => me.check_against_self(other_me), + }, + } + } +} + +impl CheckParents for Parentful +where + Parentful: CheckSelf, + T::Parents: CheckSelf, +{ + type Parents = T::Parents; + + fn check_against_parents(&self, other: &T::Parents) -> bool { + match self { + Parentful::Any => true, + Parentful::Parents(parents) => parents.check_against_self(other), + Parentful::Me(me) => me.check_against_parents(other), + } + } +} + pub enum Parentless { Any, Me(T), } +impl CheckSelf for Parentless { + fn check_against_self(&self, other: &Self) -> bool { + match self { + Parentless::Any => true, + Parentless::Me(me) => me.check_against_self(match other { + Parentless::Any => return true, + Parentless::Me(other) => other, + }), + } + } +} + pub trait CheckParents: CheckSelf { type Parents; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f2a8c68c..4276d083 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -84,119 +84,59 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: Delegatable + Resolvable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where + // FIXME so so so broken invocation::Payload: Clone, - ::Builder: From>, + T::CheckAs: From> + From + JustCheck, + U::Builder: Clone, { - let builder: T::Builder = invoked.clone().into(); - let start: Acc = Acc { + let check_chain: T::CheckAs = invoked.clone().into(); + let start: Acc = Acc { issuer: invoked.issuer.clone(), subject: invoked.subject.clone(), - check_chain: builder, + check_chain, }; let ipld: Ipld = invoked.into(); - // let result: Result, ()> = proofs.iter().fold(Ok(start), |prev, proof| { - // if let Ok(to_check) = prev { - // // FIXME check conditions against ipldified invoked - // match step(&to_check, &proof, &ipld, now) { - // Err(_) => Err(()), - // Ok(next) => Ok(Prev { - // issuer: proof.issuer, - // subject: proof.subject, - // ability_builder: Box::new(next), - // }), - // } - // } else { - // prev - // } - // }); - // - // match result { - // Ok(_) => Ok(()), - // Err(_) => Err(()), - // } + let result = proofs.iter().fold(Ok(&start), |prev, proof| { + if let Ok(to_check) = prev { + match step1(&to_check, proof, &ipld, now) { + Err(_) => Err(()), + Ok(next) => Ok(next), + } + } else { + prev + } + }); + todo!() } } -enum Either { - Left(Box), - Right(Box), -} - -// FIXME "CanProve" -trait ProofHack { - fn try_prove1(&self, proof: U) -> Result, ()>; -} - -// impl ProofHack for T -// where -// T: TryProve, -// { -// fn try_prove1(&self, proof: U) -> Result, ()> { -// match self.try_prove(proof) { -// Ok(_) => Ok(Either::Left(Box::new(()))), -// Err(_) => Ok(Either::Right(Box::new(proof))), -// } -// } -// } - -// struct Prev { -// issuer: Did, -// subject: Did, -// ability_builder: Box>, -// } - -// impl From> for Prev -// where -// T::Builder: ProofHack, -// { -// fn from(invoked: invocation::Payload) -> Self { -// Prev { -// issuer: invoked.issuer, -// subject: invoked.subject, -// ability_builder: Box::new(invoked.ability.into()), -// } -// } -// } - -// impl From> for Prev -// where -// T::Builder: ProofHack, -// { -// fn from(delegation: Payload) -> Self { -// Prev { -// issuer: delegation.issuer, -// subject: delegation.subject, -// ability_builder: Box::new(delegation.ability_builder), -// } -// } -// } - -struct Acc { +#[derive(Clone)] +struct Acc { issuer: Did, subject: Did, - check_chain: T, + check_chain: T::CheckAs, } // FIXME this needs to move to Delegatable -fn step<'a, T: JustCheck, U: Delegatable, C: Condition>( +fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, -) -> () -// FIXME +) -> Result<&'a Acc, ()> where - U::Builder: HasChecker, + T::CheckAs: From + JustCheck, + U::Builder: Clone, { if prev.issuer != proof.audience { todo!() @@ -233,10 +173,9 @@ where }) .expect("FIXME"); - // Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned - JustCheck::check(&prev.check_chain, &proof.ability_builder); + JustCheck::check(&prev.check_chain, &proof.ability_builder.clone().into()); - () + todo!() } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] From 66502a8686be092ba3d9bf755f1bce7d63a6abef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 11:57:53 -0800 Subject: [PATCH 086/234] Much improved proof checking --- src/ability/crud.rs | 147 +++++++++++++++++- src/ability/traits.rs | 314 +------------------------------------- src/delegation/payload.rs | 10 +- src/prove.rs | 46 ++---- src/prove/internal.rs | 2 + src/prove/parentful.rs | 92 +++++++++++ src/prove/parentless.rs | 35 +++++ src/prove/traits.rs | 31 ++++ 8 files changed, 326 insertions(+), 351 deletions(-) create mode 100644 src/prove/internal.rs create mode 100644 src/prove/parentful.rs create mode 100644 src/prove/parentless.rs create mode 100644 src/prove/traits.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 6fed603a..16d171b3 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,4 +1,8 @@ -use crate::{promise::Promise, prove::TryProve}; +use crate::{ + ability::traits::{CheckParents, CheckSelf, HasChecker}, + promise::Promise, + prove::TryProve, +}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; @@ -33,6 +37,147 @@ pub struct CrudDestroy { pub uri: Url, } +pub struct CrudAny; +pub struct CrudMutate; +pub struct CrudCreate; +pub struct CrudUpdate; +pub struct CrudDestroy; +pub struct CrudRead; + +pub enum CrudParents { + MutableParent(CrudMutate), + AnyParent(CrudAny), +} + +impl HasChecker for CrudCreate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudCreate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckSelf for CrudUpdate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudUpdate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudDestroy { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudDestroy { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudMutate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudMutate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudAny { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudAny { + type CheckAs = Parentless; +} + +// TODO note to self, this is effectively a partial order +impl CheckParents for CrudMutate { + type Parents = CrudAny; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} + +impl CheckSelf for CrudParents { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + CrudParents::MutableParent(mutate) => match other { + CrudParents::MutableParent(other_mutate) => mutate.check_against_self(other_mutate), + CrudParents::AnyParent(any) => mutate.check_against_parents(any), + }, + _ => Err(()), + } + } +} + +impl CheckParents for CrudCreate { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckParents for CrudUpdate { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckParents for CrudDestroy { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckSelf for CrudRead { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for CrudRead { + type Parents = CrudAny; + type ParentError = (); + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} + // FIXME these should probably be behind a feature flag // impl Capabilty for CrudRead{ diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 244fa091..24ec772a 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,4 @@ -use crate::{did::Did, nonce::Nonce, prove::TryProve}; +use crate::{did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -77,315 +77,3 @@ impl TryFrom for DynJs { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } - -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// - -// trait IntoCheckable { -// type Checkable; -// fn to_checkable(&self) -> Self::Checkable; -// } -// -// trait Checkable { -// type Checker; -// fn check(me: &Self::Checker, other: &Self::Checker) -> bool; -// } - -struct CrudAny; -struct CrudMutate; -struct CrudCreate; -struct CrudUpdate; -struct CrudDestroy; - -enum Either { - Left(Box), - Right(Box), -} - -enum Wrapper<'a, T: ?Sized, P> { - Any, - Parents(&'a P), - Me(&'a T), -} - -// NOTE must remain unexported! -trait IsChecker {} - -impl IsChecker for Parentful {} -impl IsChecker for Parentless {} - -pub trait HasChecker { - type CheckAs: IsChecker; -} - -trait CheckSelf { - fn check_against_self(&self, other: &Self) -> bool; -} - -impl HasChecker for CrudCreate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudCreate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl CheckSelf for CrudUpdate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudUpdate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudDestroy { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudDestroy { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudMutate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudMutate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudAny { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudAny { - type CheckAs = Parentless; -} - -pub enum Parentful { - Any, - Parents(T::Parents), - Me(T), -} - -impl CheckSelf for Parentful -where - T::Parents: CheckSelf, -{ - fn check_against_self(&self, other: &Self) -> bool { - match self { - Parentful::Any => true, - Parentful::Parents(parents) => match other { - Parentful::Any => true, - Parentful::Parents(other_parents) => parents.check_against_self(other_parents), - Parentful::Me(_other_me) => false, - }, - Parentful::Me(me) => match other { - Parentful::Any => true, - Parentful::Parents(_other_parents) => false, - Parentful::Me(other_me) => me.check_against_self(other_me), - }, - } - } -} - -impl CheckParents for Parentful -where - Parentful: CheckSelf, - T::Parents: CheckSelf, -{ - type Parents = T::Parents; - - fn check_against_parents(&self, other: &T::Parents) -> bool { - match self { - Parentful::Any => true, - Parentful::Parents(parents) => parents.check_against_self(other), - Parentful::Me(me) => me.check_against_parents(other), - } - } -} - -pub enum Parentless { - Any, - Me(T), -} - -impl CheckSelf for Parentless { - fn check_against_self(&self, other: &Self) -> bool { - match self { - Parentless::Any => true, - Parentless::Me(me) => me.check_against_self(match other { - Parentless::Any => return true, - Parentless::Me(other) => other, - }), - } - } -} - -pub trait CheckParents: CheckSelf { - type Parents; - - fn check_against_parents(&self, other: &Self::Parents) -> bool; -} - -pub trait JustCheck { - fn check<'a>(&'a self, other: &'a T) -> bool; -} - -impl JustCheck> for T { - fn check<'a>(&'a self, other: &'a Parentless) -> bool { - match other { - Parentless::Any => true, - Parentless::Me(me) => self.check_against_self(&me), - } - } -} - -impl JustCheck> for T { - fn check<'a>(&'a self, other: &'a Parentful) -> bool { - match other { - Parentful::Any => true, - Parentful::Parents(parents) => self.check_against_parents(parents), - Parentful::Me(me) => self.check_against_self(&me), - } - } -} - -// trait JustCheckNormalized {} -// -// impl JustCheckNormalized for T {} - -// impl JustCheck for T { -// type ToCheck = Parentful; -// } -// -// trait EvenMoreCheck: JustCheck { -// fn check<'a>(&'a self, other: Self::ToCheck) -> bool; -// } -// -// impl>> EvenMoreCheck for T { -// fn check<'a>(&'a self, other: Parentless) -> bool { -// match other { -// Parentless::Any => true, -// Parentless::Me(me) => self.check_against_self(&me), -// } -// } -// } -// -// impl>> EvenMoreCheck for T { -// fn check<'a>(&'a self, other: Parentless) -> bool { -// match other { -// Parentful::Any => true, -// Parentful::Parents(parents) => self.check_against_parents(parents), -// Parentful::Me(me) => self.check_against_self(&me), -// } -// } -// } - -// impl JustCheck for T {} - -// impl Parentless for CrudAny {} - -// TODO note to self, this is effectively a partial order -impl CheckParents for CrudMutate { - type Parents = CrudAny; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - true - } -} - -impl CheckSelf for Either { - fn check_against_self(&self, other: &Self) -> bool { - match self { - Either::Left(mutate) => match other { - Either::Left(other_mutate) => mutate.check_against_self(other_mutate), - Either::Right(any) => mutate.check_against_parents(any), - }, - _ => false, - } - } -} - -impl CheckParents for CrudCreate { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -impl CheckParents for CrudUpdate { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -impl CheckParents for CrudDestroy { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -trait IntoParentsFor { - fn into_parents(self) -> T::Parents; -} - -// impl IntoParentsFor for CrudAny { -// fn into_parents(self) -> Either { -// todo!() -// } -// } - -enum Void {} - -// impl SuperParentalChecker for CrudAny { -// type SuperParents = Void; -// -// fn check_against_self(self, other: Self) -> bool { -// self == other -// } -// -// fn check_against_parents(self, other: Self::SuperParents) -> bool { -// match other { -// Either::Left(create) => self == create, -// Either::Right(update) => self == update, -// } -// } -// } - -enum CrudChecker { - Create, - Read, - Update, - Delete, -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4276d083..cad89185 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,10 @@ use super::condition::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs, HasChecker, JustCheck}, + ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, did::Did, nonce::Nonce, - prove::TryProve, + prove::traits::{HasChecker, Prove}, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -93,7 +93,7 @@ impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload where // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + JustCheck, + T::CheckAs: From> + From + Prove, U::Builder: Clone, { let check_chain: T::CheckAs = invoked.clone().into(); @@ -135,7 +135,7 @@ fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( now: SystemTime, ) -> Result<&'a Acc, ()> where - T::CheckAs: From + JustCheck, + T::CheckAs: From + Prove, U::Builder: Clone, { if prev.issuer != proof.audience { @@ -173,7 +173,7 @@ where }) .expect("FIXME"); - JustCheck::check(&prev.check_chain, &proof.ability_builder.clone().into()); + Prove::check(&prev.check_chain, &proof.ability_builder.clone().into()); todo!() } diff --git a/src/prove.rs b/src/prove.rs index b6ddc45d..e4b8122b 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -1,33 +1,15 @@ -use std::convert::Infallible; +// NOTE must remain *un*exported! +pub(super) mod internal; +pub mod parentful; +pub mod parentless; +pub mod traits; -#[cfg_attr(doc, aquamarine::aquamarine)] -/// FIXME -/// -/// ```mermaid -/// flowchart LR -/// Invocation --> more --> Self --> Proof --> more2 -/// more[...] -/// more2[...] -/// ``` -pub trait TryProve { - type Proven; - type Error; - - // FIXME rename to proof? - fn try_prove(&self, proof: T) -> Result; -} - -// pub trait Prove { -// type Proven; -// -// fn prove(self, proof: T) -> Self::Proven; -// } -// -// impl + ?Sized, U> TryProve for T { -// type Proven = T::Proven; -// type Error = Infallible; -// -// fn try_prove(&self, proof: U) -> Result { -// Ok(self.prove(proof)) -// } -// } +// #[cfg_attr(doc, aquamarine::aquamarine)] +// /// FIXME +// /// +// /// ```mermaid +// /// flowchart LR +// /// Invocation --> more --> Self --> Proof --> more2 +// /// more[...] +// /// more2[...] +// /// ``` diff --git a/src/prove/internal.rs b/src/prove/internal.rs new file mode 100644 index 00000000..8963d51e --- /dev/null +++ b/src/prove/internal.rs @@ -0,0 +1,2 @@ +// FIXME rename +pub trait IsChecker {} diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs new file mode 100644 index 00000000..a883d4a9 --- /dev/null +++ b/src/prove/parentful.rs @@ -0,0 +1,92 @@ +use super::{ + internal::IsChecker, + traits::{CheckParents, CheckSelf, Prove}, +}; + +pub enum Parentful { + Any, + Parents(T::Parents), + Me(T), +} + +// TODO better names & derivations +pub enum ParentfulError +where + T::Parents: CheckSelf, +{ + ParentError(T::ParentError), + ParentSelfError(<::Parents as CheckSelf>::SelfError), + SelfError(::SelfError), + + // Compared self to parents + EscelationError, +} + +impl IsChecker for Parentful {} + +impl CheckSelf for Parentful +where + T::Parents: CheckSelf, +{ + type SelfError = ParentfulError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => match other { + Parentful::Any => Ok(()), + Parentful::Parents(other_parents) => parents + .check_against_self(other_parents) + .map_err(ParentfulError::ParentSelfError), + Parentful::Me(_other_me) => Err(ParentfulError::EscelationError), + }, + Parentful::Me(me) => match other { + Parentful::Any => Ok(()), + Parentful::Parents(other_parents) => me + .check_against_parents(other_parents) + .map_err(ParentfulError::ParentError), + Parentful::Me(other_me) => me + .check_against_self(other_me) + .map_err(ParentfulError::SelfError), + }, + } + } +} + +impl CheckParents for Parentful +where + Parentful: CheckSelf, + T::Parents: CheckSelf, +{ + type Parents = T::Parents; + type ParentError = ParentfulError; + + fn check_against_parents(&self, other: &T::Parents) -> Result<(), Self::ParentError> { + // FIXME note to self: see if you can extract the parentful stuff out into the to level Prove + match self { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), + Parentful::Me(me) => me + .check_against_parents(other) + .map_err(ParentfulError::ParentError), + } + } +} + +impl Prove> for T +where + T::Parents: CheckSelf, +{ + type ProveError = ParentfulError; + fn check<'a>(&'a self, other: &'a Parentful) -> Result<(), Self::ProveError> { + match other { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => self + .check_against_parents(parents) + .map_err(ParentfulError::ParentError), + Parentful::Me(me) => self + .check_against_self(&me) + .map_err(ParentfulError::SelfError), + } + } +} diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs new file mode 100644 index 00000000..9814bf1e --- /dev/null +++ b/src/prove/parentless.rs @@ -0,0 +1,35 @@ +use super::{ + internal::IsChecker, + traits::{CheckSelf, Prove}, +}; + +pub enum Parentless { + Any, + Me(T), +} + +impl IsChecker for Parentless {} + +impl CheckSelf for Parentless { + type SelfError = T::SelfError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! + Parentless::Me(me) => match other { + Parentless::Any => Ok(()), + Parentless::Me(other) => me.check_against_self(other), + }, + } + } +} + +impl Prove> for T { + type ProveError = T::SelfError; + fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::SelfError> { + match other { + Parentless::Any => Ok(()), + Parentless::Me(me) => self.check_against_self(&me), + } + } +} diff --git a/src/prove/traits.rs b/src/prove/traits.rs new file mode 100644 index 00000000..bab927aa --- /dev/null +++ b/src/prove/traits.rs @@ -0,0 +1,31 @@ +use super::internal::IsChecker; + +pub trait CheckSelf { + type SelfError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError>; +} + +pub trait CheckParents: CheckSelf { + type Parents; + type ParentError; + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; +} + +pub trait HasChecker { + type CheckAs: IsChecker; +} + +pub trait Prove { + type ProveError; + fn check<'a>(&'a self, other: &'a T) -> Result<(), Self::ProveError>; +} + +// FIXME needed? +pub trait IntoParent { + fn as_parent(self) -> T::Parents; +} + +// Nightly only... sadness +// trait Foo = HasChecker + Prove; From 831695d48b0b5fd826f53fa36fee646d564e0753 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 13:07:36 -0800 Subject: [PATCH 087/234] Implement CRUD heirarchy to test it out -- seems fine --- src/ability.rs | 7 +- src/ability/any.rs | 13 -- src/ability/crud.rs | 275 +----------------------------------- src/ability/crud/any.rs | 20 +++ src/ability/crud/create.rs | 46 ++++++ src/ability/crud/destroy.rs | 39 +++++ src/ability/crud/mutate.rs | 32 +++++ src/ability/crud/parents.rs | 20 +++ src/ability/crud/read.rs | 38 +++++ src/ability/crud/update.rs | 43 ++++++ src/prove/traits.rs | 2 +- 11 files changed, 250 insertions(+), 285 deletions(-) delete mode 100644 src/ability/any.rs create mode 100644 src/ability/crud/any.rs create mode 100644 src/ability/crud/create.rs create mode 100644 src/ability/crud/destroy.rs create mode 100644 src/ability/crud/mutate.rs create mode 100644 src/ability/crud/parents.rs create mode 100644 src/ability/crud/read.rs create mode 100644 src/ability/crud/update.rs diff --git a/src/ability.rs b/src/ability.rs index c9645a96..ac1d449c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,9 +1,10 @@ -pub mod any; -// pub mod crud; -// pub mod msg; pub mod traits; // pub mod wasm; +// FIXME feature flag each? +pub mod crud; +// pub mod msg; + // TODO move to crate::wasm? #[cfg(feature = "wasm")] pub mod dynamic; diff --git a/src/ability/any.rs b/src/ability/any.rs deleted file mode 100644 index 5d970cad..00000000 --- a/src/ability/any.rs +++ /dev/null @@ -1,13 +0,0 @@ -// use crate::prove::Prove; -use std::convert::Infallible; - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct DelegateAny; - -// impl Prove for DelegateAny { -// type Proven = Self; -// -// fn prove(self, _proof: Self) -> Self::Proven { -// self -// } -// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 16d171b3..e134e48d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,271 +1,10 @@ -use crate::{ - ability::traits::{CheckParents, CheckSelf, HasChecker}, - promise::Promise, - prove::TryProve, -}; -use std::{collections::BTreeMap, fmt::Debug}; -use url::Url; +pub mod any; +pub mod create; +pub mod destroy; +pub mod mutate; +pub mod parents; +pub mod read; +pub mod update; // FIXME macro to derive promise versions & delagted builder versions // ... also maybe Ipld - -pub struct Crud { - pub uri: Url, -} - -pub struct CrudRead { - pub uri: Url, -} - -pub struct CrudMutate { - pub uri: Url, -} - -pub struct CrudCreate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CrudUpdate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CrudDestroy { - pub uri: Url, -} - -pub struct CrudAny; -pub struct CrudMutate; -pub struct CrudCreate; -pub struct CrudUpdate; -pub struct CrudDestroy; -pub struct CrudRead; - -pub enum CrudParents { - MutableParent(CrudMutate), - AnyParent(CrudAny), -} - -impl HasChecker for CrudCreate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudCreate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl CheckSelf for CrudUpdate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudUpdate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudDestroy { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudDestroy { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudMutate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudMutate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudAny { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudAny { - type CheckAs = Parentless; -} - -// TODO note to self, this is effectively a partial order -impl CheckParents for CrudMutate { - type Parents = CrudAny; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) - } -} - -impl CheckSelf for CrudParents { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { - match self { - CrudParents::MutableParent(mutate) => match other { - CrudParents::MutableParent(other_mutate) => mutate.check_against_self(other_mutate), - CrudParents::AnyParent(any) => mutate.check_against_parents(any), - }, - _ => Err(()), - } - } -} - -impl CheckParents for CrudCreate { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckParents for CrudUpdate { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckParents for CrudDestroy { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckSelf for CrudRead { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl CheckParents for CrudRead { - type Parents = CrudAny; - type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) - } -} - -// FIXME these should probably be behind a feature flag - -// impl Capabilty for CrudRead{ -// const COMMAND = "crud/read"; -// -// fn subject(&self) -> Did { -// todo!() -// } -// } - -// impl TryProve for CrudDestroy { -// type Error = (); // FIXME -// type Proven = CrudDestroy; -// fn try_prove<'a>(&'a self, proof: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// // FIXME ProveWith? -// impl TryProve for CrudDestroy { -// type Error = (); // FIXME -// type Proven = CrudDestroy; -// -// fn try_prove<'a>(&'a self, proof: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudRead { -// type Error = (); -// type Proven = CrudRead; -// -// fn try_prove<'a>(&'a self, proof: &'a CrudRead) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// // FIXME contains & args -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudRead { -// type Error = (); // FIXME -// type Proven = CrudRead; -// -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudMutate { -// type Error = (); // FIXME -// type Proven = CrudMutate; -// -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// // FIXME -// impl> TryProve for C { -// type Error = (); -// type Proven = C; -// -// // FIXME -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a C, ()> { -// match self.try_prove(&CrudMutate { -// uri: proof.uri.clone(), -// }) { -// Ok(_) => Ok(self), -// Err(_) => Err(()), -// } -// } -// } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs new file mode 100644 index 00000000..b2fed77c --- /dev/null +++ b/src/ability/crud/any.rs @@ -0,0 +1,20 @@ +use crate::prove::{ + parentless::Parentless, + traits::{CheckSelf, HasChecker}, +}; +use url::Url; + +pub struct AnyBuilder { + pub uri: Option, +} + +impl HasChecker for AnyBuilder { + type CheckAs = Parentless; +} + +impl CheckSelf for AnyBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs new file mode 100644 index 00000000..d7efc3e6 --- /dev/null +++ b/src/ability/crud/create.rs @@ -0,0 +1,46 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::parents::Mutable; + +pub struct Create { + pub uri: Url, + pub args: BTreeMap, +} + +pub struct CreateBuilder { + pub uri: Option, + pub args: BTreeMap, +} + +impl HasChecker for CreateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for CreateBuilder { + type SelfError = (); // FIXME better error + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + if self.uri == other.uri { + Ok(()) + } else { + Err(()) + } + } +} + +impl CheckParents for CreateBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs new file mode 100644 index 00000000..f9021b25 --- /dev/null +++ b/src/ability/crud/destroy.rs @@ -0,0 +1,39 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use url::Url; + +use super::parents::Mutable; + +#[derive(Debug, Clone, PartialEq)] +pub struct CrudDestroy { + pub uri: Url, +} + +pub struct DestroyBuilder { + pub uri: Option, +} + +impl HasChecker for DestroyBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for DestroyBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for DestroyBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs new file mode 100644 index 00000000..bf63635a --- /dev/null +++ b/src/ability/crud/mutate.rs @@ -0,0 +1,32 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use url::Url; + +use super::any::AnyBuilder; + +pub struct MutateBuilder { + pub uri: Option, +} + +impl HasChecker for MutateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for MutateBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +// TODO note to self, this is effectively a partial order +impl CheckParents for MutateBuilder { + type Parents = AnyBuilder; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs new file mode 100644 index 00000000..a4a4a042 --- /dev/null +++ b/src/ability/crud/parents.rs @@ -0,0 +1,20 @@ +use super::{any::AnyBuilder, mutate::MutateBuilder}; +use crate::prove::traits::CheckSelf; + +pub enum Mutable { + Mutate(MutateBuilder), + Any(AnyBuilder), +} + +impl CheckSelf for Mutable { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Mutable::Mutate(mutate) => match other { + Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), + Mutable::Any(any) => Ok(()), + }, + _ => Err(()), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs new file mode 100644 index 00000000..f7935f63 --- /dev/null +++ b/src/ability/crud/read.rs @@ -0,0 +1,38 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::any::AnyBuilder; + +pub struct Read { + pub uri: Url, + pub args: BTreeMap, +} + +pub struct ReadBuilder { + pub uri: Option, + pub args: BTreeMap, +} + +impl HasChecker for ReadBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for ReadBuilder { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for ReadBuilder { + type Parents = AnyBuilder; + type ParentError = (); + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs new file mode 100644 index 00000000..5c922146 --- /dev/null +++ b/src/ability/crud/update.rs @@ -0,0 +1,43 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::parents::Mutable; + +#[derive(Debug, Clone, PartialEq)] +pub struct Update { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct UpdateBuilder { + pub uri: Option, + pub args: BTreeMap, // FIXME use a type param? +} + +impl HasChecker for UpdateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for UpdateBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for UpdateBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/prove/traits.rs b/src/prove/traits.rs index bab927aa..d2e40e69 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -13,7 +13,7 @@ pub trait CheckParents: CheckSelf { fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; } -pub trait HasChecker { +pub trait HasChecker: CheckSelf { type CheckAs: IsChecker; } From 8f9053ad4573e567046214f4576c8195a844c329 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 13:09:50 -0800 Subject: [PATCH 088/234] Save state --- src/ability/crud/any.rs | 4 ++-- src/ability/crud/create.rs | 4 ++-- src/ability/crud/destroy.rs | 4 ++-- src/ability/crud/mutate.rs | 4 ++-- src/ability/crud/parents.rs | 4 ++-- src/ability/crud/read.rs | 4 ++-- src/ability/crud/update.rs | 4 ++-- src/prove/parentful.rs | 15 +++++++-------- src/prove/parentless.rs | 8 ++++---- src/prove/traits.rs | 4 ++-- 10 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index b2fed77c..08d5a3ac 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -13,8 +13,8 @@ impl HasChecker for AnyBuilder { } impl CheckSelf for AnyBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index d7efc3e6..c12871f9 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -23,8 +23,8 @@ impl HasChecker for CreateBuilder { } impl CheckSelf for CreateBuilder { - type SelfError = (); // FIXME better error - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); // FIXME better error + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { if self.uri == other.uri { Ok(()) } else { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f9021b25..ec2ad91e 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -20,8 +20,8 @@ impl HasChecker for DestroyBuilder { } impl CheckSelf for DestroyBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index bf63635a..14b1e18d 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -15,8 +15,8 @@ impl HasChecker for MutateBuilder { } impl CheckSelf for MutateBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index a4a4a042..3caf7025 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -7,8 +7,8 @@ pub enum Mutable { } impl CheckSelf for Mutable { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Mutable::Mutate(mutate) => match other { Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f7935f63..d56c9182 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -23,8 +23,8 @@ impl HasChecker for ReadBuilder { } impl CheckSelf for ReadBuilder { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 5c922146..53ee6dd5 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -24,8 +24,8 @@ impl HasChecker for UpdateBuilder { } impl CheckSelf for UpdateBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs index a883d4a9..19fb8f19 100644 --- a/src/prove/parentful.rs +++ b/src/prove/parentful.rs @@ -15,8 +15,9 @@ where T::Parents: CheckSelf, { ParentError(T::ParentError), - ParentSelfError(<::Parents as CheckSelf>::SelfError), - SelfError(::SelfError), + // FIXME needs a WAAAAAY better name + ParentSelfError(<::Parents as CheckSelf>::Error), + Error(::Error), // Compared self to parents EscelationError, @@ -28,9 +29,9 @@ impl CheckSelf for Parentful where T::Parents: CheckSelf, { - type SelfError = ParentfulError; + type Error = ParentfulError; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Parentful::Any => Ok(()), Parentful::Parents(parents) => match other { @@ -47,7 +48,7 @@ where .map_err(ParentfulError::ParentError), Parentful::Me(other_me) => me .check_against_self(other_me) - .map_err(ParentfulError::SelfError), + .map_err(ParentfulError::Error), }, } } @@ -84,9 +85,7 @@ where Parentful::Parents(parents) => self .check_against_parents(parents) .map_err(ParentfulError::ParentError), - Parentful::Me(me) => self - .check_against_self(&me) - .map_err(ParentfulError::SelfError), + Parentful::Me(me) => self.check_against_self(&me).map_err(ParentfulError::Error), } } } diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs index 9814bf1e..5890135f 100644 --- a/src/prove/parentless.rs +++ b/src/prove/parentless.rs @@ -11,9 +11,9 @@ pub enum Parentless { impl IsChecker for Parentless {} impl CheckSelf for Parentless { - type SelfError = T::SelfError; + type Error = T::Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! Parentless::Me(me) => match other { @@ -25,8 +25,8 @@ impl CheckSelf for Parentless { } impl Prove> for T { - type ProveError = T::SelfError; - fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::SelfError> { + type ProveError = T::Error; + fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::Error> { match other { Parentless::Any => Ok(()), Parentless::Me(me) => self.check_against_self(&me), diff --git a/src/prove/traits.rs b/src/prove/traits.rs index d2e40e69..b0e89541 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -1,9 +1,9 @@ use super::internal::IsChecker; pub trait CheckSelf { - type SelfError; + type Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError>; + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error>; } pub trait CheckParents: CheckSelf { From 6382d8ac5b5c180e3791593a4f59aae793f5cbae Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 16:08:12 -0800 Subject: [PATCH 089/234] Save before changig CheckSelf --- Cargo.toml | 1 + src/ability.rs | 5 +- src/ability/crud.rs | 3 - src/ability/crud/any.rs | 20 ++- src/ability/crud/create.rs | 74 ++++++++--- src/ability/crud/destroy.rs | 48 +++++-- src/ability/crud/mutate.rs | 37 +++++- src/ability/crud/parents.rs | 7 +- src/ability/crud/read.rs | 51 +++++--- src/ability/crud/update.rs | 87 +++++++++++-- src/ability/msg.rs | 243 +----------------------------------- src/ability/msg/any.rs | 45 +++++++ src/ability/msg/receive.rs | 80 ++++++++++++ src/ability/msg/send.rs | 113 +++++++++++++++++ src/delegation/payload.rs | 8 +- src/prove/internal.rs | 2 +- src/prove/parentful.rs | 55 +++++--- src/prove/parentless.rs | 38 ++++-- src/prove/traits.rs | 22 ++-- 19 files changed, 582 insertions(+), 357 deletions(-) create mode 100644 src/ability/msg/any.rs create mode 100644 src/ability/msg/receive.rs create mode 100644 src/ability/msg/send.rs diff --git a/Cargo.toml b/Cargo.toml index a58dba08..39aae0a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" serde_json = "1.0.107" +serde_with = {version = "3.5", features = ["alloc"] } signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" diff --git a/src/ability.rs b/src/ability.rs index ac1d449c..f3a4f8aa 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -3,8 +3,11 @@ pub mod traits; // FIXME feature flag each? pub mod crud; -// pub mod msg; +pub mod msg; // TODO move to crate::wasm? #[cfg(feature = "wasm")] pub mod dynamic; + +// FIXME macro to derive promise versions & delagted builder versions +// ... also maybe Ipld diff --git a/src/ability/crud.rs b/src/ability/crud.rs index e134e48d..dda6aa59 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -5,6 +5,3 @@ pub mod mutate; pub mod parents; pub mod read; pub mod update; - -// FIXME macro to derive promise versions & delagted builder versions -// ... also maybe Ipld diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 08d5a3ac..5c17b6b3 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,20 +1,30 @@ -use crate::prove::{ - parentless::Parentless, - traits::{CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentless::Parentless, + traits::{CheckSelf, Checkable}, + }, }; +use serde::{Deserialize, Serialize}; use url::Url; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct AnyBuilder { pub uri: Option, } -impl HasChecker for AnyBuilder { +impl Command for AnyBuilder { + const COMMAND: &'static str = "crud/*"; +} + +impl Checkable for AnyBuilder { type CheckAs = Parentless; } impl CheckSelf for AnyBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index c12871f9..1b0bdab4 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,31 +1,53 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::parents::Mutable; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Create { - pub uri: Url, - pub args: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option>, } -pub struct CreateBuilder { - pub uri: Option, - pub args: BTreeMap, +impl Command for Create { + const COMMAND: &'static str = "crud/create"; } -impl HasChecker for CreateBuilder { - type CheckAs = Parentful; +impl From for Ipld { + fn from(create: Create) -> Self { + create.into() + } } -impl CheckSelf for CreateBuilder { +impl TryFrom for Create { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Checkable for Create { + type CheckAs = Parentful; +} + +impl CheckSelf for Create { type Error = (); // FIXME better error - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { - if self.uri == other.uri { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if self.uri == proof.uri { Ok(()) } else { Err(()) @@ -33,14 +55,30 @@ impl CheckSelf for CreateBuilder { } } -impl CheckParents for CreateBuilder { +impl CheckParents for Create { type Parents = Mutable; type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + if let Some(self_uri) = &self.uri { + match other { + Mutable::Any(any) => { + if let Some(proof_uri) = &any.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + Mutable::Mutate(mutate) => { + if let Some(proof_uri) = &mutate.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + } } + + Ok(()) } } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index ec2ad91e..8021e202 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,32 +1,54 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use url::Url; use super::parents::Mutable; -#[derive(Debug, Clone, PartialEq)] -pub struct CrudDestroy { - pub uri: Url, +// Destroy is its own builder +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Destroy { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, } -pub struct DestroyBuilder { - pub uri: Option, +impl Command for Destroy { + const COMMAND: &'static str = "crud/destroy"; +} + +impl From for Ipld { + fn from(destroy: Destroy) -> Self { + destroy.into() + } +} + +impl TryFrom for Destroy { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for DestroyBuilder { - type CheckAs = Parentful; +impl Checkable for Destroy { + type CheckAs = Parentful; } -impl CheckSelf for DestroyBuilder { +impl CheckSelf for Destroy { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -impl CheckParents for DestroyBuilder { +impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 14b1e18d..a81492e8 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,22 +1,47 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use url::Url; use super::any::AnyBuilder; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MutateBuilder { pub uri: Option, } -impl HasChecker for MutateBuilder { +impl Command for MutateBuilder { + const COMMAND: &'static str = "crud/mutate"; +} + +impl From for Ipld { + fn from(mutate: MutateBuilder) -> Self { + mutate.into() + } +} + +impl TryFrom for MutateBuilder { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Checkable for MutateBuilder { type CheckAs = Parentful; } impl CheckSelf for MutateBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -26,7 +51,7 @@ impl CheckParents for MutateBuilder { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_against_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 3caf7025..44177e30 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,6 +1,9 @@ use super::{any::AnyBuilder, mutate::MutateBuilder}; use crate::prove::traits::CheckSelf; +use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum Mutable { Mutate(MutateBuilder), Any(AnyBuilder), @@ -8,9 +11,9 @@ pub enum Mutable { impl CheckSelf for Mutable { type Error = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { - Mutable::Mutate(mutate) => match other { + Mutable::Mutate(mutate) => match proof { Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), Mutable::Any(any) => Ok(()), }, diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index d56c9182..797e9656 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,35 +1,58 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::any::AnyBuilder; +// Read is its own builder +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Read { - pub uri: Url, - pub args: BTreeMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, } -pub struct ReadBuilder { - pub uri: Option, - pub args: BTreeMap, +impl Command for Read { + const COMMAND: &'static str = "crud/read"; +} + +impl From for Ipld { + fn from(read: Read) -> Self { + read.into() + } +} + +impl TryFrom for Read { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for ReadBuilder { - type CheckAs = Parentful; +impl Checkable for Read { + type CheckAs = Parentful; } -impl CheckSelf for ReadBuilder { +impl CheckSelf for Read { type Error = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -impl CheckParents for ReadBuilder { +impl CheckParents for Read { type Parents = AnyBuilder; type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 53ee6dd5..94d07d41 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,31 +1,76 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::parents::Mutable; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Update { - pub uri: Url, - pub args: BTreeMap, String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub args: BTreeMap, +} + +impl From for Ipld { + fn from(udpdate: Update) -> Self { + udpdate.into() + } +} + +impl TryFrom for Update { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Command for Update { + const COMMAND: &'static str = "crud/update"; } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct UpdateBuilder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, - pub args: BTreeMap, // FIXME use a type param? + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, // FIXME use a type param? +} + +impl From for Ipld { + fn from(udpdate: UpdateBuilder) -> Self { + udpdate.into() + } +} + +impl TryFrom for UpdateBuilder { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for UpdateBuilder { +impl Checkable for UpdateBuilder { type CheckAs = Parentful; } impl CheckSelf for UpdateBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -35,9 +80,25 @@ impl CheckParents for UpdateBuilder { type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + if let Some(self_uri) = &self.uri { + match other { + Mutable::Any(any) => { + if let Some(proof_uri) = &any.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + Mutable::Mutate(mutate) => { + if let Some(proof_uri) = &mutate.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + } } + + Ok(()) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 8b21f673..33792dd3 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,240 +1,3 @@ -use crate::{ - ability::traits::{Command, Delegatable, Resolvable}, - promise::Deferrable, - prove::TryProve, -}; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use url::Url; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Msg { - to: Url, - from: Url, -} - -impl Command for Msg { - const COMMAND: &'static str = "msg/*"; -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgDeferrable { - to: Deferrable, - from: Deferrable, -} - -impl Delegatable for Msg { - type Builder = MsgBuilder; -} - -impl Resolvable for Msg { - type Awaiting = MsgDeferrable; -} - -impl From for MsgBuilder { - fn from(msg: Msg) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for Msg { - type Error = (); - - fn try_from(builder: MsgBuilder) -> Result { - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(()) // FIXME - } - } -} - -impl From for MsgDeferrable { - fn from(msg: Msg) -> Self { - Self { - to: Deferrable::Resolved(msg.to), - from: Deferrable::Resolved(msg.from), - } - } -} - -impl TryFrom for Msg { - type Error = (); - - fn try_from(deferable: MsgDeferrable) -> Result { - if let (Deferrable::Resolved(to), Deferrable::Resolved(from)) = - (deferable.to, deferable.from) - { - Ok(Self { to, from }) - } else { - Err(()) // FIXME - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSend { - to: Url, - from: Url, - message: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSendBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, - #[serde(skip_serializing_if = "Option::is_none")] - message: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSendDeferrable { - to: Deferrable, - from: Deferrable, - message: Deferrable, -} - -// TODO is the to or from often also the subject? Shoudl that be accounted for? -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceive { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceiveBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, -} - -impl From for MsgReceiveBuilder { - fn from(msg: MsgReceive) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for MsgReceive { - type Error = MsgReceiveBuilder; - - fn try_from(builder: MsgReceiveBuilder) -> Result { - // FIXME - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(builder.clone()) // FIXME - } - } -} - -impl From for Ipld { - fn from(msg_rcv: MsgReceive) -> Self { - msg_rcv.into() - } -} - -impl TryFrom for MsgReceiveBuilder { - type Error = (); - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceiveDeferrable { - to: Deferrable, - from: Deferrable, -} - -impl Command for MsgReceive { - const COMMAND: &'static str = "msg/receive"; -} - -impl TryFrom for MsgReceive { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl<'a> TryProve<&'a Msg> for &'a Msg { - type Error = (); // FIXME - type Proven = &'a Msg; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self == proof { - Ok(self) - } else { - Err(()) - } - } -} - -impl<'a> TryProve<&'a Msg> for &'a MsgSend { - type Error = (); // FIXME - type Proven = &'a MsgSend; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self.to == proof.to && self.from == proof.from { - Ok(self) - } else { - Err(()) - } - } -} - -impl<'a> TryProve<&'a Msg> for &'a MsgReceive { - type Error = (); // FIXME - type Proven = &'a MsgReceive; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self.to == proof.to && self.from == proof.from { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME this needs to work on builders! -impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { - type Error = (); // FIXME - type Proven = &'a MsgReceive; - - fn try_prove(self, proof: &'a MsgReceive) -> Result { - if self == proof { - Ok(self) - } else { - Err(()) - } - } -} +pub mod any; +pub mod receive; +pub mod send; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs new file mode 100644 index 00000000..214aa653 --- /dev/null +++ b/src/ability/msg/any.rs @@ -0,0 +1,45 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentless::Parentless, + traits::{CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Any { + pub from: Option, +} + +impl Command for Any { + const COMMAND: &'static str = "msg"; +} + +impl From for Ipld { + fn from(any: Any) -> Self { + any.into() + } +} + +impl TryFrom for Any { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for Any { + type CheckAs = Parentless; +} + +impl CheckSelf for Any { + type Error = (); + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs new file mode 100644 index 00000000..fdba60c8 --- /dev/null +++ b/src/ability/msg/receive.rs @@ -0,0 +1,80 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::any as msg; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Receive { + pub from: Option, +} + +// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +// #[serde(deny_unknown_fields)] +// pub struct MsgReceiveDeferrable { +// to: Deferrable, +// from: Deferrable, +// } + +impl Command for Receive { + const COMMAND: &'static str = "msg/send"; +} + +impl From for Ipld { + fn from(receive: Receive) -> Self { + receive.into() + } +} + +impl TryFrom for Receive { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for Receive { + type CheckAs = Parentful; +} + +impl CheckSelf for Receive { + type Error = (); // FIXME better error + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &proof.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} + +impl CheckParents for Receive { + type Parents = msg::Any; + type ParentError = ::Error; + + // FIXME rename other to proof + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &other.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs new file mode 100644 index 00000000..2a1d7857 --- /dev/null +++ b/src/ability/msg/send.rs @@ -0,0 +1,113 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::any as msg; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Send { + pub to: Url, + pub from: Url, + pub message: String, +} + +impl Command for Send { + const COMMAND: &'static str = "msg/send"; +} + +impl From for Ipld { + fn from(send: Send) -> Self { + send.into() + } +} + +impl TryFrom for Send { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SendBuilder { + pub to: Option, + pub from: Option, + pub message: Option, +} + +impl From for Ipld { + fn from(send: SendBuilder) -> Self { + send.into() + } +} + +impl TryFrom for SendBuilder { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for SendBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for SendBuilder { + type Error = (); // FIXME better error + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(self_to) = &self.to { + if let Some(proof_to) = &proof.to { + if self_to != proof_to { + return Err(()); + } + } + } + + if let Some(self_from) = &self.from { + if let Some(proof_from) = &proof.from { + if self_from != proof_from { + return Err(()); + } + } + } + + if let Some(self_msg) = &self.message { + if let Some(proof_msg) = &proof.message { + if self_msg != proof_msg { + return Err(()); + } + } + } + + Ok(()) + } +} + +impl CheckParents for SendBuilder { + type Parents = msg::Any; + type ParentError = ::Error; + + // FIXME rename other to proof + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &other.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index cad89185..a78050ed 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -4,7 +4,7 @@ use crate::{ capsule::Capsule, did::Did, nonce::Nonce, - prove::traits::{HasChecker, Prove}, + prove::traits::{Checkable, Prove}, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -84,7 +84,7 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, @@ -121,14 +121,14 @@ impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload } #[derive(Clone)] -struct Acc { +struct Acc { issuer: Did, subject: Did, check_chain: T::CheckAs, } // FIXME this needs to move to Delegatable -fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( +fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, diff --git a/src/prove/internal.rs b/src/prove/internal.rs index 8963d51e..a932a8a9 100644 --- a/src/prove/internal.rs +++ b/src/prove/internal.rs @@ -1,2 +1,2 @@ // FIXME rename -pub trait IsChecker {} +pub trait Checker {} diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs index 19fb8f19..62ce0c8f 100644 --- a/src/prove/parentful.rs +++ b/src/prove/parentful.rs @@ -1,12 +1,35 @@ use super::{ - internal::IsChecker, + internal::Checker, traits::{CheckParents, CheckSelf, Prove}, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentful { Any, Parents(T::Parents), - Me(T), + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentful: Parentful) -> Self { + parentful.into() + } +} + +impl + DeserializeOwned + CheckParents> TryFrom for Parentful +where + ::Parents: DeserializeOwned, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } } // TODO better names & derivations @@ -23,7 +46,7 @@ where EscelationError, } -impl IsChecker for Parentful {} +impl Checker for Parentful {} impl CheckSelf for Parentful where @@ -31,24 +54,24 @@ where { type Error = ParentfulError; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { Parentful::Any => Ok(()), - Parentful::Parents(parents) => match other { + Parentful::Parents(parents) => match proof { Parentful::Any => Ok(()), Parentful::Parents(other_parents) => parents .check_against_self(other_parents) .map_err(ParentfulError::ParentSelfError), - Parentful::Me(_other_me) => Err(ParentfulError::EscelationError), + Parentful::This(_other_me) => Err(ParentfulError::EscelationError), }, - Parentful::Me(me) => match other { + Parentful::This(this) => match proof { Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => me + Parentful::Parents(other_parents) => this .check_against_parents(other_parents) .map_err(ParentfulError::ParentError), - Parentful::Me(other_me) => me - .check_against_self(other_me) - .map_err(ParentfulError::Error), + Parentful::This(that) => { + this.check_against_self(that).map_err(ParentfulError::Error) + } }, } } @@ -67,7 +90,7 @@ where match self { Parentful::Any => Ok(()), Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), - Parentful::Me(me) => me + Parentful::This(this) => this .check_against_parents(other) .map_err(ParentfulError::ParentError), } @@ -79,13 +102,15 @@ where T::Parents: CheckSelf, { type ProveError = ParentfulError; - fn check<'a>(&'a self, other: &'a Parentful) -> Result<(), Self::ProveError> { - match other { + fn check<'a>(&'a self, proof: &'a Parentful) -> Result<(), Self::ProveError> { + match proof { Parentful::Any => Ok(()), Parentful::Parents(parents) => self .check_against_parents(parents) .map_err(ParentfulError::ParentError), - Parentful::Me(me) => self.check_against_self(&me).map_err(ParentfulError::Error), + Parentful::This(that) => self + .check_against_self(&that) + .map_err(ParentfulError::Error), } } } diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs index 5890135f..aa669af2 100644 --- a/src/prove/parentless.rs +++ b/src/prove/parentless.rs @@ -1,24 +1,44 @@ use super::{ - internal::IsChecker, + internal::Checker, traits::{CheckSelf, Prove}, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentless { Any, - Me(T), + This(T), } -impl IsChecker for Parentless {} +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentless: Parentless) -> Self { + parentless.into() + } +} + +impl + DeserializeOwned> TryFrom for Parentless { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentless {} impl CheckSelf for Parentless { type Error = T::Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! - Parentless::Me(me) => match other { + Parentless::This(this) => match proof { Parentless::Any => Ok(()), - Parentless::Me(other) => me.check_against_self(other), + Parentless::This(other) => this.check_against_self(other), }, } } @@ -26,10 +46,10 @@ impl CheckSelf for Parentless { impl Prove> for T { type ProveError = T::Error; - fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::Error> { - match other { + fn check<'a>(&'a self, proof: &'a Parentless) -> Result<(), T::Error> { + match proof { Parentless::Any => Ok(()), - Parentless::Me(me) => self.check_against_self(&me), + Parentless::This(this) => self.check_against_self(&this), } } } diff --git a/src/prove/traits.rs b/src/prove/traits.rs index b0e89541..d50f52e6 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -1,31 +1,27 @@ -use super::internal::IsChecker; +use super::internal::Checker; pub trait CheckSelf { type Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error>; + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error>; } pub trait CheckParents: CheckSelf { type Parents; type ParentError; - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; + fn check_against_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } -pub trait HasChecker: CheckSelf { - type CheckAs: IsChecker; +pub trait Checkable: CheckSelf { + type CheckAs: Checker; } -pub trait Prove { +// FIXME is it worth locking consumers out with that Checker bound? +pub trait Prove { type ProveError; - fn check<'a>(&'a self, other: &'a T) -> Result<(), Self::ProveError>; -} - -// FIXME needed? -pub trait IntoParent { - fn as_parent(self) -> T::Parents; + fn check<'a>(&'a self, proof: &'a T) -> Result<(), Self::ProveError>; } // Nightly only... sadness -// trait Foo = HasChecker + Prove; +// trait Foo = Checkable + Prove; From f24fcf854541027066ea0c43ed7e5aee3d577512 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 23:37:08 -0800 Subject: [PATCH 090/234] Proving almost done --- src/ability/crud/any.rs | 9 +-- src/ability/crud/create.rs | 11 +-- src/ability/crud/destroy.rs | 11 +-- src/ability/crud/mutate.rs | 11 +-- src/ability/crud/parents.rs | 8 +- src/ability/crud/read.rs | 11 +-- src/ability/crud/update.rs | 43 ++++------- src/ability/msg/any.rs | 9 +-- src/ability/msg/receive.rs | 34 ++------- src/ability/msg/send.rs | 51 +++---------- src/delegation/condition.rs | 7 +- src/delegation/condition/common.rs | 14 ++-- src/delegation/condition/traits.rs | 5 ++ src/delegation/payload.rs | 86 +++++++++++++-------- src/lib.rs | 2 +- src/proof.rs | 9 +++ src/proof/checkable.rs | 5 ++ src/{prove => proof}/internal.rs | 1 - src/proof/parentful.rs | 59 +++++++++++++++ src/proof/parentless.rs | 48 ++++++++++++ src/proof/parents.rs | 8 ++ src/proof/prove.rs | 16 ++++ src/proof/same.rs | 50 +++++++++++++ src/prove.rs | 15 ---- src/prove/parentful.rs | 116 ----------------------------- src/prove/parentless.rs | 55 -------------- src/prove/traits.rs | 27 ------- 27 files changed, 318 insertions(+), 403 deletions(-) create mode 100644 src/delegation/condition/traits.rs create mode 100644 src/proof.rs create mode 100644 src/proof/checkable.rs rename src/{prove => proof}/internal.rs (56%) create mode 100644 src/proof/parentful.rs create mode 100644 src/proof/parentless.rs create mode 100644 src/proof/parents.rs create mode 100644 src/proof/prove.rs create mode 100644 src/proof/same.rs delete mode 100644 src/prove.rs delete mode 100644 src/prove/parentful.rs delete mode 100644 src/prove/parentless.rs delete mode 100644 src/prove/traits.rs diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 5c17b6b3..a3b0b6c0 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentless::Parentless, - traits::{CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, }; use serde::{Deserialize, Serialize}; use url::Url; @@ -22,9 +19,9 @@ impl Checkable for AnyBuilder { type CheckAs = Parentless; } -impl CheckSelf for AnyBuilder { +impl CheckSame for AnyBuilder { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 1b0bdab4..9e9f4ec7 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -44,9 +41,9 @@ impl Checkable for Create { type CheckAs = Parentful; } -impl CheckSelf for Create { +impl CheckSame for Create { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if self.uri == proof.uri { Ok(()) } else { @@ -59,7 +56,7 @@ impl CheckParents for Create { type Parents = Mutable; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_uri) = &self.uri { match other { Mutable::Any(any) => { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 8021e202..9a2f209d 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -41,9 +38,9 @@ impl Checkable for Destroy { type CheckAs = Parentful; } -impl CheckSelf for Destroy { +impl CheckSame for Destroy { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -52,7 +49,7 @@ impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { Mutable::Mutate(mutate) => Ok(()), Mutable::Any(any) => Ok(()), diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index a81492e8..c03836bc 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -39,9 +36,9 @@ impl Checkable for MutateBuilder { type CheckAs = Parentful; } -impl CheckSelf for MutateBuilder { +impl CheckSame for MutateBuilder { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -51,7 +48,7 @@ impl CheckParents for MutateBuilder { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 44177e30..dc8c6d7b 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,5 +1,5 @@ use super::{any::AnyBuilder, mutate::MutateBuilder}; -use crate::prove::traits::CheckSelf; +use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -9,12 +9,12 @@ pub enum Mutable { Any(AnyBuilder), } -impl CheckSelf for Mutable { +impl CheckSame for Mutable { type Error = (); - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { Mutable::Mutate(mutate) => match proof { - Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), + Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), Mutable::Any(any) => Ok(()), }, _ => Err(()), diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 797e9656..2cc601fc 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -45,9 +42,9 @@ impl Checkable for Read { type CheckAs = Parentful; } -impl CheckSelf for Read { +impl CheckSame for Read { type Error = (); - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -55,7 +52,7 @@ impl CheckSelf for Read { impl CheckParents for Read { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 94d07d41..a5463413 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -68,37 +65,23 @@ impl Checkable for UpdateBuilder { type CheckAs = Parentful; } -impl CheckSelf for UpdateBuilder { - type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for UpdateBuilder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.uri.check_same(&proof.uri).map_err(|_| ())?; + self.args.check_same(&proof.args).map_err(|_| ()) } } impl CheckParents for UpdateBuilder { type Parents = Mutable; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_uri) = &self.uri { - match other { - Mutable::Any(any) => { - if let Some(proof_uri) = &any.uri { - if self_uri != proof_uri { - return Err(()); - } - } - } - Mutable::Mutate(mutate) => { - if let Some(proof_uri) = &mutate.uri { - if self_uri != proof_uri { - return Err(()); - } - } - } - } - } + type ParentError = (); // FIXME - Ok(()) + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match proof { + Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), + Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), + } } } diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 214aa653..7d80f3ec 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentless::Parentless, - traits::{CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -37,9 +34,9 @@ impl Checkable for Any { type CheckAs = Parentless; } -impl CheckSelf for Any { +impl CheckSame for Any { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index fdba60c8..ce818d1c 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -46,35 +43,18 @@ impl Checkable for Receive { type CheckAs = Parentful; } -impl CheckSelf for Receive { +impl CheckSame for Receive { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &proof.from { - if self_from != proof_from { - return Err(()); - } - } - } - - Ok(()) + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.from.check_same(&proof.from).map_err(|_| ()) } } impl CheckParents for Receive { type Parents = msg::Any; - type ParentError = ::Error; - - // FIXME rename other to proof - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &other.from { - if self_from != proof_from { - return Err(()); - } - } - } + type ParentError = ::Error; - Ok(()) + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&proof.from).map_err(|_| ()) } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 2a1d7857..d7eb7ee7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -63,51 +60,21 @@ impl Checkable for SendBuilder { type CheckAs = Parentful; } -impl CheckSelf for SendBuilder { +impl CheckSame for SendBuilder { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(self_to) = &self.to { - if let Some(proof_to) = &proof.to { - if self_to != proof_to { - return Err(()); - } - } - } - - if let Some(self_from) = &self.from { - if let Some(proof_from) = &proof.from { - if self_from != proof_from { - return Err(()); - } - } - } - - if let Some(self_msg) = &self.message { - if let Some(proof_msg) = &proof.message { - if self_msg != proof_msg { - return Err(()); - } - } - } - - Ok(()) + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.to.check_same(&proof.to).map_err(|_| ())?; // FIXME + self.from.check_same(&proof.from).map_err(|_| ())?; + self.message.check_same(&proof.message).map_err(|_| ()) } } impl CheckParents for SendBuilder { type Parents = msg::Any; - type ParentError = ::Error; + type ParentError = ::Error; // FIXME rename other to proof - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &other.from { - if self_from != proof_from { - return Err(()); - } - } - } - - Ok(()) + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&other.from).map_err(|_| ()) } } diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index b03b07b6..9883283f 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,7 +1,2 @@ -use libipld_core::ipld::Ipld; - pub mod common; - -pub trait Condition { - fn validate(&self, ipld: &Ipld) -> bool; -} +pub mod traits; diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs index 8588b22b..7af720d5 100644 --- a/src/delegation/condition/common.rs +++ b/src/delegation/condition/common.rs @@ -1,4 +1,4 @@ -use super::Condition; +use super::traits::Condition; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use regex::Regex; use serde; @@ -15,8 +15,6 @@ pub enum Common { Matches(Matches), } -// FIXME dynamic js version? - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAll { @@ -24,11 +22,11 @@ pub struct ContainsAll { values: Vec, } -// impl From for Ipld { -// fn from(contains_all: ContainsAll) -> Self { -// contains_all.into() -// } -// } +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + contains_all.into() + } +} impl TryFrom for ContainsAll { type Error = (); // FIXME diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs new file mode 100644 index 00000000..29d1be0b --- /dev/null +++ b/src/delegation/condition/traits.rs @@ -0,0 +1,5 @@ +use libipld_core::ipld::Ipld; + +pub trait Condition { + fn validate(&self, ipld: &Ipld) -> bool; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index a78050ed..7eb6d2e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,15 @@ -use super::condition::Condition; +use super::condition::traits::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs}, + ability::traits::{Command, Delegatable, DynJs, Resolvable}, capsule::Capsule, did::Did, + invocation::payload as invocation, nonce::Nonce, - prove::traits::{Checkable, Prove}, + proof::{ + checkable::Checkable, + prove::{Outcome, Prove}, + same::CheckSame, + }, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -82,8 +87,6 @@ impl From> for Ipld { } } -use crate::{ability::traits::Resolvable, invocation::payload as invocation}; - impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version @@ -93,7 +96,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< where // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + Prove, + T::CheckAs: From> + From + Prove + CheckSame, U::Builder: Clone, { let check_chain: T::CheckAs = invoked.clone().into(); @@ -105,18 +108,28 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< let ipld: Ipld = invoked.into(); - let result = proofs.iter().fold(Ok(&start), |prev, proof| { + let result = proofs.iter().fold(Ok(start), |prev, proof| { if let Ok(to_check) = prev { match step1(&to_check, proof, &ipld, now) { - Err(_) => Err(()), - Ok(next) => Ok(next), + Outcome::ArgumentEscelation(_) => Err(()), + Outcome::InvalidProofChain(_) => Err(()), + Outcome::ProvenByAny => Ok(to_check), // NOTE this case! + Outcome::Proven => Ok(Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + check_chain: proof.ability_builder.clone().into(), // FIXME double check + }), } } else { prev } }); - todo!() + // FIXME + match result { + Ok(_) => Ok(()), + Err(_) => Err(()), + } } } @@ -127,55 +140,66 @@ struct Acc { check_chain: T::CheckAs, } +// FIXME replace with check_parents? // FIXME this needs to move to Delegatable fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, -) -> Result<&'a Acc, ()> +) -> Outcome<(), ()> +// FIXME Outcome types where T::CheckAs: From + Prove, U::Builder: Clone, { - if prev.issuer != proof.audience { - todo!() + if let Err(_) = prev.issuer.check_same(&proof.issuer) { + return Outcome::InvalidProofChain(()); } - if prev.subject != proof.subject { - todo!() + if let Err(_) = prev.subject.check_same(&proof.subject) { + return Outcome::InvalidProofChain(()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > now { - todo!() + return Outcome::InvalidProofChain(()); } } if SystemTime::from(proof.expiration.clone()) > now { - todo!() + return Outcome::InvalidProofChain(()); } // FIXME check the spec - // if self.conditions != proof.conditions { + // if self.conditions.len() < proof.conditions { + // ...etc etc // return Err(()); // } - proof - .conditions - .iter() - .try_fold((), |_acc, c| { - if c.validate(&invoked_ipld) { - Ok(()) - } else { - Err(()) - } - }) - .expect("FIXME"); + let cond_result = proof.conditions.iter().try_fold((), |_acc, c| { + if c.validate(&invoked_ipld) { + Ok(()) + } else { + Err(()) + } + }); + + if let Err(_) = cond_result { + return Outcome::InvalidProofChain(()); + } - Prove::check(&prev.check_chain, &proof.ability_builder.clone().into()); + // FIXME pricey clone + let foo = prev + .check_chain + .check(&proof.ability_builder.clone().into()); - todo!() + match foo { + Outcome::Proven => Outcome::Proven, + Outcome::ProvenByAny => Outcome::ProvenByAny, + Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), + Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), + } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/lib.rs b/src/lib.rs index fae01988..d40671f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,7 @@ pub mod invocation; pub mod ipld; pub mod nonce; pub mod promise; -pub mod prove; +pub mod proof; pub mod receipt; pub mod signature; diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 00000000..75559c3b --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,9 @@ +pub mod checkable; +pub mod parentful; +pub mod parentless; +pub mod parents; +pub mod prove; +pub mod same; + +// NOTE must remain *un*exported! +pub(super) mod internal; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs new file mode 100644 index 00000000..07db3dce --- /dev/null +++ b/src/proof/checkable.rs @@ -0,0 +1,5 @@ +use super::{internal::Checker, same::CheckSame}; + +pub trait Checkable: CheckSame { + type CheckAs: Checker; +} diff --git a/src/prove/internal.rs b/src/proof/internal.rs similarity index 56% rename from src/prove/internal.rs rename to src/proof/internal.rs index a932a8a9..b7adf7fa 100644 --- a/src/prove/internal.rs +++ b/src/proof/internal.rs @@ -1,2 +1 @@ -// FIXME rename pub trait Checker {} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs new file mode 100644 index 00000000..aa6ca820 --- /dev/null +++ b/src/proof/parentful.rs @@ -0,0 +1,59 @@ +use super::{ + internal::Checker, + parents::CheckParents, + prove::{Outcome, Prove}, + same::CheckSame, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Parentful { + Any, + Parents(T::Parents), + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentful: Parentful) -> Self { + parentful.into() + } +} + +impl + DeserializeOwned + CheckParents> TryFrom for Parentful +where + ::Parents: DeserializeOwned, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentful {} + +impl Prove> for T +where + T::Parents: CheckSame, +{ + type ArgumentError = T::Error; + type ProofChainError = T::ParentError; + + fn check<'a>(&'a self, proof: &'a Parentful) -> Outcome { + match proof { + Parentful::Any => Outcome::ProvenByAny, + Parentful::Parents(parents) => match self.check_parents(parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidProofChain(e), + }, + Parentful::This(that) => match self.check_same(&that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, + } + } +} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs new file mode 100644 index 00000000..ec1dd15a --- /dev/null +++ b/src/proof/parentless.rs @@ -0,0 +1,48 @@ +use super::{ + internal::Checker, + prove::{Outcome, Prove}, + same::CheckSame, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::convert::Infallible; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Parentless { + Any, + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentless: Parentless) -> Self { + parentless.into() + } +} + +impl + DeserializeOwned> TryFrom for Parentless { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentless {} + +impl Prove> for T { + type ArgumentError = T::Error; + type ProofChainError = Infallible; + + fn check<'a>(&'a self, proof: &'a Parentless) -> Outcome { + match proof { + Parentless::Any => Outcome::Proven, + Parentless::This(this) => match self.check_same(&this) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, + } + } +} diff --git a/src/proof/parents.rs b/src/proof/parents.rs new file mode 100644 index 00000000..a3a92b54 --- /dev/null +++ b/src/proof/parents.rs @@ -0,0 +1,8 @@ +use super::same::CheckSame; + +pub trait CheckParents: CheckSame { + type Parents; + type ParentError; + + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; +} diff --git a/src/proof/prove.rs b/src/proof/prove.rs new file mode 100644 index 00000000..9fc91ca2 --- /dev/null +++ b/src/proof/prove.rs @@ -0,0 +1,16 @@ +use super::internal::Checker; + +// FIXME is it worth locking consumers out with that Checker bound? +pub trait Prove { + type ArgumentError; + type ProofChainError; + + fn check<'a>(&'a self, proof: &'a T) -> Outcome; +} + +pub enum Outcome { + Proven, + ProvenByAny, + ArgumentEscelation(ArgErr), + InvalidProofChain(ChainErr), +} diff --git a/src/proof/same.rs b/src/proof/same.rs new file mode 100644 index 00000000..f2079ea6 --- /dev/null +++ b/src/proof/same.rs @@ -0,0 +1,50 @@ +use crate::did::Did; +use serde::Serialize; + +pub trait CheckSame { + type Error; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; +} + +// Genereic +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Unequal; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum OptionalFieldErr { + MissingField, + UnequalValue, +} + +impl CheckSame for Did { + type Error = Unequal; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.eq(proof) { + Ok(()) + } else { + Err(Unequal) + } + } +} + +impl CheckSame for Option { + type Error = OptionalFieldErr; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { + None => Ok(()), + Some(proof_) => match self { + None => Err(OptionalFieldErr::MissingField), + Some(self_) => { + if self_.eq(proof_) { + Ok(()) + } else { + Err(OptionalFieldErr::UnequalValue) + } + } + }, + } + } +} diff --git a/src/prove.rs b/src/prove.rs deleted file mode 100644 index e4b8122b..00000000 --- a/src/prove.rs +++ /dev/null @@ -1,15 +0,0 @@ -// NOTE must remain *un*exported! -pub(super) mod internal; -pub mod parentful; -pub mod parentless; -pub mod traits; - -// #[cfg_attr(doc, aquamarine::aquamarine)] -// /// FIXME -// /// -// /// ```mermaid -// /// flowchart LR -// /// Invocation --> more --> Self --> Proof --> more2 -// /// more[...] -// /// more2[...] -// /// ``` diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs deleted file mode 100644 index 62ce0c8f..00000000 --- a/src/prove/parentful.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::{ - internal::Checker, - traits::{CheckParents, CheckSelf, Prove}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentful { - Any, - Parents(T::Parents), - This(T), -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentful: Parentful) -> Self { - parentful.into() - } -} - -impl + DeserializeOwned + CheckParents> TryFrom for Parentful -where - ::Parents: DeserializeOwned, -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// TODO better names & derivations -pub enum ParentfulError -where - T::Parents: CheckSelf, -{ - ParentError(T::ParentError), - // FIXME needs a WAAAAAY better name - ParentSelfError(<::Parents as CheckSelf>::Error), - Error(::Error), - - // Compared self to parents - EscelationError, -} - -impl Checker for Parentful {} - -impl CheckSelf for Parentful -where - T::Parents: CheckSelf, -{ - type Error = ParentfulError; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => match proof { - Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => parents - .check_against_self(other_parents) - .map_err(ParentfulError::ParentSelfError), - Parentful::This(_other_me) => Err(ParentfulError::EscelationError), - }, - Parentful::This(this) => match proof { - Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => this - .check_against_parents(other_parents) - .map_err(ParentfulError::ParentError), - Parentful::This(that) => { - this.check_against_self(that).map_err(ParentfulError::Error) - } - }, - } - } -} - -impl CheckParents for Parentful -where - Parentful: CheckSelf, - T::Parents: CheckSelf, -{ - type Parents = T::Parents; - type ParentError = ParentfulError; - - fn check_against_parents(&self, other: &T::Parents) -> Result<(), Self::ParentError> { - // FIXME note to self: see if you can extract the parentful stuff out into the to level Prove - match self { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), - Parentful::This(this) => this - .check_against_parents(other) - .map_err(ParentfulError::ParentError), - } - } -} - -impl Prove> for T -where - T::Parents: CheckSelf, -{ - type ProveError = ParentfulError; - fn check<'a>(&'a self, proof: &'a Parentful) -> Result<(), Self::ProveError> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => self - .check_against_parents(parents) - .map_err(ParentfulError::ParentError), - Parentful::This(that) => self - .check_against_self(&that) - .map_err(ParentfulError::Error), - } - } -} diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs deleted file mode 100644 index aa669af2..00000000 --- a/src/prove/parentless.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::{ - internal::Checker, - traits::{CheckSelf, Prove}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentless { - Any, - This(T), -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentless: Parentless) -> Self { - parentless.into() - } -} - -impl + DeserializeOwned> TryFrom for Parentless { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Checker for Parentless {} - -impl CheckSelf for Parentless { - type Error = T::Error; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! - Parentless::This(this) => match proof { - Parentless::Any => Ok(()), - Parentless::This(other) => this.check_against_self(other), - }, - } - } -} - -impl Prove> for T { - type ProveError = T::Error; - fn check<'a>(&'a self, proof: &'a Parentless) -> Result<(), T::Error> { - match proof { - Parentless::Any => Ok(()), - Parentless::This(this) => self.check_against_self(&this), - } - } -} diff --git a/src/prove/traits.rs b/src/prove/traits.rs deleted file mode 100644 index d50f52e6..00000000 --- a/src/prove/traits.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::internal::Checker; - -pub trait CheckSelf { - type Error; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error>; -} - -pub trait CheckParents: CheckSelf { - type Parents; - type ParentError; - - fn check_against_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; -} - -pub trait Checkable: CheckSelf { - type CheckAs: Checker; -} - -// FIXME is it worth locking consumers out with that Checker bound? -pub trait Prove { - type ProveError; - fn check<'a>(&'a self, proof: &'a T) -> Result<(), Self::ProveError>; -} - -// Nightly only... sadness -// trait Foo = Checkable + Prove; From 2355b7994e7691bc7ccb4891b77240e0e3c2f8d5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 00:37:04 -0800 Subject: [PATCH 091/234] Fixed conditions --- src/delegation/condition.rs | 61 ++++- src/delegation/condition/common.rs | 264 ---------------------- src/delegation/condition/contains_all.rs | 37 +++ src/delegation/condition/contains_any.rs | 37 +++ src/delegation/condition/contains_key.rs | 46 ++++ src/delegation/condition/excludes_all.rs | 37 +++ src/delegation/condition/excludes_key.rs | 33 +++ src/delegation/condition/matches_regex.rs | 70 ++++++ src/delegation/condition/max_length.rs | 35 +++ src/delegation/condition/max_number.rs | 41 ++++ src/delegation/condition/min_length.rs | 35 +++ src/delegation/condition/min_number.rs | 41 ++++ src/lib.rs | 1 + src/number.rs | 23 ++ 14 files changed, 496 insertions(+), 265 deletions(-) delete mode 100644 src/delegation/condition/common.rs create mode 100644 src/delegation/condition/contains_all.rs create mode 100644 src/delegation/condition/contains_any.rs create mode 100644 src/delegation/condition/contains_key.rs create mode 100644 src/delegation/condition/excludes_all.rs create mode 100644 src/delegation/condition/excludes_key.rs create mode 100644 src/delegation/condition/matches_regex.rs create mode 100644 src/delegation/condition/max_length.rs create mode 100644 src/delegation/condition/max_number.rs create mode 100644 src/delegation/condition/min_length.rs create mode 100644 src/delegation/condition/min_number.rs create mode 100644 src/number.rs diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 9883283f..3b3b0c7f 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,2 +1,61 @@ -pub mod common; +pub mod contains_all; +pub mod contains_any; +pub mod contains_key; +pub mod excludes_all; +pub mod excludes_key; +pub mod matches_regex; +pub mod max_length; +pub mod max_number; +pub mod min_length; +pub mod min_number; pub mod traits; + +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use traits::Condition; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Common { + ContainsAll(contains_all::ContainsAll), + ContainsAny(contains_any::ContainsAny), + ContainsKey(contains_key::ContainsKey), + ExcludesKey(excludes_key::ExcludesKey), + ExcludesAll(excludes_all::ExcludesAll), + MinLength(min_length::MinLength), + MaxLength(max_length::MaxLength), + MinNumber(min_number::MinNumber), + MaxNumber(max_number::MaxNumber), + MatchesRegex(matches_regex::MatchesRegex), +} + +impl From for Ipld { + fn from(common: Common) -> Self { + common.into() + } +} + +impl TryFrom for Common { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for Common { + fn validate(&self, ipld: &Ipld) -> bool { + match self { + Common::ContainsAll(c) => c.validate(ipld), + Common::ContainsAny(c) => c.validate(ipld), + Common::ContainsKey(c) => c.validate(ipld), + Common::ExcludesKey(c) => c.validate(ipld), + Common::ExcludesAll(c) => c.validate(ipld), + Common::MinLength(c) => c.validate(ipld), + Common::MaxLength(c) => c.validate(ipld), + Common::MinNumber(c) => c.validate(ipld), + Common::MaxNumber(c) => c.validate(ipld), + Common::MatchesRegex(c) => c.validate(ipld), + } + } +} diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs deleted file mode 100644 index 7af720d5..00000000 --- a/src/delegation/condition/common.rs +++ /dev/null @@ -1,264 +0,0 @@ -use super::traits::Condition; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use regex::Regex; -use serde; -use serde_derive::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq)] -pub enum Common { - ContainsAll(ContainsAll), - ContainsAny(ContainsAny), - ExcludesAll(ExcludesAll), - MaxLength(MaxLength), - MinNumber(MinNumber), - MaxNumber(MaxNumber), - Matches(Matches), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAll { - field: String, - values: Vec, -} - -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - contains_all.into() - } -} - -impl TryFrom for ContainsAll { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl Condition for ContainsAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.values.iter().all(|ipld| array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.values.iter().all(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAny { - field: String, - value: Vec, -} - -impl Condition for ContainsAny { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => array.iter().any(|ipld| self.value.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.value.iter().any(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesAll { - field: String, - value: Vec, -} - -impl Condition for ExcludesAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.value.iter().all(|ipld| !array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.value.iter().all(|ipld| !vals.contains(&ipld)) - } - _ => false, - } - } -} - -impl TryFrom for ExcludesAll { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -// FIXME serialization? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub enum Numeric { - Float(f64), - Integer(i128), -} - -impl TryFrom for Numeric { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinNumber { - field: String, - value: Numeric, -} - -impl TryFrom for MinNumber { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl Condition for MinNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.value { - Numeric::Float(float) => *integer as f64 >= float, - Numeric::Integer(integer) => integer >= integer, - }, - Ipld::Float(float) => match self.value { - Numeric::Float(float) => float >= float, - Numeric::Integer(integer) => *float >= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxNumber { - field: String, - value: Numeric, -} - -impl Condition for MaxNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.value { - Numeric::Float(float) => *integer as f64 <= float, - Numeric::Integer(integer) => integer <= integer, - }, - Ipld::Float(float) => match self.value { - Numeric::Float(float) => float <= float, - Numeric::Integer(integer) => *float <= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} - -impl TryFrom for MaxNumber { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinLength { - field: String, - value: u64, -} - -impl Condition for MinLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() >= self.value as usize, - Ipld::List(list) => list.len() >= self.value as usize, - Ipld::Map(btree) => btree.len() >= self.value as usize, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxLength { - field: String, - value: u64, -} - -impl Condition for MaxLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() <= self.value as usize, - Ipld::List(list) => list.len() <= self.value as usize, - Ipld::Map(btree) => btree.len() <= self.value as usize, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Matches { - field: String, - matcher: Matcher, -} - -#[derive(Debug, Clone)] -pub struct Matcher(Regex); - -impl PartialEq for Matcher { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for Matcher {} - -impl serde::Serialize for Matcher { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.as_str().serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for Matcher { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &str = serde::Deserialize::deserialize(deserializer)?; - match Regex::new(s) { - Ok(regex) => Ok(Matcher(regex)), - Err(_) => { - // FIXME - todo!() - } - } - } -} - -impl Condition for Matcher { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => self.0.is_match(string), - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs new file mode 100644 index 00000000..57d71297 --- /dev/null +++ b/src/delegation/condition/contains_all.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsAll { + field: String, + contains_all: Vec, +} + +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + contains_all.into() + } +} + +impl TryFrom for ContainsAll { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.contains_all.iter().all(|ipld| array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.contains_all.iter().all(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs new file mode 100644 index 00000000..dde1212b --- /dev/null +++ b/src/delegation/condition/contains_any.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsAny { + field: String, + contains_any: Vec, +} + +impl From for Ipld { + fn from(contains_any: ContainsAny) -> Self { + contains_any.into() + } +} + +impl TryFrom for ContainsAny { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsAny { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => array.iter().any(|ipld| self.contains_any.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.contains_any.iter().any(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs new file mode 100644 index 00000000..098237db --- /dev/null +++ b/src/delegation/condition/contains_key.rs @@ -0,0 +1,46 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsKey { + field: String, + contains_key: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + with_value: Option, +} + +impl From for Ipld { + fn from(contains_key: ContainsKey) -> Self { + contains_key.into() + } +} + +impl TryFrom for ContainsKey { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsKey { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Map(map) => { + if let Some(value) = map.get(&self.field) { + if let Some(with_value) = &self.with_value { + value == with_value + } else { + true + } + } else { + false + } + } + _ => false, + } + } +} diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs new file mode 100644 index 00000000..2e94ca3e --- /dev/null +++ b/src/delegation/condition/excludes_all.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExcludesAll { + field: String, + excludes_all: Vec, +} + +impl From for Ipld { + fn from(excludes_all: ExcludesAll) -> Self { + excludes_all.into() + } +} + +impl TryFrom for ExcludesAll { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ExcludesAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.excludes_all.iter().all(|ipld| !array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.excludes_all.iter().all(|ipld| !vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs new file mode 100644 index 00000000..b12ef89e --- /dev/null +++ b/src/delegation/condition/excludes_key.rs @@ -0,0 +1,33 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExcludesKey { + field: String, + excludes_key: String, +} + +impl From for Ipld { + fn from(excludes_key: ExcludesKey) -> Self { + excludes_key.into() + } +} + +impl TryFrom for ExcludesKey { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ExcludesKey { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Map(map) => map.get(&self.field).is_none(), + _ => false, + } + } +} diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs new file mode 100644 index 00000000..02c0f2e4 --- /dev/null +++ b/src/delegation/condition/matches_regex.rs @@ -0,0 +1,70 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use regex::Regex; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MatchesRegex { + field: String, + matches_regex: Matcher, +} + +impl From for Ipld { + fn from(matches_regex: MatchesRegex) -> Self { + matches_regex.into() + } +} + +impl TryFrom for MatchesRegex { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MatchesRegex { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => self.matches_regex.0.is_match(string), + _ => false, + } + } +} + +#[derive(Debug, Clone)] +pub struct Matcher(Regex); + +impl PartialEq for Matcher { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Eq for Matcher {} + +impl serde::Serialize for Matcher { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Matcher { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + match Regex::new(s) { + Ok(regex) => Ok(Matcher(regex)), + Err(_) => { + // FIXME + todo!() + } + } + } +} diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs new file mode 100644 index 00000000..13fe157c --- /dev/null +++ b/src/delegation/condition/max_length.rs @@ -0,0 +1,35 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MaxLength { + field: String, + max_length: usize, +} + +impl From for Ipld { + fn from(max_length: MaxLength) -> Self { + max_length.into() + } +} + +impl TryFrom for MaxLength { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MaxLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() <= self.max_length, + Ipld::List(list) => list.len() <= self.max_length, + Ipld::Map(map) => map.len() <= self.max_length, + _ => false, + } + } +} diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs new file mode 100644 index 00000000..46327ee0 --- /dev/null +++ b/src/delegation/condition/max_number.rs @@ -0,0 +1,41 @@ +use super::traits::Condition; +use crate::number::Number; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MaxNumber { + field: String, + max_number: Number, +} + +impl From for Ipld { + fn from(max_number: MaxNumber) -> Self { + max_number.into() + } +} + +impl TryFrom for MaxNumber { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MaxNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.max_number { + Number::Float(float) => *integer as f64 <= float, + Number::Integer(integer) => integer <= integer, + }, + Ipld::Float(float) => match self.max_number { + Number::Float(float) => float <= float, + Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs new file mode 100644 index 00000000..85dbe1c9 --- /dev/null +++ b/src/delegation/condition/min_length.rs @@ -0,0 +1,35 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MinLength { + field: String, + min_length: usize, +} + +impl From for Ipld { + fn from(min_length: MinLength) -> Self { + min_length.into() + } +} + +impl TryFrom for MinLength { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MinLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() >= self.min_length, + Ipld::List(list) => list.len() >= self.min_length, + Ipld::Map(map) => map.len() >= self.min_length, + _ => false, + } + } +} diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs new file mode 100644 index 00000000..a19ef7c3 --- /dev/null +++ b/src/delegation/condition/min_number.rs @@ -0,0 +1,41 @@ +use super::traits::Condition; +use crate::number::Number; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MinNumber { + field: String, + min_number: Number, +} + +impl From for Ipld { + fn from(min_number: MinNumber) -> Self { + min_number.into() + } +} + +impl TryFrom for MinNumber { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MinNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.min_number { + Number::Float(float) => *integer as f64 >= float, + Number::Integer(integer) => integer >= integer, + }, + Ipld::Float(float) => match self.min_number { + Number::Float(float) => float >= float, + Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d40671f3..c57d2d70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,7 @@ pub mod delegation; pub mod invocation; pub mod ipld; pub mod nonce; +pub mod number; pub mod promise; pub mod proof; pub mod receipt; diff --git a/src/number.rs b/src/number.rs new file mode 100644 index 00000000..20f3042f --- /dev/null +++ b/src/number.rs @@ -0,0 +1,23 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Number { + Float(f64), + Integer(i128), +} + +impl From for Ipld { + fn from(number: Number) -> Self { + number.into() + } +} + +impl TryFrom for Number { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} From 21af73e51613e28e84a31177c702e65048ed3f46 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 02:27:15 -0800 Subject: [PATCH 092/234] Got rid of the extra constraint... but at what cost?!??! dun dun duuuun --- src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 2 +- src/delegation/payload.rs | 78 ++++++++++++----------- src/proof/checkable.rs | 4 +- src/proof/parentful.rs | 94 +++++++++++++++++++++++++--- src/proof/parentless.rs | 36 +++++++++-- src/proof/prove.rs | 11 +++- src/prove/traits/internal/checker.rs | 1 + 15 files changed, 179 insertions(+), 63 deletions(-) create mode 100644 src/prove/traits/internal/checker.rs diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index a3b0b6c0..ccebc717 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -16,7 +16,7 @@ impl Command for AnyBuilder { } impl Checkable for AnyBuilder { - type CheckAs = Parentless; + type Heirarchy = Parentless; } impl CheckSame for AnyBuilder { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 9e9f4ec7..59b00619 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -38,7 +38,7 @@ impl TryFrom for Create { } impl Checkable for Create { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Create { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 9a2f209d..cf70caa1 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -35,7 +35,7 @@ impl TryFrom for Destroy { } impl Checkable for Destroy { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Destroy { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c03836bc..c28af052 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -33,7 +33,7 @@ impl TryFrom for MutateBuilder { } impl Checkable for MutateBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for MutateBuilder { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 2cc601fc..0d9446af 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -39,7 +39,7 @@ impl TryFrom for Read { } impl Checkable for Read { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Read { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index a5463413..44dd284d 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -62,7 +62,7 @@ impl TryFrom for UpdateBuilder { } impl Checkable for UpdateBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for UpdateBuilder { diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 7d80f3ec..64a08311 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -31,7 +31,7 @@ impl TryFrom for Any { } impl Checkable for Any { - type CheckAs = Parentless; + type Heirarchy = Parentless; } impl CheckSame for Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index ce818d1c..c5969d82 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -40,7 +40,7 @@ impl TryFrom for Receive { } impl Checkable for Receive { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Receive { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index d7eb7ee7..dbbbe32d 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -57,7 +57,7 @@ impl TryFrom for SendBuilder { } impl Checkable for SendBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for SendBuilder { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7eb6d2e9..19c4e05a 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -81,7 +81,7 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -89,39 +89,44 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: invocation::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + Prove + CheckSame, U::Builder: Clone, + T::Heirarchy: From> + From + CheckSame, { - let check_chain: T::CheckAs = invoked.clone().into(); - let start: Acc = Acc { - issuer: invoked.issuer.clone(), - subject: invoked.subject.clone(), - check_chain, + let start: Acc<'a, T> = Acc { + issuer: &invoked.issuer, + subject: &invoked.subject, + to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let ipld: Ipld = invoked.into(); + let ipld: Ipld = invoked.clone().into(); - let result = proofs.iter().fold(Ok(start), |prev, proof| { - if let Ok(to_check) = prev { - match step1(&to_check, proof, &ipld, now) { + let result = proofs.iter().fold(Ok(start), |acc, proof| { + if let Ok(prev) = acc { + match step(&prev, proof, &ipld, now) { Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), - Outcome::ProvenByAny => Ok(to_check), // NOTE this case! + Outcome::InvalidParents(_) => Err(()), + Outcome::CommandEscelation => Err(()), + // NOTE this case! + Outcome::ProvenByAny => Ok(Acc { + issuer: &proof.issuer, + subject: &proof.subject, + to_check: prev.to_check, + }), Outcome::Proven => Ok(Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - check_chain: proof.ability_builder.clone().into(), // FIXME double check + issuer: &proof.issuer, + subject: &proof.subject, + to_check: proof.ability_builder.clone().into(), // FIXME double check }), } } else { - prev + acc } }); @@ -133,25 +138,24 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< } } -#[derive(Clone)] -struct Acc { - issuer: Did, - subject: Did, - check_chain: T::CheckAs, +#[derive(Debug, Clone)] +struct Acc<'a, T: Checkable> { + issuer: &'a Did, + subject: &'a Did, + to_check: T::Heirarchy, } -// FIXME replace with check_parents? -// FIXME this needs to move to Delegatable -fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( - prev: &'a Acc, - proof: &'a Payload, - invoked_ipld: &'a Ipld, +// FIXME this should move to Delegatable +fn step<'a, T: Checkable, U: Delegatable, C: Condition>( + prev: &Acc<'a, T>, + proof: &Payload, + invoked_ipld: &Ipld, now: SystemTime, -) -> Outcome<(), ()> -// FIXME Outcome types +) -> Outcome<(), (), ()> +// FIXME ^^^^^^^^^^^^ Outcome types where - T::CheckAs: From + Prove, U::Builder: Clone, + T::Heirarchy: From, { if let Err(_) = prev.issuer.check_same(&proof.issuer) { return Outcome::InvalidProofChain(()); @@ -189,16 +193,16 @@ where return Outcome::InvalidProofChain(()); } - // FIXME pricey clone - let foo = prev - .check_chain - .check(&proof.ability_builder.clone().into()); + // FIXME pretty sure we can avoid this clone + let outcome = prev.to_check.check(&proof.ability_builder.clone().into()); - match foo { + match outcome { Outcome::Proven => Outcome::Proven, Outcome::ProvenByAny => Outcome::ProvenByAny, + Outcome::CommandEscelation => Outcome::CommandEscelation, Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), + Outcome::InvalidParents(_) => Outcome::InvalidParents(()), } } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 07db3dce..44e8195d 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ -use super::{internal::Checker, same::CheckSame}; +use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type CheckAs: Checker; + type Heirarchy: Checker + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index aa6ca820..dea984f8 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -14,6 +14,13 @@ pub enum Parentful { This(T), } +pub enum ParentfulError { + CommandEscelation, + ArgumentEscelation(ArgErr), + InvalidProofChain(PrfErr), + InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least +} + impl From> for Ipld where Ipld: From, @@ -34,25 +41,96 @@ where } } +impl CheckSame for Parentful +where + T::Parents: CheckSame, +{ + type Error = ParentfulError::Error>; // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { + Parentful::Any => Ok(()), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(parents) => parents + .check_same(their_parents) + .map_err(ParentfulError::InvalidParents), + Parentful::This(this) => this + .check_parents(their_parents) + .map_err(ParentfulError::InvalidProofChain), + }, + Parentful::This(that) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), + Parentful::This(this) => this + .check_same(that) + .map_err(ParentfulError::ArgumentEscelation), + }, + } + } +} + +impl CheckParents for Parentful +where + T::Parents: CheckSame, +{ + type Parents = Parentful; + type ParentError = ParentfulError::Error>; + + fn check_parents(&self, proof: &Parentful) -> Result<(), Self::ParentError> { + match proof { + Parentful::Any => Ok(()), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(parents) => parents + .check_same(their_parents) + .map_err(ParentfulError::InvalidParents), + Parentful::This(this) => this + .check_parents(their_parents) + .map_err(ParentfulError::InvalidProofChain), + }, + Parentful::This(that) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), + Parentful::This(this) => this + .check_same(that) + .map_err(ParentfulError::ArgumentEscelation), + }, + } + } +} + impl Checker for Parentful {} -impl Prove> for T +impl Prove> for Parentful where T::Parents: CheckSame, { type ArgumentError = T::Error; type ProofChainError = T::ParentError; + type ParentsError = ::Error; // FIXME better name - fn check<'a>(&'a self, proof: &'a Parentful) -> Outcome { + fn check(&self, proof: &Parentful) -> Outcome { match proof { Parentful::Any => Outcome::ProvenByAny, - Parentful::Parents(parents) => match self.check_parents(parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidProofChain(e), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Outcome::CommandEscelation, + Parentful::Parents(parents) => match parents.check_same(their_parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidParents(e), + }, + Parentful::This(this) => match this.check_parents(their_parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidProofChain(e), + }, }, - Parentful::This(that) => match self.check_same(&that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Parentful::This(that) => match self { + Parentful::Any => Outcome::CommandEscelation, + Parentful::Parents(_) => Outcome::CommandEscelation, + Parentful::This(this) => match this.check_same(that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, }, } } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index ec1dd15a..c9492ef5 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -13,6 +13,12 @@ pub enum Parentless { This(T), } +#[derive(Debug, Clone, PartialEq)] +pub enum ParentlessError { + CommandEscelation, + ArgumentEscelation(T::Error), +} + impl From> for Ipld where Ipld: From, @@ -30,18 +36,38 @@ impl + DeserializeOwned> TryFrom for Parentless { } } +impl CheckSame for Parentless { + type Error = ParentlessError; + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + match other { + Parentless::Any => Ok(()), + Parentless::This(that) => match self { + Parentless::Any => Err(ParentlessError::CommandEscelation), + Parentless::This(this) => this + .check_same(that) + .map_err(ParentlessError::ArgumentEscelation), + }, + } + } +} + impl Checker for Parentless {} -impl Prove> for T { +impl Prove> for Parentless { type ArgumentError = T::Error; type ProofChainError = Infallible; + type ParentsError = Infallible; - fn check<'a>(&'a self, proof: &'a Parentless) -> Outcome { + fn check(&self, proof: &Parentless) -> Outcome { match proof { Parentless::Any => Outcome::Proven, - Parentless::This(this) => match self.check_same(&this) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Parentless::This(that) => match self { + Parentless::Any => Outcome::Proven, + Parentless::This(this) => match this.check_same(that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, }, } } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 9fc91ca2..d9876dcb 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -4,13 +4,20 @@ use super::internal::Checker; pub trait Prove { type ArgumentError; type ProofChainError; + type ParentsError; - fn check<'a>(&'a self, proof: &'a T) -> Outcome; + fn check( + &self, + proof: &T, + ) -> Outcome; } -pub enum Outcome { +// FIXME that's a lot of error type params +pub enum Outcome { Proven, ProvenByAny, ArgumentEscelation(ArgErr), InvalidProofChain(ChainErr), + InvalidParents(ParentErr), + CommandEscelation, } diff --git a/src/prove/traits/internal/checker.rs b/src/prove/traits/internal/checker.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/prove/traits/internal/checker.rs @@ -0,0 +1 @@ + From f417565d3b91c6a57ccec5f44c9c93d8b4fecafa Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 17:09:35 -0800 Subject: [PATCH 093/234] Rounding out types & breaking up modules --- src/ability/crud/any.rs | 24 ++++--- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 8 +-- src/ability/crud/parents.rs | 4 +- src/ability/crud/read.rs | 7 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 8 +-- src/ability/msg/receive.rs | 40 +++++------ src/ability/msg/send.rs | 132 +++++++++++++++++++++++++----------- src/ability/traits.rs | 91 +++++++++++++++++++------ src/ability/wasm.rs | 14 ++++ src/delegation/payload.rs | 9 +-- src/invocation/payload.rs | 1 + src/promise.rs | 26 +++++-- src/proof/checkable.rs | 2 +- src/proof/parentful.rs | 4 +- src/proof/parentless.rs | 9 +++ src/proof/same.rs | 12 +++- 19 files changed, 270 insertions(+), 127 deletions(-) diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index ccebc717..eaf3656f 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,27 +1,31 @@ use crate::{ ability::traits::Command, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{ + parentless::NoParents, + same::{CheckSame, OptionalFieldErr}, + }, }; use serde::{Deserialize, Serialize}; use url::Url; +// NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct AnyBuilder { +pub struct Builder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for AnyBuilder { +impl Command for Builder { const COMMAND: &'static str = "crud/*"; } -impl Checkable for AnyBuilder { - type Heirarchy = Parentless; -} +impl NoParents for Builder {} -impl CheckSame for AnyBuilder { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for Builder { + type Error = OptionalFieldErr; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.uri.check_same(&proof.uri) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 59b00619..9aa758b4 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -38,7 +38,7 @@ impl TryFrom for Create { } impl Checkable for Create { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Create { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index cf70caa1..0ff025ec 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -35,7 +35,7 @@ impl TryFrom for Destroy { } impl Checkable for Destroy { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Destroy { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c28af052..c37a86b6 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,3 +1,4 @@ +use super::any; use crate::{ ability::traits::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -6,11 +7,10 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; -use super::any::AnyBuilder; - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MutateBuilder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } @@ -33,7 +33,7 @@ impl TryFrom for MutateBuilder { } impl Checkable for MutateBuilder { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for MutateBuilder { @@ -45,7 +45,7 @@ impl CheckSame for MutateBuilder { // TODO note to self, this is effectively a partial order impl CheckParents for MutateBuilder { - type Parents = AnyBuilder; + type Parents = any::Builder; type ParentError = (); fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index dc8c6d7b..f42b25a8 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,4 +1,4 @@ -use super::{any::AnyBuilder, mutate::MutateBuilder}; +use super::{any, mutate::MutateBuilder}; use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[serde(deny_unknown_fields)] pub enum Mutable { Mutate(MutateBuilder), - Any(AnyBuilder), + Any(any::Builder), } impl CheckSame for Mutable { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 0d9446af..9a55348c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,3 +1,4 @@ +use super::any; use crate::{ ability::traits::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -7,8 +8,6 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -use super::any::AnyBuilder; - // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -39,7 +38,7 @@ impl TryFrom for Read { } impl Checkable for Read { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Read { @@ -50,7 +49,7 @@ impl CheckSame for Read { } impl CheckParents for Read { - type Parents = AnyBuilder; + type Parents = any::Builder; type ParentError = (); fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 44dd284d..dc8c53c8 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -62,7 +62,7 @@ impl TryFrom for UpdateBuilder { } impl Checkable for UpdateBuilder { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for UpdateBuilder { diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 64a08311..468c8c75 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,6 +1,6 @@ use crate::{ ability::traits::Command, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -13,7 +13,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "msg"; + const COMMAND: &'static str = "msg/*"; } impl From for Ipld { @@ -30,9 +30,7 @@ impl TryFrom for Any { } } -impl Checkable for Any { - type Heirarchy = Parentless; -} +impl NoParents for Any {} impl CheckSame for Any { type Error = (); diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c5969d82..158680fc 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -14,33 +14,12 @@ pub struct Receive { pub from: Option, } -// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -// #[serde(deny_unknown_fields)] -// pub struct MsgReceiveDeferrable { -// to: Deferrable, -// from: Deferrable, -// } - impl Command for Receive { const COMMAND: &'static str = "msg/send"; } -impl From for Ipld { - fn from(receive: Receive) -> Self { - receive.into() - } -} - -impl TryFrom for Receive { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - impl Checkable for Receive { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Receive { @@ -58,3 +37,20 @@ impl CheckParents for Receive { self.from.check_same(&proof.from).map_err(|_| ()) } } + +//////////// + + +impl From for Ipld { + fn from(receive: Receive) -> Self { + receive.into() + } +} + +impl TryFrom for Receive { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index dbbbe32d..3ace3175 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,75 +1,62 @@ use crate::{ - ability::traits::Command, + ability::traits::{Command, Delegatable, Resolvable}, + promise::Deferrable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use url::Url; use super::any as msg; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Send { - pub to: Url, - pub from: Url, - pub message: String, +pub struct Generic { + pub to: To, + pub from: From, + pub message: Message, } -impl Command for Send { - const COMMAND: &'static str = "msg/send"; -} +pub type Resolved = Generic; +pub type Builder = Generic, Option, Option>; +pub type Awaiting = Generic, Deferrable, Deferrable>; -impl From for Ipld { - fn from(send: Send) -> Self { - send.into() - } +impl Delegatable for Resolved { + type Builder = Builder; } -impl TryFrom for Send { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } +impl Resolvable for Resolved { + type Awaiting = Awaiting; } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SendBuilder { - pub to: Option, - pub from: Option, - pub message: Option, -} - -impl From for Ipld { - fn from(send: SendBuilder) -> Self { - send.into() +impl From for Builder { + fn from(awaiting: Awaiting) -> Self { + Builder { + to: awaiting.to.try_extract().ok(), + from: awaiting.from.try_extract().ok(), + message: awaiting.message.try_extract().ok(), + } } } -impl TryFrom for SendBuilder { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } +impl Command for Generic { + const COMMAND: &'static str = "msg/send"; } -impl Checkable for SendBuilder { - type Heirarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for SendBuilder { +impl CheckSame for Builder { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.to.check_same(&proof.to).map_err(|_| ())?; // FIXME + self.to.check_same(&proof.to).map_err(|_| ())?; self.from.check_same(&proof.from).map_err(|_| ())?; self.message.check_same(&proof.message).map_err(|_| ()) } } -impl CheckParents for SendBuilder { +impl CheckParents for Builder { type Parents = msg::Any; type ParentError = ::Error; @@ -78,3 +65,66 @@ impl CheckParents for SendBuilder { self.from.check_same(&other.from).map_err(|_| ()) } } + +impl From for Builder { + fn from(resolved: Resolved) -> Self { + Generic { + to: resolved.to.into(), + from: resolved.from.into(), + message: resolved.message.into(), + } + } +} + +impl From for Awaiting { + fn from(resolved: Resolved) -> Self { + Generic { + to: resolved.to.into(), + from: resolved.from.into(), + message: resolved.message.into(), + } + } +} + +impl TryFrom for Resolved { + type Error = (); + + fn try_from(awaiting: Awaiting) -> Result { + Ok(Generic { + to: awaiting.to.try_extract().map_err(|_| ())?, + from: awaiting.from.try_extract().map_err(|_| ())?, + message: awaiting.message.try_extract().map_err(|_| ())?, + }) + } +} + +impl TryFrom for Resolved { + type Error = (); + + fn try_from(builder: Builder) -> Result { + Ok(Generic { + to: builder.to.ok_or(())?, + from: builder.from.ok_or(())?, + message: builder.message.ok_or(())?, + }) + } +} + +impl From> for Ipld +where + Ipld: From + From + From, +{ + fn from(send: Generic) -> Self { + send.into() + } +} + +impl TryFrom + for Generic +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 24ec772a..d43a4977 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -16,23 +16,52 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { - type Builder: Debug + TryInto + From; + type Builder: TryInto + From; } pub trait Resolvable: Delegatable { - type Awaiting: Debug + TryInto + From + Into; + type Awaiting: TryInto + From + Into; } pub trait Runnable { - type Output: Debug; - fn task_id(self, subject: Did, nonce: Nonce) -> Cid; + type Output; + + fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + + fn to_task_id(&self, subject: Did, nonce: Nonce) -> TaskId { + TaskId { + cid: self.to_task(subject, nonce).into(), + } + } + + // fn lookup(id: TaskId>) -> Result; +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TaskId { + cid: Cid, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct DynJs { pub cmd: String, + #[serde(default)] pub args: BTreeMap, + + #[serde(default)] + pub serialize_nonce: DefaultTrue, +} + +// FIXME move to differet module +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct DefaultTrue(bool); + +impl Default for DefaultTrue { + fn default() -> Self { + DefaultTrue(true) + } } impl Delegatable for DynJs { @@ -43,24 +72,48 @@ impl Resolvable for DynJs { type Awaiting = Self; } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Task { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub: Option, // Is this optional? May as well make it so for now! + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + pub cmd: String, + pub args: BTreeMap, +} + +impl From for Ipld { + fn from(task: Task) -> Ipld { + task.into() + } +} + +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + } +} + +// FIXME DynJs may need a hook for if the nonce should be included impl Runnable for DynJs { type Output = Ipld; - fn task_id(self, subject: Did, nonce: Nonce) -> Cid { - let ipld: Ipld = BTreeMap::from_iter([ - ("sub".into(), subject.into()), - ("do".into(), self.cmd.clone().into()), - ("args".into(), self.cmd.clone().into()), - ("nonce".into(), nonce.into()), - ]) - .into(); - - let mut encoded = vec![]; - ipld.encode(DagCborCodec, &mut encoded) - .expect("should never fail if `encodable_as` is implemented correctly"); - - let multihash = Sha2_256.digest(encoded.as_slice()); - CidGeneric::new_v1(DagCborCodec.into(), multihash) + fn to_task(&self, subject: Did, nonce: Nonce) -> Task { + Task { + sub: Some(subject), + nonce: if self.serialize_nonce == DefaultTrue(true) { + Some(nonce) + } else { + None + }, + cmd: self.cmd.clone(), + args: self.args.clone(), + } } } diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 4ccd51b5..748c9e6e 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,3 +1,4 @@ +use crate::proof::parentless::NoParents; use libipld_core::{ipld::Ipld, link::Link}; #[derive(Debug, Clone, PartialEq)] @@ -12,3 +13,16 @@ pub enum Module { Inline(Vec), Cid(Link>), } + +impl Command for Run { + const COMMAND: &'static str = "wasm/run"; +} + +impl NoParents for Run {} + +impl CheckSame for Run { + type Error = (); + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 19c4e05a..36fdf4e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -96,7 +96,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< where invocation::Payload: Clone, U::Builder: Clone, - T::Heirarchy: From> + From + CheckSame, + T::Hierarchy: From> + From + CheckSame, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -142,7 +142,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< struct Acc<'a, T: Checkable> { issuer: &'a Did, subject: &'a Did, - to_check: T::Heirarchy, + to_check: T::Hierarchy, } // FIXME this should move to Delegatable @@ -155,9 +155,9 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( // FIXME ^^^^^^^^^^^^ Outcome types where U::Builder: Clone, - T::Heirarchy: From, + T::Hierarchy: From, { - if let Err(_) = prev.issuer.check_same(&proof.issuer) { + if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); } @@ -278,6 +278,7 @@ impl> TryFrom for Payload for Payload { ability: DynJs { cmd: s.command, args: s.arguments, + serialize_nonce: todo!(), }, proofs: s.proofs, diff --git a/src/promise.rs b/src/promise.rs index 55962441..af1bd93e 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -5,29 +5,41 @@ use std::fmt::Debug; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Deferrable -where - T: Debug + Clone + PartialEq, -{ +pub enum Deferrable { Resolved(T), Await(Promise), } +impl Deferrable { + pub fn try_extract(self) -> Result { + match self { + Deferrable::Resolved(t) => Ok(t), + Deferrable::Await(promise) => Err(Deferrable::Await(promise)), + } + } +} + +impl From for Deferrable { + fn from(t: T) -> Self { + Deferrable::Resolved(t) + } +} + /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { PromiseAny { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? - await_any: Cid, + any: Cid, }, PromiseOk { #[serde(rename = "ucan/ok")] - await_ok: Cid, + ok: Cid, }, PromiseErr { #[serde(rename = "ucan/err")] - await_err: Cid, + err: Cid, }, } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 44e8195d..0a55ee19 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type Heirarchy: Checker + Prove; + type Hierarchy: Checker + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index dea984f8..d18e8289 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -41,7 +41,7 @@ where } } -impl CheckSame for Parentful +impl CheckSame for Parentful where T::Parents: CheckSame, { @@ -70,7 +70,7 @@ where } } -impl CheckParents for Parentful +impl CheckParents for Parentful where T::Parents: CheckSame, { diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index c9492ef5..08c0e14c 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -1,4 +1,5 @@ use super::{ + checkable::Checkable, internal::Checker, prove::{Outcome, Prove}, same::CheckSame, @@ -13,12 +14,20 @@ pub enum Parentless { This(T), } +// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename #[derive(Debug, Clone, PartialEq)] pub enum ParentlessError { CommandEscelation, ArgumentEscelation(T::Error), } +// FIXME better name +pub trait NoParents {} + +impl Checkable for T { + type Hierarchy = Parentless; +} + impl From> for Ipld where Ipld: From, diff --git a/src/proof/same.rs b/src/proof/same.rs index f2079ea6..b05f8cb6 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,5 +1,5 @@ use crate::did::Did; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub trait CheckSame { type Error; @@ -8,10 +8,16 @@ pub trait CheckSame { } // Genereic -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unequal; -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct OpionalFieldErr { + pub field: T, // Enum of fields + pub err: OptionalFieldErr, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OptionalFieldErr { MissingField, UnequalValue, From 9cb6cdf551f9b6d734d57b3ab7498e375d0e89e6 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 30 Jan 2024 23:40:47 -0800 Subject: [PATCH 094/234] Break up modules in advance of cleanup --- src/ability.rs | 9 +- src/ability/arguments.rs | 20 +++++ src/ability/command.rs | 14 +++ src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/dynamic.rs | 106 ++++++++++------------- src/ability/internal.rs | 1 + src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 3 +- src/ability/msg/send.rs | 44 ++++++++-- src/ability/traits.rs | 132 ----------------------------- src/ability/wasm.rs | 8 +- src/delegation.rs | 3 + src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 5 ++ src/delegation/payload.rs | 29 +++---- src/delegation/traits.rs | 5 ++ src/invocation.rs | 1 + src/invocation/payload.rs | 39 ++++----- src/invocation/resolvable.rs | 5 ++ src/lib.rs | 1 + src/promise.rs | 70 ++++++++++++--- src/proof/checkable.rs | 2 +- src/receipt.rs | 82 +++++++++--------- src/receipt/payload.rs | 13 +-- src/receipt/runnable.rs | 15 ++++ src/task.rs | 97 +++++++++++++++++++++ 31 files changed, 401 insertions(+), 319 deletions(-) create mode 100644 src/ability/arguments.rs create mode 100644 src/ability/command.rs create mode 100644 src/ability/internal.rs delete mode 100644 src/ability/traits.rs create mode 100644 src/delegation/delegatable.rs create mode 100644 src/delegation/traits.rs create mode 100644 src/invocation/resolvable.rs create mode 100644 src/receipt/runnable.rs create mode 100644 src/task.rs diff --git a/src/ability.rs b/src/ability.rs index f3a4f8aa..788870b2 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,12 +1,13 @@ -pub mod traits; -// pub mod wasm; - // FIXME feature flag each? pub mod crud; pub mod msg; +pub mod wasm; + +pub mod arguments; +pub mod command; // TODO move to crate::wasm? -#[cfg(feature = "wasm")] +// #[cfg(feature = "wasm")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs new file mode 100644 index 00000000..67b86d54 --- /dev/null +++ b/src/ability/arguments.rs @@ -0,0 +1,20 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Arguments(pub BTreeMap); + +impl Arguments { + pub fn from_iter(iterable: impl IntoIterator) -> Self { + Arguments(iterable.into_iter().collect()) + } +} + +impl TryFrom for Arguments { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/command.rs b/src/ability/command.rs new file mode 100644 index 00000000..f06637fc --- /dev/null +++ b/src/ability/command.rs @@ -0,0 +1,14 @@ +pub trait Command { + const COMMAND: &'static str; +} + +// NOTE do not export +pub(crate) trait ToCommand { + fn to_command(&self) -> String; +} + +impl ToCommand for T { + fn to_command(&self) -> String { + T::COMMAND.to_string() + } +} diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index eaf3656f..909d09a6 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{ parentless::NoParents, same::{CheckSame, OptionalFieldErr}, diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 9aa758b4..a1452d29 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 0ff025ec..91ac42fe 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c37a86b6..9c1fd8fe 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,6 +1,6 @@ use super::any; use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 9a55348c..f98fc969 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,6 @@ use super::any; use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dc8c53c8..83249052 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b55a9e62..cb4e5129 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,78 +1,66 @@ -//! This module is for dynamic abilities, especially for Wasm support +//! This module is for dynamic abilities, especially for FFI and Wasm support -use super::traits::{Ability, Command}; -use crate::prove::TryProve; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; +use super::{arguments::Arguments, command::ToCommand}; +use crate::{ + delegation::delegatable::Delegatable, invocation::resolvable::Resolvable, promise::Promise, +}; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::Debug; -use crate::ipld::WrappedIpld; +// FIXME move module? +// use js_sys; +// use wasm_bindgen::prelude::*; +// type JsDynamic = Dynamic<&'a js_sys::Function>; +// type JsBuilder = Builder<&'a js_sys::Function>; +// type JsPromised = Promised<&'a js_sys::Function>; +// FIXME move these fiels to a wrapper struct in a different module +// #[serde(skip_serializing)] +// pub chain_validator: Pred, +// #[serde(skip_serializing)] +// pub shape_validator: Pred, +// #[serde(skip_serializing)] +// pub serialize_nonce: DefaultTrue, -use js_sys; -use wasm_bindgen::prelude::*; - -#[derive(Debug, Clone, PartialEq)] -pub struct Dynamic<'a> { - pub command: String, - pub args: BTreeMap, // FIXME consider this being just JsValue - pub validator: &'a js_sys::Function, +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Generic { + pub cmd: String, + pub args: Args, } -#[derive(Debug, Clone, PartialEq)] -pub struct DynamicBuilder<'a> { - pub command: String, - pub args: Option>, - pub validator: &'a js_sys::Function, -} +pub type Dynamic = Generic; +pub type Promised = Generic>; -impl<'a> From> for DynamicBuilder<'a> { - fn from(dynamic: Dynamic<'a>) -> Self { - Self { - command: dynamic.command.clone(), - args: Some(dynamic.args), - validator: dynamic.validator, - } +impl ToCommand for Generic { + fn to_command(&self) -> String { + self.cmd.clone() } } -impl<'a> TryFrom> for Dynamic<'a> { - type Error = (); // FIXME - - fn try_from(builder: DynamicBuilder) -> Result { - if let Some(args) = builder.clone().args { - Ok(Self { - command: builder.command.clone(), - args, - }) - } else { - Err(()) - } - } +impl Delegatable for Dynamic { + type Builder = Dynamic; } -impl<'a> Command for Dynamic<'a> { - fn command(&self) -> &'static str { - self.command - } +impl Resolvable for Dynamic { + type Promised = Dynamic; } -impl<'a> Command for DynamicBuilder<'a> { - fn command(&self) -> &'static str { - self.command +impl From for Arguments { + fn from(dynamic: Dynamic) -> Self { + dynamic.args } } -impl<'a> Ability for Dynamic<'a> { - type Builder = DynamicBuilder<'a>; -} - -impl<'a> TryProve> for DynamicBuilder<'a> { - type Error = JsError; - type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that - - fn try_prove(&'a self, proof: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { - let js_self: JsValue = self.into().into(); - let js_proof: JsValue = proof.into().into(); +impl TryFrom for Dynamic { + type Error = (); // FIXME - self.validator.apply(js_self, js_proof); + fn try_from(awaiting: Promised) -> Result { + if let Promise::Resolved(args) = &awaiting.args { + Ok(Dynamic { + cmd: awaiting.cmd, + args: args.clone(), + }) + } else { + Err(()) + } } } diff --git a/src/ability/internal.rs b/src/ability/internal.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/ability/internal.rs @@ -0,0 +1 @@ + diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 468c8c75..ae603aa4 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 158680fc..c7fb6d68 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -40,7 +40,6 @@ impl CheckParents for Receive { //////////// - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 3ace3175..2cd3293d 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,10 +1,13 @@ use crate::{ - ability::traits::{Command, Delegatable, Resolvable}, - promise::Deferrable, + ability::{arguments::Arguments, command::Command}, + delegation::delegatable::Delegatable, + invocation::resolvable::Resolvable, + promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::BTreeMap; use url::Url; use super::any as msg; @@ -19,18 +22,41 @@ pub struct Generic { pub type Resolved = Generic; pub type Builder = Generic, Option, Option>; -pub type Awaiting = Generic, Deferrable, Deferrable>; +pub type Promised = Generic, Promise, Promise>; impl Delegatable for Resolved { type Builder = Builder; } impl Resolvable for Resolved { - type Awaiting = Awaiting; + type Promised = Promised; } -impl From for Builder { - fn from(awaiting: Awaiting) -> Self { +impl From for Arguments { + fn from(b: Builder) -> Self { + let mut btree = BTreeMap::new(); + b.to.map(|to| btree.insert("to".into(), to.to_string().into())); + b.from + .map(|from| btree.insert("from".into(), from.to_string().into())); + b.message + .map(|msg| btree.insert("message".into(), msg.into())); + + Arguments(btree) + } +} + +impl From for Arguments { + fn from(promised: Promised) -> Self { + Arguments(BTreeMap::from_iter([ + ("to".into(), promised.to.map(String::from).into()), + ("from".into(), promised.from.map(String::from).into()), + ("message".into(), promised.message.into()), + ])) + } +} + +impl From for Builder { + fn from(awaiting: Promised) -> Self { Builder { to: awaiting.to.try_extract().ok(), from: awaiting.from.try_extract().ok(), @@ -76,7 +102,7 @@ impl From for Builder { } } -impl From for Awaiting { +impl From for Promised { fn from(resolved: Resolved) -> Self { Generic { to: resolved.to.into(), @@ -86,10 +112,10 @@ impl From for Awaiting { } } -impl TryFrom for Resolved { +impl TryFrom for Resolved { type Error = (); - fn try_from(awaiting: Awaiting) -> Result { + fn try_from(awaiting: Promised) -> Result { Ok(Generic { to: awaiting.to.try_extract().map_err(|_| ())?, from: awaiting.from.try_extract().map_err(|_| ())?, diff --git a/src/ability/traits.rs b/src/ability/traits.rs deleted file mode 100644 index d43a4977..00000000 --- a/src/ability/traits.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::{did::Did, nonce::Nonce}; -use libipld_cbor::DagCborCodec; -use libipld_core::{ - cid::{Cid, CidGeneric}, - codec::Encode, - ipld::Ipld, - multihash::{Code::Sha2_256, MultihashDigest}, - serde as ipld_serde, -}; -use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; - -pub trait Command { - const COMMAND: &'static str; -} - -// FIXME Delegable and make it proven? -pub trait Delegatable: Sized { - type Builder: TryInto + From; -} - -pub trait Resolvable: Delegatable { - type Awaiting: TryInto + From + Into; -} - -pub trait Runnable { - type Output; - - fn to_task(&self, subject: Did, nonce: Nonce) -> Task; - - fn to_task_id(&self, subject: Did, nonce: Nonce) -> TaskId { - TaskId { - cid: self.to_task(subject, nonce).into(), - } - } - - // fn lookup(id: TaskId>) -> Result; -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TaskId { - cid: Cid, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct DynJs { - pub cmd: String, - #[serde(default)] - pub args: BTreeMap, - - #[serde(default)] - pub serialize_nonce: DefaultTrue, -} - -// FIXME move to differet module -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct DefaultTrue(bool); - -impl Default for DefaultTrue { - fn default() -> Self { - DefaultTrue(true) - } -} - -impl Delegatable for DynJs { - type Builder = Self; -} - -impl Resolvable for DynJs { - type Awaiting = Self; -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Task { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub sub: Option, // Is this optional? May as well make it so for now! - #[serde(default, skip_serializing_if = "Option::is_none")] - pub nonce: Option, - - pub cmd: String, - pub args: BTreeMap, -} - -impl From for Ipld { - fn from(task: Task) -> Ipld { - task.into() - } -} - -impl From for Cid { - fn from(task: Task) -> Cid { - let mut buffer = vec![]; - let ipld: Ipld = task.into(); - ipld.encode(DagCborCodec, &mut buffer) - .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) - } -} - -// FIXME DynJs may need a hook for if the nonce should be included -impl Runnable for DynJs { - type Output = Ipld; - - fn to_task(&self, subject: Did, nonce: Nonce) -> Task { - Task { - sub: Some(subject), - nonce: if self.serialize_nonce == DefaultTrue(true) { - Some(nonce) - } else { - None - }, - cmd: self.cmd.clone(), - args: self.args.clone(), - } - } -} - -impl From for Ipld { - fn from(js: DynJs) -> Self { - js.into() - } -} - -impl TryFrom for DynJs { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 748c9e6e..07e8d5cd 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,4 +1,5 @@ -use crate::proof::parentless::NoParents; +use super::command::Command; +use crate::proof::{parentless::NoParents, same::CheckSame}; use libipld_core::{ipld::Ipld, link::Link}; #[derive(Debug, Clone, PartialEq)] @@ -8,6 +9,7 @@ pub struct Run { pub args: Vec, } +// FIXME #[derive(Debug, Clone, PartialEq)] pub enum Module { Inline(Vec), @@ -21,8 +23,8 @@ impl Command for Run { impl NoParents for Run {} impl CheckSame for Run { - type Error = (); + type Error = (); // FIXME fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) + Ok(()) // FIXME } } diff --git a/src/delegation.rs b/src/delegation.rs index 5c96d411..431531d1 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,4 +1,7 @@ +// FIXME rename delegate? + pub mod condition; +pub mod delegatable; pub mod delegate; pub mod payload; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 29d1be0b..f56edc60 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,5 +1,5 @@ use libipld_core::ipld::Ipld; -pub trait Condition { +pub trait Condition: TryFrom + Into { fn validate(&self, ipld: &Ipld) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs new file mode 100644 index 00000000..151d4fab --- /dev/null +++ b/src/delegation/delegatable.rs @@ -0,0 +1,5 @@ +use crate::ability::arguments::Arguments; + +pub trait Delegatable: Sized { + type Builder: TryInto + From + Into; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 36fdf4e9..527246f8 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,9 +1,9 @@ -use super::condition::traits::Condition; +use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::traits::{Command, Delegatable, DynJs, Resolvable}, + ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invocation::payload as invocation, + invocation::{payload as invocation, resolvable::Resolvable}, nonce::Nonce, proof::{ checkable::Checkable, @@ -95,8 +95,8 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< ) -> Result<(), ()> where invocation::Payload: Clone, - U::Builder: Clone, - T::Hierarchy: From> + From + CheckSame, + U::Builder: Clone + Into, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -154,8 +154,7 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types where - U::Builder: Clone, - T::Hierarchy: From, + U::Builder: Into + Clone, { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); @@ -219,7 +218,7 @@ struct InternalSerializer { #[serde(rename = "can")] command: String, #[serde(rename = "args")] - arguments: BTreeMap, + arguments: Arguments, #[serde(rename = "cond")] conditions: Vec, @@ -234,8 +233,7 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> - for InternalSerializer +impl> From> for InternalSerializer where BTreeMap: From, { @@ -266,19 +264,18 @@ impl TryFrom for InternalSerializer { } } -impl> TryFrom for Payload { +impl> TryFrom for Payload { type Error = (); // FIXME - fn try_from(s: InternalSerializer) -> Result, ()> { + fn try_from(s: InternalSerializer) -> Result, ()> { Ok(Payload { issuer: s.issuer, subject: s.subject, audience: s.audience, - ability_builder: DynJs { + ability_builder: dynamic::Dynamic { cmd: s.command, args: s.arguments, - serialize_nonce: todo!(), }, conditions: s .conditions @@ -300,8 +297,8 @@ impl> TryFrom for Payload> From> for InternalSerializer { - fn from(p: Payload) -> Self { +impl> From> for InternalSerializer { + fn from(p: Payload) -> Self { InternalSerializer { issuer: p.issuer, subject: p.subject, diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs new file mode 100644 index 00000000..b442734a --- /dev/null +++ b/src/delegation/traits.rs @@ -0,0 +1,5 @@ +use crate::{ability::arguments::Arguments, did::Did, nonce::Nonce, task, task::Task}; + +pub trait Delegatable: Sized { + type Builder: TryInto + From + Into; +} diff --git a/src/invocation.rs b/src/invocation.rs index 33842a80..8ac4e9eb 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,4 +1,5 @@ pub mod payload; +pub mod resolvable; use crate::signature; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fae88bf4..9ff818b1 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,5 +1,6 @@ +use super::resolvable::Resolvable; use crate::{ - ability::traits::{Command, DynJs, Resolvable}, + ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, nonce::Nonce, @@ -9,13 +10,15 @@ use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; +// FIXME this version should not be resolvable... +// FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, - pub ability: T::Awaiting, + pub ability: T::Promised, pub proofs: Vec, pub cause: Option, @@ -93,7 +96,7 @@ struct InternalSerializer { #[serde(rename = "do")] command: String, #[serde(rename = "args")] - arguments: BTreeMap, + arguments: Arguments, #[serde(rename = "prf")] proofs: Vec, @@ -111,24 +114,15 @@ struct InternalSerializer { expiration: Timestamp, } -impl From> for InternalSerializer -where - BTreeMap: From, - Ipld: From, -{ +impl From> for InternalSerializer { fn from(payload: Payload) -> Self { - let arguments: BTreeMap = match Ipld::from(payload.ability) { - Ipld::Map(btree) => btree, - _ => panic!("FIXME"), - }; - InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, command: T::COMMAND.into(), - arguments, + arguments: payload.ability.into(), proofs: payload.proofs, cause: payload.cause, @@ -150,17 +144,16 @@ impl TryFrom for InternalSerializer { } } -impl From for Payload { +impl From for Payload { fn from(s: InternalSerializer) -> Self { Payload { issuer: s.issuer, subject: s.subject, audience: s.audience, - ability: DynJs { + ability: dynamic::Dynamic { cmd: s.command, - args: s.arguments, - serialize_nonce: todo!(), + args: s.arguments.into(), }, proofs: s.proofs, @@ -175,11 +168,9 @@ impl From for Payload { } } -impl TryFrom> for InternalSerializer { - type Error = (); // FIXME - - fn try_from(p: Payload) -> Result { - Ok(InternalSerializer { +impl From> for InternalSerializer { + fn from(p: Payload) -> Self { + InternalSerializer { issuer: p.issuer, subject: p.subject, audience: p.audience, @@ -195,6 +186,6 @@ impl TryFrom> for InternalSerializer { not_before: p.not_before, expiration: p.expiration, - }) + } } } diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs new file mode 100644 index 00000000..232f2538 --- /dev/null +++ b/src/invocation/resolvable.rs @@ -0,0 +1,5 @@ +use crate::ability::arguments::Arguments; + +pub trait Resolvable: Sized { + type Promised: TryInto + From + Into; +} diff --git a/src/lib.rs b/src/lib.rs index c57d2d70..2eee9e05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ pub mod promise; pub mod proof; pub mod receipt; pub mod signature; +pub mod task; /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/promise.rs b/src/promise.rs index af1bd93e..54465191 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,3 +1,4 @@ +use crate::ability::arguments::Arguments; use cid::Cid; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; @@ -5,48 +6,93 @@ use std::fmt::Debug; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Deferrable { +pub enum Promise { Resolved(T), - Await(Promise), + Waiting(Selector), } -impl Deferrable { +impl Promise { + pub fn map(self, f: F) -> Promise + where + F: FnOnce(T) -> U, + { + match self { + Promise::Resolved(t) => Promise::Resolved(f(t)), + Promise::Waiting(selector) => Promise::Waiting(selector), + } + } +} + +impl> From> for Arguments { + fn from(promise: Promise) -> Self { + match promise { + Promise::Resolved(t) => t.into(), + Promise::Waiting(selector) => selector.into(), + } + } +} + +impl Promise { pub fn try_extract(self) -> Result { match self { - Deferrable::Resolved(t) => Ok(t), - Deferrable::Await(promise) => Err(Deferrable::Await(promise)), + Promise::Resolved(t) => Ok(t), + Promise::Waiting(promise) => Err(Promise::Waiting(promise)), } } } -impl From for Deferrable { +impl From for Promise { fn from(t: T) -> Self { - Deferrable::Resolved(t) + Promise::Resolved(t) + } +} + +impl From> for Ipld +where + T: Into, +{ + fn from(promise: Promise) -> Self { + match promise { + Promise::Resolved(t) => t.into(), + Promise::Waiting(selector) => selector.into(), + } } } /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also -pub enum Promise { - PromiseAny { +pub enum Selector { + Any { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? any: Cid, }, - PromiseOk { + Ok { #[serde(rename = "ucan/ok")] ok: Cid, }, - PromiseErr { + Err { #[serde(rename = "ucan/err")] err: Cid, }, } -impl TryFrom for Promise { +impl From for Ipld { + fn from(selector: Selector) -> Self { + selector.into() + } +} + +impl TryFrom for Selector { type Error = (); fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } + +impl From for Arguments { + fn from(selector: Selector) -> Self { + todo!() // Arguments(selector.to_string()) + } +} diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 0a55ee19..f8a7cd62 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type Hierarchy: Checker + Prove; + type Hierarchy: Checker + CheckSame + Prove; } diff --git a/src/receipt.rs b/src/receipt.rs index 8f921123..5e70bd54 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,55 +1,51 @@ -use crate::{ - ability::traits::{Command, Delegatable}, - signature, -}; +use crate::signature; use libipld_core::ipld::Ipld; +use payload::Payload; use std::{collections::BTreeMap, fmt::Debug}; pub mod payload; -use payload::Payload; +pub mod runnable; -pub type Receipt = signature::Envelope>; - -// FIXME show piping ability +pub type Receipt = signature::Envelope>; // FIXME #[derive(Debug, Clone, PartialEq)] pub struct ProxyExecute { - pub command: String, + pub cmd: String, pub args: BTreeMap, } -impl Delegatable for ProxyExecute { - type Builder = ProxyExecuteBuilder; -} - -// FIXME hmmm -#[derive(Debug, Clone, PartialEq)] -pub struct ProxyExecuteBuilder { - pub command: Option, - pub args: BTreeMap, -} - -impl Command for ProxyExecute { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl From for ProxyExecuteBuilder { - fn from(proxy: ProxyExecute) -> Self { - ProxyExecuteBuilder { - command: Some(ProxyExecute::COMMAND.into()), - args: proxy.args.clone(), - } - } -} - -impl TryFrom for ProxyExecute { - type Error = (); // FIXME - - fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { - match command { - None => Err(()), - Some(command) => Ok(Self { command, args }), - } - } -} +// impl Delegatable for ProxyExecute { +// type Builder = ProxyExecuteBuilder; +// } +// +// // FIXME hmmm +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// impl Command for ProxyExecute { +// const COMMAND: &'static str = "ucan/proxy"; +// } +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ca496986..25a7033b 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,4 +1,5 @@ -use crate::{ability::traits::Runnable, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use super::runnable::Runnable; +use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; @@ -6,12 +7,12 @@ use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Payload where - T::Output: Serialize + DeserializeOwned, + T::Success: Serialize + DeserializeOwned, { pub issuer: Did, pub ran: Cid, - pub out: Result>, + pub out: Result>, pub next: Vec, pub proofs: Vec, @@ -23,14 +24,14 @@ where impl Capsule for Payload where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } impl TryFrom for Payload where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { type Error = (); // FIXME @@ -41,7 +42,7 @@ where impl From> for Ipld where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { fn from(payload: Payload) -> Self { payload.into() diff --git a/src/receipt/runnable.rs b/src/receipt/runnable.rs new file mode 100644 index 00000000..1fa9f00a --- /dev/null +++ b/src/receipt/runnable.rs @@ -0,0 +1,15 @@ +use crate::{did::Did, nonce::Nonce, task, task::Task}; + +pub trait Runnable { + type Success; + + fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + + fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { + task::Id { + cid: self.to_task(subject, nonce).into(), + } + } + + // fn lookup(id: TaskId>) -> Result; +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 00000000..e79d072d --- /dev/null +++ b/src/task.rs @@ -0,0 +1,97 @@ +use crate::{did::Did, nonce::Nonce}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + error::SerdeError, + ipld::Ipld, + multihash::{Code::Sha2_256, MultihashDigest}, + serde as ipld_serde, +}; +use serde_derive::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Task { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub: Option, // Is this optional? May as well make it so for now! + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + pub cmd: String, + pub args: BTreeMap, +} + +impl From for Id { + fn from(task: Task) -> Id { + Id { + cid: Cid::from(task), + } + } +} + +impl TryFrom for Task { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(task: Task) -> Self { + task.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Id { + pub cid: Cid, +} + +impl TryFrom for Id { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(id: Id) -> Self { + id.cid.into() + } +} + +// FIXME move to differet module +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct DefaultTrue(pub bool); + +impl From for bool { + fn from(def_true: DefaultTrue) -> Self { + def_true.0 + } +} + +impl From for DefaultTrue { + fn from(b: bool) -> Self { + DefaultTrue(b) + } +} + +impl Default for DefaultTrue { + fn default() -> Self { + DefaultTrue(true) + } +} + +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + } +} From 9c6fb070c7d202ff75c87891a8cad38e98b7f5fb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 00:28:34 -0800 Subject: [PATCH 095/234] More cleanup --- src/invocation.rs | 1 + src/invocation/payload.rs | 89 +++++++++++++++++++++++------------- src/invocation/serializer.rs | 1 + 3 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 src/invocation/serializer.rs diff --git a/src/invocation.rs b/src/invocation.rs index 8ac4e9eb..bb914f29 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,5 +1,6 @@ pub mod payload; pub mod resolvable; +mod serializer; use crate::signature; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9ff818b1..715fc14e 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,19 +6,19 @@ use crate::{ nonce::Nonce, time::Timestamp, }; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, - pub ability: T::Promised, + pub ability: T, pub proofs: Vec, pub cause: Option, @@ -29,11 +29,28 @@ pub struct Payload { pub expiration: Timestamp, } -impl Capsule for Payload { +// NOTE This is the version that accepts promises +pub type Unresolved = Payload; +// type Dynamic = Payload; <- ? + +// FIXME parser for both versions +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MaybeResolved + Into> +where + Payload: From, + Unresolved: From, + T::Promised: Clone + Command + Debug + PartialEq, +{ + Resolved(Payload), + Unresolved(Unresolved), +} + +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, InternalSerializer: From>, @@ -43,11 +60,11 @@ where S: Serializer, { let s = InternalSerializer::from(self.clone()); - Serialize::serialize(&s, serializer) + serde::Serialize::serialize(&s, serializer) } } -impl<'de, T: Resolvable> Deserialize<'de> for Payload +impl<'de, T> serde::Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -65,7 +82,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -77,15 +94,15 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] -struct InternalSerializer { +pub(super) struct InternalSerializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] @@ -114,33 +131,17 @@ struct InternalSerializer { expiration: Timestamp, } -impl From> for InternalSerializer { - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, - - command: T::COMMAND.into(), - arguments: payload.ability.into(), - - proofs: payload.proofs, - cause: payload.cause, - metadata: payload.metadata, - - nonce: payload.nonce, - - not_before: payload.not_before, - expiration: payload.expiration, - } +impl From for Ipld { + fn from(serializer: InternalSerializer) -> Self { + serializer.into() } } impl TryFrom for InternalSerializer { - type Error = (); // FIXME + type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } @@ -189,3 +190,25 @@ impl From> for InternalSerializer { } } } + +impl> From> for InternalSerializer { + fn from(payload: Payload) -> Self { + InternalSerializer { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + + command: T::COMMAND.into(), + arguments: payload.ability.into(), + + proofs: payload.proofs, + cause: payload.cause, + metadata: payload.metadata, + + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, + } + } +} diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/invocation/serializer.rs @@ -0,0 +1 @@ + From fdaae6cb263ac96097148e6b686812ae4593eb9d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 14:46:49 -0800 Subject: [PATCH 096/234] I think mainly done moving modules around --- src/ability.rs | 1 + src/ability/arguments.rs | 16 ++++ src/ability/dynamic.rs | 4 +- src/ability/msg/send.rs | 4 +- src/ability/ucan.rs | 82 +++++++++++++++++++ src/condition.rs | 1 - src/condition/common.rs | 1 - src/{delegation.rs => delegate.rs} | 13 +-- src/{delegation => delegate}/condition.rs | 0 .../condition/contains_all.rs | 0 .../condition/contains_any.rs | 0 .../condition/contains_key.rs | 0 .../condition/excludes_all.rs | 0 .../condition/excludes_key.rs | 0 .../condition/matches_regex.rs | 0 .../condition/max_length.rs | 0 .../condition/max_number.rs | 0 .../condition/min_length.rs | 0 .../condition/min_number.rs | 0 .../condition/traits.rs | 0 src/{delegation => delegate}/delegatable.rs | 0 src/{delegation => delegate}/payload.rs | 9 +- src/{delegation => delegate}/traits.rs | 0 src/delegation/delegate.rs | 19 ----- src/invocation/serializer.rs | 1 - src/{invocation.rs => invoke.rs} | 7 +- src/{invocation => invoke}/payload.rs | 0 src/{invocation => invoke}/resolvable.rs | 0 src/invoke/serializer.rs | 0 src/ipld.rs | 10 +-- src/lib.rs | 6 +- src/promise.rs | 2 + src/receipt.rs | 51 ------------ src/respond.rs | 9 ++ src/{receipt => respond}/payload.rs | 12 +-- .../runnable.rs => respond/responds.rs} | 2 +- src/signature.rs | 56 ++++++++++++- 37 files changed, 200 insertions(+), 106 deletions(-) create mode 100644 src/ability/ucan.rs delete mode 100644 src/condition.rs delete mode 100644 src/condition/common.rs rename src/{delegation.rs => delegate.rs} (73%) rename src/{delegation => delegate}/condition.rs (100%) rename src/{delegation => delegate}/condition/contains_all.rs (100%) rename src/{delegation => delegate}/condition/contains_any.rs (100%) rename src/{delegation => delegate}/condition/contains_key.rs (100%) rename src/{delegation => delegate}/condition/excludes_all.rs (100%) rename src/{delegation => delegate}/condition/excludes_key.rs (100%) rename src/{delegation => delegate}/condition/matches_regex.rs (100%) rename src/{delegation => delegate}/condition/max_length.rs (100%) rename src/{delegation => delegate}/condition/max_number.rs (100%) rename src/{delegation => delegate}/condition/min_length.rs (100%) rename src/{delegation => delegate}/condition/min_number.rs (100%) rename src/{delegation => delegate}/condition/traits.rs (100%) rename src/{delegation => delegate}/delegatable.rs (100%) rename src/{delegation => delegate}/payload.rs (97%) rename src/{delegation => delegate}/traits.rs (100%) delete mode 100644 src/delegation/delegate.rs delete mode 100644 src/invocation/serializer.rs rename src/{invocation.rs => invoke.rs} (54%) rename src/{invocation => invoke}/payload.rs (100%) rename src/{invocation => invoke}/resolvable.rs (100%) create mode 100644 src/invoke/serializer.rs delete mode 100644 src/receipt.rs create mode 100644 src/respond.rs rename src/{receipt => respond}/payload.rs (79%) rename src/{receipt/runnable.rs => respond/responds.rs} (94%) diff --git a/src/ability.rs b/src/ability.rs index 788870b2..48abc10d 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,6 +1,7 @@ // FIXME feature flag each? pub mod crud; pub mod msg; +pub mod ucan; pub mod wasm; pub mod arguments; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 67b86d54..67d6baf6 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -11,6 +11,22 @@ impl Arguments { } } +impl Arguments { + pub fn insert(&mut self, key: String, value: Ipld) -> Option { + self.0.insert(key, value) + } + + pub fn get(&self, key: &str) -> Option<&Ipld> { + self.0.get(key) + } +} + +impl Default for Arguments { + fn default() -> Self { + Arguments(BTreeMap::new()) + } +} + impl TryFrom for Arguments { type Error = SerdeError; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index cb4e5129..197cf8f2 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,9 +1,7 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{ - delegation::delegatable::Delegatable, invocation::resolvable::Resolvable, promise::Promise, -}; +use crate::{delegate::Delegatable, invoke::Resolvable, promise::Promise}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 2cd3293d..f0cc21d3 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,7 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, - delegation::delegatable::Delegatable, - invocation::resolvable::Resolvable, + delegate::Delegatable, + invoke::Resolvable, promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs new file mode 100644 index 00000000..ba8b19b6 --- /dev/null +++ b/src/ability/ucan.rs @@ -0,0 +1,82 @@ +use super::arguments::Arguments; +use crate::{ability::command::Command, delegate::Delegatable, promise::Promise}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +// NOTE This one is primarily for enabling delegated recipets + +// FIXME +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Generic { + pub cmd: String, + pub args: Args, // FIXME Does this have specific fields? +} + +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; + +impl Command for Generic { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl Delegatable for Resolved { + type Builder = Builder; +} + +impl From for Builder { + fn from(resolved: Resolved) -> Builder { + Builder { + cmd: resolved.cmd, + args: Some(resolved.args), + } + } +} + +impl TryFrom for Resolved { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Resolved { + cmd: b.cmd, + args: b.args.ok_or(())?, + }) + } +} + +impl From for Arguments { + fn from(b: Builder) -> Arguments { + let mut args = b.args.unwrap_or_default(); + args.insert("cmd".into(), Ipld::String(b.cmd)); + args + } +} + +// // FIXME hmmm +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/condition.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/condition/common.rs b/src/condition/common.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/condition/common.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/delegation.rs b/src/delegate.rs similarity index 73% rename from src/delegation.rs rename to src/delegate.rs index 431531d1..86ebbedd 100644 --- a/src/delegation.rs +++ b/src/delegate.rs @@ -1,14 +1,15 @@ // FIXME rename delegate? -pub mod condition; -pub mod delegatable; -pub mod delegate; -pub mod payload; +mod condition; +mod delegatable; +mod payload; -use crate::signature; -pub use delegate::Delegate; +pub use condition::traits::Condition; +pub use delegatable::Delegatable; pub use payload::Payload; +use crate::signature; + /// A [`Delegation`] is a signed delegation [`Payload`] /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. diff --git a/src/delegation/condition.rs b/src/delegate/condition.rs similarity index 100% rename from src/delegation/condition.rs rename to src/delegate/condition.rs diff --git a/src/delegation/condition/contains_all.rs b/src/delegate/condition/contains_all.rs similarity index 100% rename from src/delegation/condition/contains_all.rs rename to src/delegate/condition/contains_all.rs diff --git a/src/delegation/condition/contains_any.rs b/src/delegate/condition/contains_any.rs similarity index 100% rename from src/delegation/condition/contains_any.rs rename to src/delegate/condition/contains_any.rs diff --git a/src/delegation/condition/contains_key.rs b/src/delegate/condition/contains_key.rs similarity index 100% rename from src/delegation/condition/contains_key.rs rename to src/delegate/condition/contains_key.rs diff --git a/src/delegation/condition/excludes_all.rs b/src/delegate/condition/excludes_all.rs similarity index 100% rename from src/delegation/condition/excludes_all.rs rename to src/delegate/condition/excludes_all.rs diff --git a/src/delegation/condition/excludes_key.rs b/src/delegate/condition/excludes_key.rs similarity index 100% rename from src/delegation/condition/excludes_key.rs rename to src/delegate/condition/excludes_key.rs diff --git a/src/delegation/condition/matches_regex.rs b/src/delegate/condition/matches_regex.rs similarity index 100% rename from src/delegation/condition/matches_regex.rs rename to src/delegate/condition/matches_regex.rs diff --git a/src/delegation/condition/max_length.rs b/src/delegate/condition/max_length.rs similarity index 100% rename from src/delegation/condition/max_length.rs rename to src/delegate/condition/max_length.rs diff --git a/src/delegation/condition/max_number.rs b/src/delegate/condition/max_number.rs similarity index 100% rename from src/delegation/condition/max_number.rs rename to src/delegate/condition/max_number.rs diff --git a/src/delegation/condition/min_length.rs b/src/delegate/condition/min_length.rs similarity index 100% rename from src/delegation/condition/min_length.rs rename to src/delegate/condition/min_length.rs diff --git a/src/delegation/condition/min_number.rs b/src/delegate/condition/min_number.rs similarity index 100% rename from src/delegation/condition/min_number.rs rename to src/delegate/condition/min_number.rs diff --git a/src/delegation/condition/traits.rs b/src/delegate/condition/traits.rs similarity index 100% rename from src/delegation/condition/traits.rs rename to src/delegate/condition/traits.rs diff --git a/src/delegation/delegatable.rs b/src/delegate/delegatable.rs similarity index 100% rename from src/delegation/delegatable.rs rename to src/delegate/delegatable.rs diff --git a/src/delegation/payload.rs b/src/delegate/payload.rs similarity index 97% rename from src/delegation/payload.rs rename to src/delegate/payload.rs index 527246f8..f6997ec6 100644 --- a/src/delegation/payload.rs +++ b/src/delegate/payload.rs @@ -3,7 +3,8 @@ use crate::{ ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invocation::{payload as invocation, resolvable::Resolvable}, + invoke, + invoke::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, @@ -89,14 +90,14 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version + invoked: &'a invoke::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invoke::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegation/traits.rs b/src/delegate/traits.rs similarity index 100% rename from src/delegation/traits.rs rename to src/delegate/traits.rs diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs deleted file mode 100644 index 0046484a..00000000 --- a/src/delegation/delegate.rs +++ /dev/null @@ -1,19 +0,0 @@ -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Delegate { - #[serde(rename = "ucan/*")] - Any, - Specific(T), -} - -impl + serde::de::DeserializeOwned> TryFrom for Delegate { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/invocation/serializer.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/invocation.rs b/src/invoke.rs similarity index 54% rename from src/invocation.rs rename to src/invoke.rs index bb914f29..83575db1 100644 --- a/src/invocation.rs +++ b/src/invoke.rs @@ -1,7 +1,10 @@ -pub mod payload; -pub mod resolvable; +mod payload; +mod resolvable; mod serializer; +pub use payload::Payload; +pub use resolvable::Resolvable; + use crate::signature; pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invoke/payload.rs similarity index 100% rename from src/invocation/payload.rs rename to src/invoke/payload.rs diff --git a/src/invocation/resolvable.rs b/src/invoke/resolvable.rs similarity index 100% rename from src/invocation/resolvable.rs rename to src/invoke/resolvable.rs diff --git a/src/invoke/serializer.rs b/src/invoke/serializer.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/ipld.rs b/src/ipld.rs index 64a8ea5a..a820acaf 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,23 +3,23 @@ use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsValue; -pub struct WrappedIpld(pub Ipld); +pub struct Newtype(pub Ipld); -impl From for WrappedIpld { +impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) } } -impl From for Ipld { - fn from(wrapped: WrappedIpld) -> Self { +impl From for Ipld { + fn from(wrapped: Newtype) -> Self { wrapped.0 } } // TODO testme #[cfg(target_arch = "wasm32")] -impl From for JsValue { +impl From for JsValue { fn from(wrapped: WrappedIpld) -> Self { match wrapped.0 { Ipld::Null => JsValue::Null, diff --git a/src/lib.rs b/src/lib.rs index 2eee9e05..2c922a09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,14 +114,14 @@ use std::fmt::Debug; pub mod ability; pub mod capsule; -pub mod delegation; -pub mod invocation; +pub mod delegate; +pub mod invoke; pub mod ipld; pub mod nonce; pub mod number; pub mod promise; pub mod proof; -pub mod receipt; +pub mod respond; pub mod signature; pub mod task; diff --git a/src/promise.rs b/src/promise.rs index 54465191..f3189f1d 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -4,6 +4,8 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +// FIXME move under invoke? + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { diff --git a/src/receipt.rs b/src/receipt.rs deleted file mode 100644 index 5e70bd54..00000000 --- a/src/receipt.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::signature; -use libipld_core::ipld::Ipld; -use payload::Payload; -use std::{collections::BTreeMap, fmt::Debug}; - -pub mod payload; -pub mod runnable; - -pub type Receipt = signature::Envelope>; - -// FIXME -#[derive(Debug, Clone, PartialEq)] -pub struct ProxyExecute { - pub cmd: String, - pub args: BTreeMap, -} - -// impl Delegatable for ProxyExecute { -// type Builder = ProxyExecuteBuilder; -// } -// -// // FIXME hmmm -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// impl Command for ProxyExecute { -// const COMMAND: &'static str = "ucan/proxy"; -// } -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } diff --git a/src/respond.rs b/src/respond.rs new file mode 100644 index 00000000..2cd81284 --- /dev/null +++ b/src/respond.rs @@ -0,0 +1,9 @@ +mod payload; +mod responds; + +pub use payload::Payload; +pub use responds::Responds; + +use crate::signature; + +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/respond/payload.rs similarity index 79% rename from src/receipt/payload.rs rename to src/respond/payload.rs index 25a7033b..b6374f26 100644 --- a/src/receipt/payload.rs +++ b/src/respond/payload.rs @@ -1,11 +1,11 @@ -use super::runnable::Runnable; +use super::responds::Responds; use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload +pub struct Payload where T::Success: Serialize + DeserializeOwned, { @@ -22,14 +22,14 @@ where pub issued_at: Option, } -impl Capsule for Payload +impl Capsule for Payload where for<'de> T::Success: Serialize + Deserialize<'de>, { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl TryFrom for Payload +impl TryFrom for Payload where for<'de> T::Success: Serialize + Deserialize<'de>, { @@ -40,7 +40,7 @@ where } } -impl From> for Ipld +impl From> for Ipld where for<'de> T::Success: Serialize + Deserialize<'de>, { diff --git a/src/receipt/runnable.rs b/src/respond/responds.rs similarity index 94% rename from src/receipt/runnable.rs rename to src/respond/responds.rs index 1fa9f00a..4bd6dfee 100644 --- a/src/receipt/runnable.rs +++ b/src/respond/responds.rs @@ -1,6 +1,6 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; -pub trait Runnable { +pub trait Responds { type Success; fn to_task(&self, subject: Did, nonce: Nonce) -> Task; diff --git a/src/signature.rs b/src/signature.rs index 12ceda4d..392a9f9a 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,5 +1,6 @@ use crate::capsule::Capsule; use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] @@ -9,7 +10,8 @@ pub struct Envelope { } // FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Signature { One(Vec), Batch { @@ -18,6 +20,58 @@ pub enum Signature { }, } +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(transparent)] +// pub struct StaticVec { +// pub slice: Box<[T]>, +// } +// +// impl From> for StaticVec { +// fn from(vec: Vec) -> Self { +// Self { +// slice: vec.into_boxed_slice(), +// } +// } +// } +// +// impl From> for Vec { +// fn from(vec: StaticVec) -> Vec { +// vec.slice.into() +// } +// } +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(transparent)] +// pub struct StaticString { +// string: Box, +// } +// +// impl From for StaticString { +// fn from(string: String) -> Self { +// Self { +// string: string.into_boxed_str(), +// } +// } +// } +// +// impl<'a> From<&'a str> for StaticString { +// fn from(s: &'a str) -> Self { +// Self { string: s.into() } +// } +// } +// +// impl<'a> From<&'a StaticString> for &'a str { +// fn from(s: &'a StaticString) -> &'a str { +// &s.string +// } +// } +// +// impl From for String { +// fn from(s: StaticString) -> String { +// s.string.into() +// } +// } + impl From<&Signature> for Ipld { fn from(sig: &Signature) -> Self { match sig { From 8ff556cf516a333c8cc0fd7336e08b81da9fb4ab Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 18:28:10 -0800 Subject: [PATCH 097/234] Serde sometimes, I tell ya --- src/ability/dynamic.rs | 7 +- src/ability/msg/send.rs | 4 +- src/ability/ucan.rs | 4 +- src/{delegate.rs => delegation.rs} | 9 +- src/{delegate => delegation}/condition.rs | 0 .../condition/contains_all.rs | 0 .../condition/contains_any.rs | 0 .../condition/contains_key.rs | 0 .../condition/excludes_all.rs | 0 .../condition/excludes_key.rs | 0 .../condition/matches_regex.rs | 0 .../condition/max_length.rs | 0 .../condition/max_number.rs | 0 .../condition/min_length.rs | 0 .../condition/min_number.rs | 0 .../condition/traits.rs | 0 src/{delegate => delegation}/delegatable.rs | 0 src/{delegate => delegation}/payload.rs | 10 +- src/{delegate => delegation}/traits.rs | 0 src/{invoke.rs => invocation.rs} | 2 +- src/{invoke => invocation}/payload.rs | 2 +- src/{invoke => invocation}/resolvable.rs | 0 src/{invoke => invocation}/serializer.rs | 0 src/lib.rs | 197 +++++++----------- src/promise.rs | 2 +- src/{respond.rs => receipt.rs} | 8 +- src/receipt/payload.rs | 135 ++++++++++++ src/receipt/receipt.rs | 4 + src/{respond => receipt}/responds.rs | 2 - src/receipt/store.rs | 16 ++ src/respond/payload.rs | 50 ----- 31 files changed, 255 insertions(+), 197 deletions(-) rename src/{delegate.rs => delegation.rs} (70%) rename src/{delegate => delegation}/condition.rs (100%) rename src/{delegate => delegation}/condition/contains_all.rs (100%) rename src/{delegate => delegation}/condition/contains_any.rs (100%) rename src/{delegate => delegation}/condition/contains_key.rs (100%) rename src/{delegate => delegation}/condition/excludes_all.rs (100%) rename src/{delegate => delegation}/condition/excludes_key.rs (100%) rename src/{delegate => delegation}/condition/matches_regex.rs (100%) rename src/{delegate => delegation}/condition/max_length.rs (100%) rename src/{delegate => delegation}/condition/max_number.rs (100%) rename src/{delegate => delegation}/condition/min_length.rs (100%) rename src/{delegate => delegation}/condition/min_number.rs (100%) rename src/{delegate => delegation}/condition/traits.rs (100%) rename src/{delegate => delegation}/delegatable.rs (100%) rename src/{delegate => delegation}/payload.rs (97%) rename src/{delegate => delegation}/traits.rs (100%) rename src/{invoke.rs => invocation.rs} (80%) rename src/{invoke => invocation}/payload.rs (99%) rename src/{invoke => invocation}/resolvable.rs (100%) rename src/{invoke => invocation}/serializer.rs (100%) rename src/{respond.rs => receipt.rs} (50%) create mode 100644 src/receipt/payload.rs create mode 100644 src/receipt/receipt.rs rename src/{respond => receipt}/responds.rs (83%) create mode 100644 src/receipt/store.rs delete mode 100644 src/respond/payload.rs diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 197cf8f2..4cc68940 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,11 +1,11 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{delegate::Delegatable, invoke::Resolvable, promise::Promise}; +use crate::{delegation::Delegatable, invocation::Resolvable, promise::Promise}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -// FIXME move module? +// FIXME move commented-out module? // use js_sys; // use wasm_bindgen::prelude::*; // type JsDynamic = Dynamic<&'a js_sys::Function>; @@ -19,6 +19,9 @@ use std::fmt::Debug; // #[serde(skip_serializing)] // pub serialize_nonce: DefaultTrue, +// NOTE the lack of checking functions! +// This is meant to be embedded inside of structs that have e.g. FFI bindings to +// a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Generic { pub cmd: String, diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index f0cc21d3..1d7c81a1 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,7 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, - delegate::Delegatable, - invoke::Resolvable, + delegation::Delegatable, + invocation::Resolvable, promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index ba8b19b6..e2ee2867 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,10 +1,10 @@ use super::arguments::Arguments; -use crate::{ability::command::Command, delegate::Delegatable, promise::Promise}; +use crate::{ability::command::Command, delegation::Delegatable, promise::Promise}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt::Debug; -// NOTE This one is primarily for enabling delegated recipets +// NOTE This one is primarily for enabling delegationd recipets // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegate.rs b/src/delegation.rs similarity index 70% rename from src/delegate.rs rename to src/delegation.rs index 86ebbedd..52c79c97 100644 --- a/src/delegate.rs +++ b/src/delegation.rs @@ -1,10 +1,9 @@ -// FIXME rename delegate? - mod condition; mod delegatable; mod payload; -pub use condition::traits::Condition; +pub use condition::*; + pub use delegatable::Delegatable; pub use payload::Payload; @@ -16,4 +15,6 @@ use crate::signature; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; + +// FIXME add a store with delegation indexing diff --git a/src/delegate/condition.rs b/src/delegation/condition.rs similarity index 100% rename from src/delegate/condition.rs rename to src/delegation/condition.rs diff --git a/src/delegate/condition/contains_all.rs b/src/delegation/condition/contains_all.rs similarity index 100% rename from src/delegate/condition/contains_all.rs rename to src/delegation/condition/contains_all.rs diff --git a/src/delegate/condition/contains_any.rs b/src/delegation/condition/contains_any.rs similarity index 100% rename from src/delegate/condition/contains_any.rs rename to src/delegation/condition/contains_any.rs diff --git a/src/delegate/condition/contains_key.rs b/src/delegation/condition/contains_key.rs similarity index 100% rename from src/delegate/condition/contains_key.rs rename to src/delegation/condition/contains_key.rs diff --git a/src/delegate/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs similarity index 100% rename from src/delegate/condition/excludes_all.rs rename to src/delegation/condition/excludes_all.rs diff --git a/src/delegate/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs similarity index 100% rename from src/delegate/condition/excludes_key.rs rename to src/delegation/condition/excludes_key.rs diff --git a/src/delegate/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs similarity index 100% rename from src/delegate/condition/matches_regex.rs rename to src/delegation/condition/matches_regex.rs diff --git a/src/delegate/condition/max_length.rs b/src/delegation/condition/max_length.rs similarity index 100% rename from src/delegate/condition/max_length.rs rename to src/delegation/condition/max_length.rs diff --git a/src/delegate/condition/max_number.rs b/src/delegation/condition/max_number.rs similarity index 100% rename from src/delegate/condition/max_number.rs rename to src/delegation/condition/max_number.rs diff --git a/src/delegate/condition/min_length.rs b/src/delegation/condition/min_length.rs similarity index 100% rename from src/delegate/condition/min_length.rs rename to src/delegation/condition/min_length.rs diff --git a/src/delegate/condition/min_number.rs b/src/delegation/condition/min_number.rs similarity index 100% rename from src/delegate/condition/min_number.rs rename to src/delegation/condition/min_number.rs diff --git a/src/delegate/condition/traits.rs b/src/delegation/condition/traits.rs similarity index 100% rename from src/delegate/condition/traits.rs rename to src/delegation/condition/traits.rs diff --git a/src/delegate/delegatable.rs b/src/delegation/delegatable.rs similarity index 100% rename from src/delegate/delegatable.rs rename to src/delegation/delegatable.rs diff --git a/src/delegate/payload.rs b/src/delegation/payload.rs similarity index 97% rename from src/delegate/payload.rs rename to src/delegation/payload.rs index f6997ec6..2f5ad289 100644 --- a/src/delegate/payload.rs +++ b/src/delegation/payload.rs @@ -3,8 +3,8 @@ use crate::{ ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invoke, - invoke::Resolvable, + invocation, + invocation::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, @@ -90,14 +90,14 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invoke::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invoke::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegate/traits.rs b/src/delegation/traits.rs similarity index 100% rename from src/delegate/traits.rs rename to src/delegation/traits.rs diff --git a/src/invoke.rs b/src/invocation.rs similarity index 80% rename from src/invoke.rs rename to src/invocation.rs index 83575db1..5cb2b95e 100644 --- a/src/invoke.rs +++ b/src/invocation.rs @@ -2,7 +2,7 @@ mod payload; mod resolvable; mod serializer; -pub use payload::Payload; +pub use payload::{Payload, Unresolved}; pub use resolvable::Resolvable; use crate::signature; diff --git a/src/invoke/payload.rs b/src/invocation/payload.rs similarity index 99% rename from src/invoke/payload.rs rename to src/invocation/payload.rs index 715fc14e..46fc3d41 100644 --- a/src/invoke/payload.rs +++ b/src/invocation/payload.rs @@ -102,7 +102,7 @@ impl From> for Ipld { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct InternalSerializer { +struct InternalSerializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] diff --git a/src/invoke/resolvable.rs b/src/invocation/resolvable.rs similarity index 100% rename from src/invoke/resolvable.rs rename to src/invocation/resolvable.rs diff --git a/src/invoke/serializer.rs b/src/invocation/serializer.rs similarity index 100% rename from src/invoke/serializer.rs rename to src/invocation/serializer.rs diff --git a/src/lib.rs b/src/lib.rs index 2c922a09..f8eb43d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,40 +4,38 @@ //! ucan -use std::str::FromStr; - -use cid::{multihash, Cid}; -use serde::{de, Deserialize, Deserializer, Serialize}; - -pub mod builder; -pub mod capability; -pub mod crypto; -pub mod did; -pub mod did_verifier; -pub mod error; -pub mod plugins; -pub mod semantics; -pub mod store; -pub mod time; -pub mod ucan; - -#[cfg(target_arch = "wasm32")] -mod wasm; - -#[cfg(target_arch = "wasm32")] -pub use wasm::*; - -#[doc(hidden)] -#[cfg(not(target_arch = "wasm32"))] -pub use linkme; - -/// The default multihash algorithm used for UCANs -pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; - -/// A decentralized identifier. -pub type Did = String; - -use std::fmt::Debug; +// use std::str::FromStr; +// +// use cid::{multihash, Cid}; +// use serde::{de, Deserialize, Deserializer, Serialize}; +// +// pub mod builder; +// pub mod capability; +// pub mod crypto; +// pub mod did_verifier; +// pub mod error; +// pub mod plugins; +// pub mod semantics; +// pub mod store; +// pub mod ucan; +// +// #[cfg(target_arch = "wasm32")] +// mod wasm; +// +// #[cfg(target_arch = "wasm32")] +// pub use wasm::*; +// +// #[doc(hidden)] +// #[cfg(not(target_arch = "wasm32"))] +// pub use linkme; +// +// /// The default multihash algorithm used for UCANs +// pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; +// +// /// A decentralized identifier. +// pub type Did = String; +// +// use std::fmt::Debug; // FIXME concrete abilitiy types in addition to promised version @@ -62,103 +60,56 @@ use std::fmt::Debug; // } // } -// FIXME Remove -// pub trait Prove { -// type Witness; -// // FIXME make sure that passing the top-level item through and not checking each -// // item in the chain against the next one is correct in the 1.0 semantics -// fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; -// } +pub mod ability; +pub mod capsule; +pub mod delegation; +pub mod did; +pub mod invocation; +pub mod ipld; +pub mod nonce; +pub mod number; +pub mod promise; +pub mod proof; +pub mod receipt; +pub mod signature; +pub mod task; +pub mod time; -// impl Prove for T { -// type Witness = T; +// FIXME consider a fact-system +// /// The empty fact +// #[derive(Debug, Clone, Default, Serialize, Deserialize)] +// pub struct EmptyFact {} // -// fn prove<'a>(&'a self, proof: &'a DelegateAny) -> &Self::Witness { -// self -// } -// } +// /// The default fact +// pub type DefaultFact = EmptyFact; // -// impl> TryProve for T { -// type Error = Void; -// type Proven = T; +// /// A newtype around Cid that (de)serializes as a string +// #[derive(Debug, Clone)] +// pub struct CidString(pub(crate) Cid); // -// fn try_prove<'a>(&'a self, proof: &'a T) -> Result<&'a T, Void> { -// Ok(proof) +// impl Serialize for CidString { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// serializer.serialize_str(self.0.to_string().as_str()) // } // } - -// FIXME lives etirely in bindgen -// https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html -// pub struct DynamicJs { -// pub command: Box, -// pub args: BTreeMap, Ipld>, -// } -// -// impl TryProve for DynamicJs { -// type Error = (); -// type Proven = DynamicJs; // -// fn try_prove<'a>(&'a self, proof: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// impl<'de> Deserialize<'de> for CidString { +// fn deserialize(deserializer: D) -> Result +// where +// D: Deserializer<'de>, +// { +// let s = String::deserialize(deserializer)?; // -// } -// } - -// impl ProveDelegaton for DynamicJs { -// type Error = anyhow::Error; -// -// fn prove(&self, proof: &DynamicJs) -> Result { -// todo!() +// Cid::from_str(&s) +// .map(CidString) +// .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) // } // } // - -pub mod ability; -pub mod capsule; -pub mod delegate; -pub mod invoke; -pub mod ipld; -pub mod nonce; -pub mod number; -pub mod promise; -pub mod proof; -pub mod respond; -pub mod signature; -pub mod task; - -/// The empty fact -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct EmptyFact {} - -/// The default fact -pub type DefaultFact = EmptyFact; - -/// A newtype around Cid that (de)serializes as a string -#[derive(Debug, Clone)] -pub struct CidString(pub(crate) Cid); - -impl Serialize for CidString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.0.to_string().as_str()) - } -} - -impl<'de> Deserialize<'de> for CidString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - - Cid::from_str(&s) - .map(CidString) - .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) - } -} - -/// Test utilities. -#[cfg(any(test, feature = "test_utils"))] -#[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] -pub mod test_utils; +// /// Test utilities. +// #[cfg(any(test, feature = "test_utils"))] +// #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] +// pub mod test_utils; diff --git a/src/promise.rs b/src/promise.rs index f3189f1d..e6f63510 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -4,7 +4,7 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -// FIXME move under invoke? +// FIXME move under invocation? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] diff --git a/src/respond.rs b/src/receipt.rs similarity index 50% rename from src/respond.rs rename to src/receipt.rs index 2cd81284..1b5284cf 100644 --- a/src/respond.rs +++ b/src/receipt.rs @@ -1,9 +1,9 @@ mod payload; +mod receipt; mod responds; +mod store; pub use payload::Payload; +pub use receipt::Receipt; pub use responds::Responds; - -use crate::signature; - -pub type Receipt = signature::Envelope>; +pub use store::Store; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs new file mode 100644 index 00000000..f428da9e --- /dev/null +++ b/src/receipt/payload.rs @@ -0,0 +1,135 @@ +use super::responds::Responds; +use crate::{ + ability::arguments::Arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp, +}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use std::{collections::BTreeMap, fmt::Debug}; + +// FIXME serialize/deseialize split out for when the T has implementations + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: Did, + + pub ran: Cid, + pub out: Result, + pub next: Vec, // FIXME rename here or in spec? + + pub proofs: Vec, + pub metadata: BTreeMap, + + pub nonce: Nonce, + pub issued_at: Option, +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version +} + +impl Serialize for Payload +where + Payload: Clone, + T::Success: Serialize + DeserializeOwned, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(self.clone()); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +where + as TryFrom>>::Error: Debug, + T::Success: Serialize + DeserializeOwned, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => Ok(s.into()), + } + } +} + +impl TryFrom for Payload +where + T::Success: Serialize + DeserializeOwned, +{ + type Error = (); // FIXME serde error + + fn try_from(ipld: Ipld) -> Result { + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + Ok(s.into()) + } +} + +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct InternalSerializer +where + T::Success: Serialize + DeserializeOwned, +{ + #[serde(rename = "iss")] + issuer: Did, + + ran: Cid, + out: Result, + next: Vec, // FIXME rename here or in spec? + + #[serde(rename = "prf")] + proofs: Vec, + #[serde(rename = "meta")] + metadata: BTreeMap, + + nonce: Nonce, + #[serde(rename = "iat")] + issued_at: Option, +} + +impl From> for Payload +where + T::Success: Serialize + DeserializeOwned, +{ + fn from(s: InternalSerializer) -> Self { + Payload { + issuer: s.issuer, + ran: s.ran, + out: s.out, + next: s.next, + proofs: s.proofs, + metadata: s.metadata, + nonce: s.nonce, + issued_at: s.issued_at, + } + } +} + +impl From> for InternalSerializer +where + T::Success: Serialize + DeserializeOwned, +{ + fn from(s: Payload) -> Self { + InternalSerializer { + issuer: s.issuer, + ran: s.ran, + out: s.out, + next: s.next, + proofs: s.proofs, + metadata: s.metadata, + nonce: s.nonce, + issued_at: s.issued_at, + } + } +} diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs new file mode 100644 index 00000000..dfd1db01 --- /dev/null +++ b/src/receipt/receipt.rs @@ -0,0 +1,4 @@ +use super::payload::Payload; +use crate::signature; + +pub type Receipt = signature::Envelope>; diff --git a/src/respond/responds.rs b/src/receipt/responds.rs similarity index 83% rename from src/respond/responds.rs rename to src/receipt/responds.rs index 4bd6dfee..e35f5d60 100644 --- a/src/respond/responds.rs +++ b/src/receipt/responds.rs @@ -10,6 +10,4 @@ pub trait Responds { cid: self.to_task(subject, nonce).into(), } } - - // fn lookup(id: TaskId>) -> Result; } diff --git a/src/receipt/store.rs b/src/receipt/store.rs new file mode 100644 index 00000000..d7316753 --- /dev/null +++ b/src/receipt/store.rs @@ -0,0 +1,16 @@ +use super::{Receipt, Responds}; +use crate::task; +use libipld_core::ipld::Ipld; + +pub trait Store { + type Abilities: Responds; + type Error; + + fn get(id: task::Id) -> Result, Self::Error> + where + ::Success: TryFrom; + + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + where + ::Success: Into; +} diff --git a/src/respond/payload.rs b/src/respond/payload.rs deleted file mode 100644 index b6374f26..00000000 --- a/src/respond/payload.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::responds::Responds; -use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload -where - T::Success: Serialize + DeserializeOwned, -{ - pub issuer: Did, - - pub ran: Cid, - pub out: Result>, - pub next: Vec, - - pub proofs: Vec, - pub metadata: BTreeMap, - - pub nonce: Nonce, - pub issued_at: Option, -} - -impl Capsule for Payload -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version -} - -impl TryFrom for Payload -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl From> for Ipld -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - fn from(payload: Payload) -> Self { - payload.into() - } -} From 406b72dd19d05ffbed14172f12bf0a484db26605 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 00:52:33 -0800 Subject: [PATCH 098/234] Roughly completed the metadata subsystem --- src/delegation.rs | 2 +- src/delegation/payload.rs | 66 ++++++----- src/lib.rs | 1 + src/metadata.rs | 238 ++++++++++++++++++++++++++++++++++++++ src/promise.rs | 18 ++- src/receipt/payload.rs | 48 +++++--- src/receipt/receipt.rs | 2 +- src/receipt/store.rs | 13 +-- 8 files changed, 334 insertions(+), 54 deletions(-) create mode 100644 src/metadata.rs diff --git a/src/delegation.rs b/src/delegation.rs index 52c79c97..945f90f7 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -15,6 +15,6 @@ use crate::signature; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; // FIXME add a store with delegation indexing diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2f5ad289..394adc42 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,6 +5,8 @@ use crate::{ did::Did, invocation, invocation::Resolvable, + metadata as meta, + metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -19,7 +21,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -27,21 +29,21 @@ pub struct Payload { pub ability_builder: T::Builder, pub conditions: Vec, - pub metadata: BTreeMap, + pub metadata: Metadata, pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where @@ -52,10 +54,11 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::Entries> Deserialize<'de> + for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -70,9 +73,10 @@ where } } -impl TryFrom for Payload +impl TryFrom + for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -82,16 +86,18 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> + Payload +{ pub fn check( invoked: &'a invocation::Payload, // FIXME promisory version - proofs: Vec>, + proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where @@ -147,9 +153,9 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::Entries>( prev: &Acc<'a, T>, - proof: &Payload, + proof: &Payload, invoked_ipld: &Ipld, now: SystemTime, ) -> Outcome<(), (), ()> @@ -234,11 +240,13 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer +impl, E: meta::Entries + Clone> + From> for InternalSerializer where BTreeMap: From, + Metadata: Mergable, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -248,7 +256,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata, + metadata: payload.metadata.merge(), nonce: payload.nonce, not_before: payload.not_before, @@ -265,10 +273,12 @@ impl TryFrom for InternalSerializer { } } -impl> TryFrom for Payload { +impl, E: meta::Entries + Clone> TryFrom + for Payload +{ type Error = (); // FIXME - fn try_from(s: InternalSerializer) -> Result, ()> { + fn try_from(s: InternalSerializer) -> Result, ()> { Ok(Payload { issuer: s.issuer, subject: s.subject, @@ -289,7 +299,7 @@ impl> TryFrom for Payload> TryFrom for Payload> From> for InternalSerializer { - fn from(p: Payload) -> Self { +impl, E: meta::Entries + Clone> From> + for InternalSerializer +where + Metadata: Mergable, +{ + fn from(p: Payload) -> Self { InternalSerializer { issuer: p.issuer, subject: p.subject, @@ -309,7 +323,7 @@ impl> From> for InternalS arguments: p.ability_builder.args, conditions: p.conditions.into_iter().map(|c| c.into()).collect(), - metadata: p.metadata, + metadata: p.metadata.merge(), nonce: p.nonce, not_before: p.not_before, diff --git a/src/lib.rs b/src/lib.rs index f8eb43d3..0c24a0e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; +pub mod metadata; pub mod nonce; pub mod number; pub mod promise; diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 00000000..da14890e --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,238 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize, Serializer}; +use std::collections::BTreeMap; + +// FIXME rename modeule to metadata + +pub trait Entry { + const KEY: &'static str; +} + +pub trait Entries: TryFrom + Into { + const KEYS: &'static [&'static str]; +} + +pub trait Meta {} +impl Meta for T {} + +pub enum Empty {} +impl Meta for Empty {} + +// NOTE no Serde +#[derive(Debug, Clone, PartialEq)] +pub struct Metadata { + known: BTreeMap, + unknown: BTreeMap, +} + +impl From> for Ipld { + fn from(meta: Metadata) -> Ipld { + Ipld::Map(meta.unknown) + } +} + +impl TryFrom for Metadata { + type Error = (); + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + match ipld { + Ipld::Map(unknown) => Ok(Metadata { + known: BTreeMap::new(), + unknown, + }), + _ => Err(()), + } + } +} + +impl Serialize for Metadata { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = Ipld::from(*self); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ipld::deserialize(d).map(Metadata::from) + } +} + +impl TryFrom> for Ipld { + type Error = String; // FIXME + + fn try_from(meta: Metadata) -> Result { + let mut btree = meta.unknown.clone(); + + for (k, v) in meta.known { + if let Some(_) = meta.unknown.get(&k) { + return Err(k); + } + + btree.insert(k, v.into()); + } + + Ok(Ipld::Map(btree)) + } +} + +impl TryFrom for Metadata { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Map(btree) => { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in btree { + if T::KEYS.contains(&k.as_str()) { + if let Ok(fact) = T::try_from(v.clone()) { + known.insert(k, fact); + } else { + unknown.insert(k, v); + } + } else { + unknown.insert(k, v); + } + } + + Ok(Self { known, unknown }) + } + _ => Err(()), + } + } +} + +impl Metadata { + pub fn new( + known: BTreeMap, + unknown: BTreeMap, + ) -> Result { + for k in known.keys() { + if unknown.contains_key(k) { + return Err(k.into()); + } + } + + Ok(Self { known, unknown }) + } + + pub fn known<'a>(&'a self) -> &'a BTreeMap { + &self.known + } + + pub fn unknown<'a>(&'a self) -> &'a BTreeMap { + &self.unknown + } + + // FIXME types + pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Result<(), Option> { + if let Some(_) = self.unknown.get(&key) { + return Err(None); + } + + match self.known.insert(key, value) { + Some(v) => Err(Some(v)), + _ => Ok(()), + } + } + + pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Result<(), Option> { + if let Some(_) = self.known.get(&key) { + return Err(None); + } + + match self.unknown.insert(key, value) { + Some(v) => Err(Some(v)), + _ => Ok(()), + } + } +} + +pub trait Mergable { + fn merge(&self) -> BTreeMap; + fn extract(merged: BTreeMap) -> Self; +} + +impl Mergable for Metadata { + fn merge(&self) -> BTreeMap { + self.unknown + } + + // FIXME better error + fn extract(unknown: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown, + } + } +} + +impl Mergable for Metadata { + fn merge(&self) -> BTreeMap { + let mut meta = self.unknown().clone(); + + for (k, v) in self.known() { + meta.insert(k.clone(), v.clone().into()); + } + + meta + } + + // FIXME better error + fn extract(merged: BTreeMap) -> Self { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in merged { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } + } + + Metadata { known, unknown } + } +} + +impl Default for Metadata { + fn default() -> Self { + Metadata { + known: BTreeMap::new(), + unknown: BTreeMap::new(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct IpvmConfig { + pub max_retries: u32, + pub workflow_fuel: u32, +} + +impl Entry for IpvmConfig { + const KEY: &'static str = "ipvm/config"; +} + +impl From for Ipld { + fn from(config: IpvmConfig) -> Self { + config.into() + } +} + +impl TryFrom for IpvmConfig { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/promise.rs b/src/promise.rs index e6f63510..f2920a58 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -2,7 +2,7 @@ use crate::ability::arguments::Arguments; use cid::Cid; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::{collections::BTreeMap, fmt::Debug}; // FIXME move under invocation? @@ -95,6 +95,20 @@ impl TryFrom for Selector { impl From for Arguments { fn from(selector: Selector) -> Self { - todo!() // Arguments(selector.to_string()) + let mut btree = BTreeMap::new(); + + match selector { + Selector::Any { any } => { + btree.insert("ucan/*".into(), any.into()); + } + Selector::Ok { ok } => { + btree.insert("ucan/ok".into(), ok.into()); + } + Selector::Err { err } => { + btree.insert("ucan/err".into(), err.into()); + } + } + + Arguments(btree) } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index f428da9e..0600528d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,6 +1,12 @@ use super::responds::Responds; use crate::{ - ability::arguments::Arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp, + ability::arguments::Arguments, + capsule::Capsule, + did::Did, + metadata as meta, + metadata::{Mergable, Metadata}, + nonce::Nonce, + time::Timestamp, }; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; @@ -9,7 +15,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, @@ -17,19 +23,19 @@ pub struct Payload { pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, - pub metadata: BTreeMap, + pub metadata: Metadata, pub nonce: Nonce, pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, + Payload: Clone, T::Success: Serialize + DeserializeOwned, { fn serialize(&self, serializer: S) -> Result @@ -41,10 +47,14 @@ where } } -impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +impl< + 'de, + T: Responds + Deserialize<'de>, + E: meta::Entries + Clone + DeserializeOwned + Serialize, + > Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, - T::Success: Serialize + DeserializeOwned, + as TryFrom>>::Error: Debug, + T::Success: DeserializeOwned + Serialize, { fn deserialize(d: D) -> Result where @@ -57,7 +67,8 @@ where } } -impl TryFrom for Payload +impl TryFrom + for Payload where T::Success: Serialize + DeserializeOwned, { @@ -69,8 +80,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -98,9 +109,11 @@ where issued_at: Option, } -impl From> for Payload +impl From> + for Payload where T::Success: Serialize + DeserializeOwned, + Metadata: Mergable, { fn from(s: InternalSerializer) -> Self { Payload { @@ -109,25 +122,26 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata, + metadata: Metadata::extract(s.metadata), nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where T::Success: Serialize + DeserializeOwned, + Metadata: Mergable, { - fn from(s: Payload) -> Self { + fn from(s: Payload) -> Self { InternalSerializer { issuer: s.issuer, ran: s.ran, out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata, + metadata: s.metadata.merge(), nonce: s.nonce, issued_at: s.issued_at, } diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs index dfd1db01..594bf3a4 100644 --- a/src/receipt/receipt.rs +++ b/src/receipt/receipt.rs @@ -1,4 +1,4 @@ use super::payload::Payload; use crate::signature; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index d7316753..76d5ebc9 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,16 +1,15 @@ use super::{Receipt, Responds}; -use crate::task; +use crate::{metadata, task}; use libipld_core::ipld::Ipld; -pub trait Store { - type Abilities: Responds; +pub trait Store { type Error; - fn get(id: task::Id) -> Result, Self::Error> + fn get(id: task::Id) -> Result, Self::Error> where - ::Success: TryFrom; + ::Success: TryFrom; - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where - ::Success: Into; + ::Success: Into; } From 5ec4b9f171d0d0f8545e6f8bada32505ac5d4966 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 13:08:55 -0800 Subject: [PATCH 099/234] Save ahead of wasming --- src/metadata.rs | 147 ++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 81 deletions(-) diff --git a/src/metadata.rs b/src/metadata.rs index da14890e..a16e2f97 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,6 +1,6 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::Infallible}; // FIXME rename modeule to metadata @@ -12,59 +12,72 @@ pub trait Entries: TryFrom + Into { const KEYS: &'static [&'static str]; } -pub trait Meta {} -impl Meta for T {} +pub trait Mergable { + fn merge(self) -> BTreeMap; + fn extract(merged: BTreeMap) -> Self; +} pub enum Empty {} -impl Meta for Empty {} // NOTE no Serde #[derive(Debug, Clone, PartialEq)] -pub struct Metadata { +pub struct Metadata { known: BTreeMap, unknown: BTreeMap, } -impl From> for Ipld { - fn from(meta: Metadata) -> Ipld { - Ipld::Map(meta.unknown) +impl Mergable for Metadata { + fn merge(self) -> BTreeMap { + self.unknown + } + + // FIXME better error + fn extract(unknown: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown, + } } } -impl TryFrom for Metadata { - type Error = (); +impl Mergable for Metadata { + fn merge(self) -> BTreeMap { + let mut meta = self.unknown; - fn try_from(ipld: Ipld) -> Result, Self::Error> { - match ipld { - Ipld::Map(unknown) => Ok(Metadata { - known: BTreeMap::new(), - unknown, - }), - _ => Err(()), + // FIXME kill the clone + for (k, v) in self.known { + meta.insert(k, v.into()); } + + meta } -} -impl Serialize for Metadata { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = Ipld::from(*self); // FIXME kill that clone with tons of refs? - serde::Serialize::serialize(&s, serializer) + // FIXME better error + fn extract(merged: BTreeMap) -> Self { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in merged { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } + } + + Metadata { known, unknown } } } -impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { - fn deserialize(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ipld::deserialize(d).map(Metadata::from) +impl TryFrom> for Ipld { + type Error = Infallible; + + fn try_from(meta: Metadata) -> Result { + Ok(Ipld::Map(meta.merge())) } } -impl TryFrom> for Ipld { +impl TryFrom> for Ipld { type Error = String; // FIXME fn try_from(meta: Metadata) -> Result { @@ -82,6 +95,25 @@ impl TryFrom> for Ipld { } } +impl Serialize for Metadata { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = Ipld::Map((*self).clone().merge()); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ipld::deserialize(d).and_then(|ipld| ipld.try_into().map_err(|_| todo!())) + } +} + impl TryFrom for Metadata { type Error = (); // FIXME @@ -110,7 +142,7 @@ impl TryFrom for Metadata { } } -impl Metadata { +impl Metadata { pub fn new( known: BTreeMap, unknown: BTreeMap, @@ -156,54 +188,7 @@ impl Metadata { } } -pub trait Mergable { - fn merge(&self) -> BTreeMap; - fn extract(merged: BTreeMap) -> Self; -} - -impl Mergable for Metadata { - fn merge(&self) -> BTreeMap { - self.unknown - } - - // FIXME better error - fn extract(unknown: BTreeMap) -> Self { - Metadata { - known: BTreeMap::new(), - unknown, - } - } -} - -impl Mergable for Metadata { - fn merge(&self) -> BTreeMap { - let mut meta = self.unknown().clone(); - - for (k, v) in self.known() { - meta.insert(k.clone(), v.clone().into()); - } - - meta - } - - // FIXME better error - fn extract(merged: BTreeMap) -> Self { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in merged { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); - } else { - unknown.insert(k, v); - } - } - - Metadata { known, unknown } - } -} - -impl Default for Metadata { +impl Default for Metadata { fn default() -> Self { Metadata { known: BTreeMap::new(), From d8ebca2e432ae327a06c4e23ce115c0cb6a6d4e1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 18:13:45 -0800 Subject: [PATCH 100/234] Save point before riping out pointers --- Cargo.toml | 22 ++-- src/ability.rs | 4 +- src/ability/arguments.rs | 8 ++ src/ability/command.rs | 4 +- src/ability/crud/destroy.rs | 4 +- src/ability/dynamic.rs | 195 ++++++++++++++++++++++++++++++------ src/ability/internal.rs | 1 - src/delegation/payload.rs | 117 +++++++++++----------- src/invocation/payload.rs | 92 ++++++++--------- src/ipld.rs | 83 ++++++++++++--- src/lib.rs | 1 + src/new_wasm.rs | 7 ++ src/nonce.rs | 18 +++- src/promise.rs | 3 +- src/task.rs | 10 +- src/time.rs | 78 +++++++++++---- 16 files changed, 465 insertions(+), 182 deletions(-) delete mode 100644 src/ability/internal.rs create mode 100644 src/new_wasm.rs diff --git a/Cargo.toml b/Cargo.toml index 39aae0a3..72735375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,8 @@ async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" -cid = "0.10" +# FIXME attempt to remove +cid = "0.11" did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" @@ -49,7 +50,6 @@ lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multibase = "0.9.1" -multihash = {version = "0.19", features = ["serde"]} p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } @@ -66,6 +66,7 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = { version = "2.5", features = ["serde"] } +uuid = "1.7" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -74,12 +75,13 @@ linkme = "0.3.15" uuid = { version = "1.7", features = ["v4"] } xid = "1.0" +# FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = { version = "0.1" } -getrandom = { version = "*", features = ["js"] } +getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } -serde-wasm-bindgen = "0.6.1" -wasm-bindgen = "0.2.87" +serde-wasm-bindgen = "0.6" +wasm-bindgen = "0.2" wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } @@ -96,7 +98,15 @@ proptest = { version = "*", default-features = false, features = ["std"] } wasm-bindgen-test = "0.2" [features] -default = ["did-key", "eddsa-verifier", "es256-verifier", "es256k-verifier", "es384-verifier", "ps256-verifier", "rs256-verifier"] +default = [ + "did-key", + "eddsa-verifier", + "es256-verifier", + "es256k-verifier", + "es384-verifier", + "ps256-verifier", + "rs256-verifier", +] test_utils = ["proptest"] did-key = [] eddsa = ["dep:ed25519", "dep:ed25519-dalek"] diff --git a/src/ability.rs b/src/ability.rs index 48abc10d..f0fd7c0c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -7,8 +7,8 @@ pub mod wasm; pub mod arguments; pub mod command; -// TODO move to crate::wasm? -// #[cfg(feature = "wasm")] +// // TODO move to crate::wasm? or hide behind feature flag? +#[cfg(target_arch = "wasm32")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 67d6baf6..0d02781a 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -9,6 +9,14 @@ impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { Arguments(iterable.into_iter().collect()) } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() + } } impl Arguments { diff --git a/src/ability/command.rs b/src/ability/command.rs index f06637fc..bd86bf36 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -2,8 +2,8 @@ pub trait Command { const COMMAND: &'static str; } -// NOTE do not export -pub(crate) trait ToCommand { +// NOTE do not export // FIXME move to internal? +pub(super) trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 91ac42fe..4432d2e2 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -51,8 +51,8 @@ impl CheckParents for Destroy { fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + Mutable::Mutate(mutate) => Ok(()), // FIXME + Mutable::Any(any) => Ok(()), // FIXME } } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 4cc68940..0b29664a 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,67 +1,200 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{delegation::Delegatable, invocation::Resolvable, promise::Promise}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; - -// FIXME move commented-out module? -// use js_sys; -// use wasm_bindgen::prelude::*; -// type JsDynamic = Dynamic<&'a js_sys::Function>; -// type JsBuilder = Builder<&'a js_sys::Function>; -// type JsPromised = Promised<&'a js_sys::Function>; -// FIXME move these fiels to a wrapper struct in a different module -// #[serde(skip_serializing)] -// pub chain_validator: Pred, -// #[serde(skip_serializing)] -// pub shape_validator: Pred, -// #[serde(skip_serializing)] -// pub serialize_nonce: DefaultTrue, +use crate::{ + delegation::Delegatable, + invocation::Resolvable, + promise::Promise, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, + same::CheckSame, + }, + task::DefaultTrue, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{ + de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt::Debug}; +use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Generic { +#[derive(Clone, PartialEq)] +pub struct Generic { pub cmd: String, pub args: Args, + pub is_nonce_meaningful: DefaultTrue, + + pub same_validator: F, + pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void + pub shape_validator: F, // FIXME needs to be a different type +} + +impl Debug for Generic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Generic") + .field("cmd", &self.cmd) + .field("args", &self.args) + .field("is_nonce_meaningful", &self.is_nonce_meaningful) + .finish() + } +} + +pub type Dynamic = Generic; +pub type Promised = Generic, F>; + +impl Serialize for Generic +where + Arguments: From, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("cmd", &self.cmd)?; + map.serialize_entry("args", &Arguments::from(self.args.clone()))?; + map.end() + } } -pub type Dynamic = Generic; -pub type Promised = Generic>; +impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + // FIXME + todo!() + // let btree = BTreeMap::deserialize(deserializer)?; + // Ok(Generic { + // cmd: btree.get("cmd")?.to_string(), + // args: btree.get("args")?.clone(), + // is_nonce_meaningful: DefaultTrue::default(), + // + // same_validator: (), + // parent_validator: (), + // shape_validator: (), + // }) + } +} -impl ToCommand for Generic { +impl ToCommand for Generic { fn to_command(&self) -> String { self.cmd.clone() } } -impl Delegatable for Dynamic { - type Builder = Dynamic; +impl Delegatable for Dynamic { + type Builder = Dynamic; } -impl Resolvable for Dynamic { - type Promised = Dynamic; +impl Resolvable for Dynamic { + type Promised = Promised; } -impl From for Arguments { - fn from(dynamic: Dynamic) -> Self { - dynamic.args +impl From> for Ipld { + fn from(generic: Generic) -> Self { + generic.into() + } +} + +impl TryFrom for Generic { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } -impl TryFrom for Dynamic { +impl, F> From> for Arguments { + fn from(generic: Generic) -> Self { + generic.args.into() + } +} + +impl TryFrom> for Dynamic { type Error = (); // FIXME - fn try_from(awaiting: Promised) -> Result { + fn try_from(awaiting: Promised) -> Result { if let Promise::Resolved(args) = &awaiting.args { Ok(Dynamic { cmd: awaiting.cmd, args: args.clone(), + + same_validator: awaiting.same_validator, + parent_validator: awaiting.parent_validator, + shape_validator: awaiting.shape_validator, + is_nonce_meaningful: awaiting.is_nonce_meaningful, }) } else { Err(()) } } } + +impl From> for Promised { + fn from(d: Dynamic) -> Self { + Promised { + cmd: d.cmd, + args: Promise::Resolved(d.args), + + same_validator: d.same_validator, + parent_validator: d.parent_validator, + shape_validator: d.shape_validator, + is_nonce_meaningful: d.is_nonce_meaningful, + } + } +} + +impl Checkable for Dynamic +where + F: Fn(&String, &Arguments) -> Result<(), String>, +{ + type Hierarchy = Parentless>; // FIXME I bet we can revover parents +} + +// FIXME Actually, we shoudl go back to wrapping? +// impl CheckSame for Dynamic +// where +// F: Fn(&String, &Arguments) -> Result<(), String>, +// { +// type Error = String; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// let chain_checker = &self.same_validator; +// let shape_checker = &self.same_validator; +// +// shape_checker(&proof.cmd, &proof.args)?; +// chain_checker(&proof.cmd, &proof.args) +// } +// } + +// #[wasm_bindgen(module = "./ability")] +// extern "C" { +// type JsAbility; +// +// // FIXME wrap in func that checks the jsval or better: converts form Ipld +// #[wasm_bindgen(constructor)] +// fn new(cmd: String, args: BTreeMap) -> JsAbility; +// +// #[wasm_bindgen(method, getter)] +// fn command(this: &JsAbility) -> String; +// +// #[wasm_bindgen(method, getter)] +// fn arguments(this: &JsAbility) -> Arguments; +// +// #[wasm_bindgen(method, getter)] +// fn is_nonce_meaningful(this: &JsAbility) -> bool; +// +// // e.g. reject extra fields +// #[wasm_bindgen(method)] +// fn validate_shape(this: &JsAbility) -> bool; +// +// // FIXME camels to snakes +// #[wasm_bindgen(method)] +// fn check_same(this: &JsAbility, proof: &JsAbility) -> Result<(), String>; +// +// fn check_parents(th.......) +// } diff --git a/src/ability/internal.rs b/src/ability/internal.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/ability/internal.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 394adc42..2fb55124 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -273,61 +273,62 @@ impl TryFrom for InternalSerializer { } } -impl, E: meta::Entries + Clone> TryFrom - for Payload -{ - type Error = (); // FIXME - - fn try_from(s: InternalSerializer) -> Result, ()> { - Ok(Payload { - issuer: s.issuer, - subject: s.subject, - audience: s.audience, - - ability_builder: dynamic::Dynamic { - cmd: s.command, - args: s.arguments, - }, - conditions: s - .conditions - .iter() - .try_fold(Vec::new(), |mut acc, c| { - C::try_from(c.clone()).map(|x| { - acc.push(x); - acc - }) - }) - .map_err(|_| ())?, // FIXME better error (collect all errors - - metadata: Metadata::extract(s.metadata), - nonce: s.nonce, - - not_before: s.not_before, - expiration: s.expiration, - }) - } -} - -impl, E: meta::Entries + Clone> From> - for InternalSerializer -where - Metadata: Mergable, -{ - fn from(p: Payload) -> Self { - InternalSerializer { - issuer: p.issuer, - subject: p.subject, - audience: p.audience, - - command: p.ability_builder.cmd, - arguments: p.ability_builder.args, - conditions: p.conditions.into_iter().map(|c| c.into()).collect(), - - metadata: p.metadata.merge(), - nonce: p.nonce, - - not_before: p.not_before, - expiration: p.expiration, - } - } -} +// FIXME +// impl, E: meta::Entries + Clone> TryFrom +// for Payload, C, E> +// { +// type Error = (); // FIXME +// +// fn try_from(s: InternalSerializer) -> Result, C, E>, ()> { +// Ok(Payload { +// issuer: s.issuer, +// subject: s.subject, +// audience: s.audience, +// +// ability_builder: dynamic::Dynamic { +// cmd: s.command, +// args: s.arguments, +// }, +// conditions: s +// .conditions +// .iter() +// .try_fold(Vec::new(), |mut acc, c| { +// C::try_from(c.clone()).map(|x| { +// acc.push(x); +// acc +// }) +// }) +// .map_err(|_| ())?, // FIXME better error (collect all errors +// +// metadata: Metadata::extract(s.metadata), +// nonce: s.nonce, +// +// not_before: s.not_before, +// expiration: s.expiration, +// }) +// } +// } +// +// impl, E: meta::Entries + Clone, F> +// From, C, E>> for InternalSerializer +// where +// Metadata: Mergable, +// { +// fn from(p: Payload, C, E>) -> Self { +// InternalSerializer { +// issuer: p.issuer, +// subject: p.subject, +// audience: p.audience, +// +// command: p.ability_builder.cmd, +// arguments: p.ability_builder.args, +// conditions: p.conditions.into_iter().map(|c| c.into()).collect(), +// +// metadata: p.metadata.merge(), +// nonce: p.nonce, +// +// not_before: p.not_before, +// expiration: p.expiration, +// } +// } +// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 46fc3d41..5e8ea6a8 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -145,51 +145,53 @@ impl TryFrom for InternalSerializer { } } -impl From for Payload { - fn from(s: InternalSerializer) -> Self { - Payload { - issuer: s.issuer, - subject: s.subject, - audience: s.audience, - - ability: dynamic::Dynamic { - cmd: s.command, - args: s.arguments.into(), - }, - - proofs: s.proofs, - cause: s.cause, - metadata: s.metadata, - - nonce: s.nonce, - - not_before: s.not_before, - expiration: s.expiration, - } - } -} - -impl From> for InternalSerializer { - fn from(p: Payload) -> Self { - InternalSerializer { - issuer: p.issuer, - subject: p.subject, - audience: p.audience, - - command: p.ability.cmd, - arguments: p.ability.args, - - proofs: p.proofs, - cause: p.cause, - metadata: p.metadata, - - nonce: p.nonce, - - not_before: p.not_before, - expiration: p.expiration, - } - } -} +// FIXME +// impl From for Payload { +// fn from(s: InternalSerializer) -> Self { +// Payload { +// issuer: s.issuer, +// subject: s.subject, +// audience: s.audience, +// +// ability: dynamic::Dynamic { +// cmd: s.command, +// args: s.arguments.into(), +// }, +// +// proofs: s.proofs, +// cause: s.cause, +// metadata: s.metadata, +// +// nonce: s.nonce, +// +// not_before: s.not_before, +// expiration: s.expiration, +// } +// } +// } + +// FIXME +// impl From> for InternalSerializer { +// fn from(p: Payload) -> Self { +// InternalSerializer { +// issuer: p.issuer, +// subject: p.subject, +// audience: p.audience, +// +// command: p.ability.cmd, +// arguments: p.ability.args, +// +// proofs: p.proofs, +// cause: p.cause, +// metadata: p.metadata, +// +// nonce: p.nonce, +// +// not_before: p.not_before, +// expiration: p.expiration, +// } +// } +// } impl> From> for InternalSerializer { fn from(payload: Payload) -> Self { diff --git a/src/ipld.rs b/src/ipld.rs index a820acaf..2a1d213b 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,7 +1,16 @@ use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use libipld_core::cid::Cid; + +#[cfg(target_arch = "wasm32")] +use libipld_core::multihash::MultihashGeneric; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Uint8Array}; pub struct Newtype(pub Ipld); @@ -17,32 +26,80 @@ impl From for Ipld { } } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CidWrapper { + version_code: u64, + multicodec_code: u64, + multihash_code: u64, + hash_bytes: Vec, +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: CidWrapper) -> Self { + Cid::new( + wrapper.version_code.try_into().expect( + "must be a valid Cid::Version because it was deconstructed from a valid one", + ), + wrapper + .multicodec_code + .try_into() + .expect("must be a valid Multicodec because it was deconstructed from a valid one"), + MultihashGeneric::<64>::wrap(wrapper.multihash_code, wrapper.hash_bytes.as_ref()) + .expect("a valid Multihash because it was deconstructed from a valid one") + .into(), + ) + .expect("the only way to get a CidWrapper is by deconstructing a valid Cid") + } +} + +#[cfg(target_arch = "wasm32")] +impl From for CidWrapper { + fn from(cid: Cid) -> Self { + Self { + version_code: cid.version().into(), + multicodec_code: cid.codec().into(), + multihash_code: cid.hash().code(), + hash_bytes: cid.hash().digest().to_vec(), + } + } +} + // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { - fn from(wrapped: WrappedIpld) -> Self { + fn from(wrapped: Newtype) -> Self { match wrapped.0 { - Ipld::Null => JsValue::Null, + Ipld::Null => JsValue::NULL, Ipld::Bool(b) => JsValue::from(b), Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(b) => JsValue::from(b), - Ipld::List(l) => { - let mut vec = Vec::new(); - for ipld in l { - vec.push(JsValue::from(ipld)); + Ipld::Bytes(bs) => { + let buffer = Uint8Array::new(&bs.len().into()); + for (index, item) in bs.iter().enumerate() { + buffer.set_index(index as u32, *item); + } + JsValue::from(buffer) + } + Ipld::List(ls) => { + let mut arr = Array::new(); + for ipld in ls { + arr.push(&JsValue::from(Newtype(ipld))); } - JsValue::from(vec) + JsValue::from(arr) } Ipld::Map(m) => { - let mut map = JsValue::new(); + let mut map = Map::new(); for (k, v) in m { - map.set(&k, JsValue::from(v)); + map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); } - map + JsValue::from(map) } - Ipld::Link(l) => JsValue::from(Link::from(l)), + // FIXME unclear if this is the correct approach, since the object loses + // a bunch of info (I presume) -- the JsIpld enum above may be better? Or a class? + Ipld::Link(cid) => CidWrapper::from(cid).into(), } } } diff --git a/src/lib.rs b/src/lib.rs index 0c24a0e6..0cb8c3e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ pub mod did; pub mod invocation; pub mod ipld; pub mod metadata; +pub mod new_wasm; pub mod nonce; pub mod number; pub mod promise; diff --git a/src/new_wasm.rs b/src/new_wasm.rs new file mode 100644 index 00000000..cc8066fe --- /dev/null +++ b/src/new_wasm.rs @@ -0,0 +1,7 @@ +use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; +use js_sys; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +// #[wasm_bindgen] +// type JsDynamic = Dynamic; diff --git a/src/nonce.rs b/src/nonce.rs index 74355690..bf64784f 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -3,8 +3,13 @@ use enum_as_inner::EnumAsInner; use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; use serde::{Deserialize, Serialize}; use std::fmt; + +#[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; +#[cfg(target_arch = "wasm32")] +use web_sys; + // FIXME pub struct Unit; // FIXME @@ -22,13 +27,24 @@ pub enum Nonce { } impl Nonce { + // pub fn new() -> Self { + // Self::generate_96() + // } + + // #[cfg(target_arch = "wasm32")] + // pub fn gen_wasm_96() -> Self { + // web_sys::Crypto::get_random_values_with_u8_array() + // } + /// Default generator, outputting an [`xid`] nonce, /// which is a 96-bit (12-byte) nonce. - pub fn generate() -> Self { + #[cfg(not(target_arch = "wasm32"))] + pub fn generate_96() -> Self { Nonce::Nonce96(*xid::new().as_bytes()) } /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. + #[cfg(not(target_arch = "wasm32"))] pub fn generate_128() -> Self { Nonce::Nonce128(*Uuid::new_v4().as_bytes()) } diff --git a/src/promise.rs b/src/promise.rs index f2920a58..3a0792cc 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,6 +1,5 @@ use crate::ability::arguments::Arguments; -use cid::Cid; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; diff --git a/src/task.rs b/src/task.rs index e79d072d..8afc7916 100644 --- a/src/task.rs +++ b/src/task.rs @@ -5,12 +5,14 @@ use libipld_core::{ codec::Encode, error::SerdeError, ipld::Ipld, - multihash::{Code::Sha2_256, MultihashDigest}, + multihash::MultihashGeneric, serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +const SHA2_256: u64 = 0x12; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -92,6 +94,10 @@ impl From for Cid { let ipld: Ipld = task.into(); ipld.encode(DagCborCodec, &mut buffer) .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), + ) } } diff --git a/src/time.rs b/src/time.rs index 29500062..291f0aa9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,8 +1,8 @@ //! Time utilities use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use web_time::{SystemTime, UNIX_EPOCH}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { @@ -12,15 +12,14 @@ pub fn now() -> u64 { .as_secs() } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Debug, Clone, PartialEq)] pub enum Timestamp { // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too - /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) - /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. - /// - /// This range can represent millions of years into the future, - /// and is thus sufficient for nearly all use cases. + // Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) + // (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. + // + // This range can represent millions of years into the future, + // and is thus sufficient for nearly all use cases. Sending(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), @@ -28,6 +27,32 @@ pub enum Timestamp { Receiving(SystemTime), } +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Timestamp::Sending(js_time) => js_time.serialize(serializer), + Timestamp::Receiving(sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if let Ok(js_time) = JsTime::deserialize(deserializer) { + return Ok(Timestamp::Sending(js_time)); + } + + todo!() + // FIXME just todo()ing this for now becuase the enum will likely go away very shortly + } +} + impl From for SystemTime { fn from(timestamp: Timestamp) -> Self { match timestamp { @@ -51,11 +76,35 @@ impl TryFrom for Timestamp { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct JsTime { time: SystemTime, } +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} + // FIXME just lifting this from Elixir for now pub struct OutOfRangeError { pub tried: SystemTime, @@ -68,12 +117,7 @@ impl JsTime { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time - .duration_since(std::time::UNIX_EPOCH) - .expect("FIXME") - .as_secs() - > 0x1FFFFFFFFFFFFF - { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { Err(OutOfRangeError { tried: time }) } else { Ok(JsTime { time }) @@ -85,7 +129,7 @@ impl From for Ipld { fn from(js_time: JsTime) -> Self { js_time .time - .duration_since(std::time::UNIX_EPOCH) + .duration_since(UNIX_EPOCH) .expect("FIXME") .as_secs() .into() From c8bfde7318a06a444f4f9d511ddda3fd406a768b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 00:43:46 -0800 Subject: [PATCH 101/234] Learning about Wasm bindgen --- src/ability.rs | 3 + src/ability/arguments.rs | 18 +-- src/ability/dynamic.rs | 303 ++++++++++++++++++-------------------- src/ability/js.rs | 213 +++++++++++++++++++++++++++ src/delegation/payload.rs | 2 +- src/invocation/payload.rs | 2 +- src/new_wasm.rs | 8 +- 7 files changed, 371 insertions(+), 178 deletions(-) create mode 100644 src/ability/js.rs diff --git a/src/ability.rs b/src/ability.rs index f0fd7c0c..a6b095ad 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -7,6 +7,9 @@ pub mod wasm; pub mod arguments; pub mod command; +#[cfg(target_arch = "wasm32")] +pub mod js; + // // TODO move to crate::wasm? or hide behind feature flag? #[cfg(target_arch = "wasm32")] pub mod dynamic; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 0d02781a..75dc7bba 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -10,22 +10,20 @@ impl Arguments { Arguments(iterable.into_iter().collect()) } - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - pub fn into_iter(self) -> impl Iterator { - self.0.into_iter() + pub fn get(&self, key: &str) -> Option<&Ipld> { + self.0.get(key) } -} -impl Arguments { pub fn insert(&mut self, key: String, value: Ipld) -> Option { self.0.insert(key, value) } - pub fn get(&self, key: &str) -> Option<&Ipld> { - self.0.get(key) + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 0b29664a..dd8bbfb1 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -21,180 +21,159 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq)] -pub struct Generic { - pub cmd: String, - pub args: Args, - pub is_nonce_meaningful: DefaultTrue, - - pub same_validator: F, - pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void - pub shape_validator: F, // FIXME needs to be a different type -} - -impl Debug for Generic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Generic") - .field("cmd", &self.cmd) - .field("args", &self.args) - .field("is_nonce_meaningful", &self.is_nonce_meaningful) - .finish() - } -} - -pub type Dynamic = Generic; -pub type Promised = Generic, F>; - -impl Serialize for Generic -where - Arguments: From, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(2))?; - map.serialize_entry("cmd", &self.cmd)?; - map.serialize_entry("args", &Arguments::from(self.args.clone()))?; - map.end() - } -} - -impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - // FIXME - todo!() - // let btree = BTreeMap::deserialize(deserializer)?; - // Ok(Generic { - // cmd: btree.get("cmd")?.to_string(), - // args: btree.get("args")?.clone(), - // is_nonce_meaningful: DefaultTrue::default(), - // - // same_validator: (), - // parent_validator: (), - // shape_validator: (), - // }) - } -} - -impl ToCommand for Generic { - fn to_command(&self) -> String { - self.cmd.clone() - } -} - -impl Delegatable for Dynamic { - type Builder = Dynamic; -} - -impl Resolvable for Dynamic { - type Promised = Promised; -} - -impl From> for Ipld { - fn from(generic: Generic) -> Self { - generic.into() - } -} - -impl TryFrom for Generic { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl, F> From> for Arguments { - fn from(generic: Generic) -> Self { - generic.args.into() - } -} - -impl TryFrom> for Dynamic { - type Error = (); // FIXME - - fn try_from(awaiting: Promised) -> Result { - if let Promise::Resolved(args) = &awaiting.args { - Ok(Dynamic { - cmd: awaiting.cmd, - args: args.clone(), - - same_validator: awaiting.same_validator, - parent_validator: awaiting.parent_validator, - shape_validator: awaiting.shape_validator, - is_nonce_meaningful: awaiting.is_nonce_meaningful, - }) - } else { - Err(()) - } - } -} - -impl From> for Promised { - fn from(d: Dynamic) -> Self { - Promised { - cmd: d.cmd, - args: Promise::Resolved(d.args), - - same_validator: d.same_validator, - parent_validator: d.parent_validator, - shape_validator: d.shape_validator, - is_nonce_meaningful: d.is_nonce_meaningful, - } - } -} - -impl Checkable for Dynamic -where - F: Fn(&String, &Arguments) -> Result<(), String>, -{ - type Hierarchy = Parentless>; // FIXME I bet we can revover parents -} +// #[derive(Clone, PartialEq)] +// pub struct Generic { +// pub cmd: String, +// pub args: Args, +// // pub is_nonce_meaningful: Fn(&String) -> bool, +// // pub same_validator: Fn(&String, &Arguments) -> Result<(), String>, +// // pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void +// // pub shape_validator: Fn(&String, &Arguments) -> Result<(), String>, // FIXME needs to be a different type +// } -// FIXME Actually, we shoudl go back to wrapping? -// impl CheckSame for Dynamic +// // pub struct DynamicValidator { +// // fn check_shape(self) -> (); +// // // fn check_same: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // // fn check_parents: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // } +// // +// // +// // +// // // FIXME Actually, we shoudl go back to wrapping? +// // // impl CheckSame for Dynamic +// // // where +// // // F: Fn(&String, &Arguments) -> Result<(), String>, +// // // { +// // // type Error = String; +// // // +// // // fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// // // let chain_checker = &self.same_validator; +// // // let shape_checker = &self.same_validator; +// // // +// // // shape_checker(&proof.cmd, &proof.args)?; +// // // chain_checker(&proof.cmd, &proof.args) +// // // } +// // // } +// +// impl Debug for Generic { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.debug_struct("Generic") +// .field("cmd", &self.cmd) +// .field("args", &self.args) +// .field("is_nonce_meaningful", &self.is_nonce_meaningful) +// .finish() +// } +// } +// +// pub type Dynamic = Generic; +// pub type Promised = Generic, F>; +// +// impl Serialize for Generic // where -// F: Fn(&String, &Arguments) -> Result<(), String>, +// Arguments: From, // { -// type Error = String; +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// let mut map = serializer.serialize_map(Some(2))?; +// map.serialize_entry("cmd", &self.cmd)?; +// map.serialize_entry("args", &Arguments::from(self.args.clone()))?; +// map.end() +// } +// } // -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// let chain_checker = &self.same_validator; -// let shape_checker = &self.same_validator; +// impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { +// fn deserialize(deserializer: D) -> Result, D::Error> +// where +// D: Deserializer<'de>, +// { +// // FIXME +// todo!() +// // let btree = BTreeMap::deserialize(deserializer)?; +// // Ok(Generic { +// // cmd: btree.get("cmd")?.to_string(), +// // args: btree.get("args")?.clone(), +// // is_nonce_meaningful: DefaultTrue::default(), +// // +// // same_validator: (), +// // parent_validator: (), +// // shape_validator: (), +// // }) +// } +// } // -// shape_checker(&proof.cmd, &proof.args)?; -// chain_checker(&proof.cmd, &proof.args) +// impl ToCommand for Generic { +// fn to_command(&self) -> String { +// self.cmd.clone() // } // } - -// #[wasm_bindgen(module = "./ability")] -// extern "C" { -// type JsAbility; // -// // FIXME wrap in func that checks the jsval or better: converts form Ipld -// #[wasm_bindgen(constructor)] -// fn new(cmd: String, args: BTreeMap) -> JsAbility; +// impl Delegatable for Dynamic { +// type Builder = Dynamic; +// } // -// #[wasm_bindgen(method, getter)] -// fn command(this: &JsAbility) -> String; +// impl Resolvable for Dynamic { +// type Promised = Promised; +// } // -// #[wasm_bindgen(method, getter)] -// fn arguments(this: &JsAbility) -> Arguments; +// impl From> for Ipld { +// fn from(generic: Generic) -> Self { +// generic.into() +// } +// } // -// #[wasm_bindgen(method, getter)] -// fn is_nonce_meaningful(this: &JsAbility) -> bool; +// impl TryFrom for Generic { +// type Error = SerdeError; // -// // e.g. reject extra fields -// #[wasm_bindgen(method)] -// fn validate_shape(this: &JsAbility) -> bool; +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld) +// } +// } // -// // FIXME camels to snakes -// #[wasm_bindgen(method)] -// fn check_same(this: &JsAbility, proof: &JsAbility) -> Result<(), String>; +// impl, F> From> for Arguments { +// fn from(generic: Generic) -> Self { +// generic.args.into() +// } +// } // -// fn check_parents(th.......) +// impl TryFrom> for Dynamic { +// type Error = (); // FIXME +// +// fn try_from(awaiting: Promised) -> Result { +// if let Promise::Resolved(args) = &awaiting.args { +// Ok(Dynamic { +// cmd: awaiting.cmd, +// args: args.clone(), +// +// same_validator: awaiting.same_validator, +// parent_validator: awaiting.parent_validator, +// shape_validator: awaiting.shape_validator, +// is_nonce_meaningful: awaiting.is_nonce_meaningful, +// }) +// } else { +// Err(()) +// } +// } +// } +// +// impl From> for Promised { +// fn from(d: Dynamic) -> Self { +// Promised { +// cmd: d.cmd, +// args: Promise::Resolved(d.args), +// +// same_validator: d.same_validator, +// parent_validator: d.parent_validator, +// shape_validator: d.shape_validator, +// is_nonce_meaningful: d.is_nonce_meaningful, +// } +// } +// } +// +// impl Checkable for Dynamic +// where +// F: Fn(&String, &Arguments) -> Result<(), String>, +// { +// type Hierarchy = Parentless>; // FIXME I bet we can revover parents // } diff --git a/src/ability/js.rs b/src/ability/js.rs new file mode 100644 index 00000000..709b69b7 --- /dev/null +++ b/src/ability/js.rs @@ -0,0 +1,213 @@ +use super::arguments::Arguments; +use crate::proof::same::CheckSame; +use js_sys::Object; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::prelude::*; + +// FIXME dynamic +pub struct Ability { + pub cmd: String, // FIXME don't need this field because it's on the validator? + // FIXME JsCast for Args or WrappedIpld, esp for Cids + pub args: BTreeMap, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args +} + +impl From for js_sys::Object { + fn from(ability: Ability) -> Self { + let args = js_sys::Map::new(); + for (k, v) in ability.args { + args.set(&k.into(), &v); + } + + let map = js_sys::Map::new(); + map.set(&"args".into(), &js_sys::Object::from(args).into()); + map.set(&"cmd".into(), &ability.cmd.into()); + map.into() + } +} + +impl TryFrom for Ability { + type Error = JsValue; + + fn try_from(map: js_sys::Map) -> Result { + if let (Some(cmd), js_args) = ( + map.get(&("cmd".into())).as_string(), + &map.get(&("args".into())), + ) { + let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; + let keys = Object::keys(obj_args); + let values = Object::values(obj_args); + + // FIXME come back when TryInto is done... if it matters + let mut args = BTreeMap::new(); + for (k, v) in keys.iter().zip(values) { + if let Some(k) = k.as_string() { + args.insert(k, v); + } else { + return Err(k); + } + } + + Ok(Ability { + cmd, + args: args.clone(), // FIXME kill clone + }) + } else { + Err(JsValue::NULL) // FIXME + } + } +} + +#[wasm_bindgen] +#[derive(Debug, Clone, PartialEq)] +pub struct Validator { + #[wasm_bindgen(skip)] + pub cmd: String, + + #[wasm_bindgen(readonly)] + pub is_nonce_meaningful: bool, + + #[wasm_bindgen(skip)] + pub validate_shape: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_same: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_parent: Option, // FIXME explore concrete types + an enum +} + +// NOTE more like a config object +#[wasm_bindgen] +impl Validator { + // FIXME wrap in func that checks the jsval or better: converts form Ipld + // FIXME notes about checking shape on the way in + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, + check_same: js_sys::Function, + check_parent: Option, + ) -> Validator { + // FIXME chec that JsErr doesn't auto-throw + Validator { + cmd, + is_nonce_meaningful, + validate_shape, + check_same, + check_parent, + } + } + + pub fn command(&self) -> String { + self.cmd.clone() + } + + // e.g. reject extra fields + pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.validate_shape.call1(&this, args)?; + Ok(()) + } + + // FIXME only dynamic? + pub fn check_same( + &self, + target: &js_sys::Object, + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.check_same.call2(&this, target, proof)?; + Ok(()) + } + + pub fn check_parents( + &self, + target: &js_sys::Object, // FIXME better type, esp for TS? + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + if let Some(checker) = &self.check_parent { + checker.call2(&this, target, proof)?; + return Ok(()); + } + + Err(this) + } +} + +pub struct Foo { + ability: Ability, + validator: Validator, +} + +impl From for Arguments { + fn from(foo: Foo) -> Self { + todo!() // FIXME + } +} + +use crate::delegation::Delegatable; + +impl Delegatable for Foo { + type Builder = Foo; +} + +impl CheckSame for Foo { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); + + let mut this_args = js_sys::Map::new(); + for (k, v) in this_it { + this_args.set(&k, v); + } + + let proof_it = proof + .ability + .args + .iter() + .map(|(k, v)| (JsValue::from(k), v)); + + let mut proof_args = js_sys::Map::new(); + for (k, v) in proof_it { + proof_args.set(&k, v); + } + + self.validator.check_same( + &Object::from_entries(&this_args)?, + &Object::from_entries(&proof_args)?, + ) + } +} + +// pub struct Ability { +// pub cmd: String, +// pub args: BTreeMap, // FIXME args +// pub val: JsValidator, +// } +// +// #[wasm_bindgen] +// impl Ability { +// #[wasm_bindgen(constructor)] +// fn new( +// cmd: String, +// args: BTreeMap, +// validator: JsValidator, +// ) -> Result { +// let args = args +// .iter() +// .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) +// .collect(); +// +// validator.check_shape(args)?; +// Ok(Ability { cmd, args, val }) +// } +// } +// +// pub struct Pipeline { +// pub validators: Vec, +// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2fb55124..2161dc63 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -222,7 +222,7 @@ struct InternalSerializer { #[serde(rename = "aud")] audience: Did, - #[serde(rename = "can")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5e8ea6a8..2ee9805c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -110,7 +110,7 @@ struct InternalSerializer { #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] audience: Option, - #[serde(rename = "do")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/new_wasm.rs b/src/new_wasm.rs index cc8066fe..760bf027 100644 --- a/src/new_wasm.rs +++ b/src/new_wasm.rs @@ -1,7 +1,7 @@ -use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; -use js_sys; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; +// use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; +// use js_sys; +// use serde::{Deserialize, Serialize}; +// use wasm_bindgen::prelude::*; // #[wasm_bindgen] // type JsDynamic = Dynamic; From f668aa6acda5cf4064aba3d5ec2c5c8a0a9fd473 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 14:21:00 -0800 Subject: [PATCH 102/234] Ipld <-> JsValue --- src/ability/js.rs | 20 ++++++-- src/ipld.rs | 122 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/ability/js.rs b/src/ability/js.rs index 709b69b7..d015dff9 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -6,11 +6,12 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; // FIXME dynamic +#[wasm_bindgen] pub struct Ability { - pub cmd: String, // FIXME don't need this field because it's on the validator? + cmd: String, // FIXME don't need this field because it's on the validator? // FIXME JsCast for Args or WrappedIpld, esp for Cids - pub args: BTreeMap, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args + args: BTreeMap, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args } impl From for js_sys::Object { @@ -78,6 +79,19 @@ pub struct Validator { pub check_parent: Option, // FIXME explore concrete types + an enum } +// Helper +pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { + // FIXME annoying number of steps... so I guess that's why they have the numbered versions... + // but those end at 3 :/ + // Hmm I guess this is reasonable, since it needs to copy the `Vec` to the JsArray + let arr = js_sys::Array::new_with_length(args.len() as u32); + for (i, arg) in args.iter().enumerate() { + arr.set(i as u32, arg.clone()); + } + + f.apply(&wasm_bindgen::JsValue::NULL, &arr) +} + // NOTE more like a config object #[wasm_bindgen] impl Validator { diff --git a/src/ipld.rs b/src/ipld.rs index 2a1d213b..ffafd65b 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -28,16 +28,35 @@ impl From for Ipld { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub struct CidWrapper { +pub struct NewCid { + // FIXME Link? version_code: u64, multicodec_code: u64, multihash_code: u64, hash_bytes: Vec, } +// FIXME better name #[cfg(target_arch = "wasm32")] -impl From for Cid { - fn from(wrapper: CidWrapper) -> Self { +#[wasm_bindgen] +impl NewCid { + pub fn from_string(cid_string: String) -> Result { + NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for NewCid { + type Error = >::Error; + + fn try_from(cid_string: String) -> Result { + Cid::try_from(cid_string).map(Into::into) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: NewCid) -> Self { Cid::new( wrapper.version_code.try_into().expect( "must be a valid Cid::Version because it was deconstructed from a valid one", @@ -50,12 +69,12 @@ impl From for Cid { .expect("a valid Multihash because it was deconstructed from a valid one") .into(), ) - .expect("the only way to get a CidWrapper is by deconstructing a valid Cid") + .expect("the only way to get a Link is by deconstructing a valid Cid") } } #[cfg(target_arch = "wasm32")] -impl From for CidWrapper { +impl From for NewCid { fn from(cid: Cid) -> Self { Self { version_code: cid.version().into(), @@ -76,13 +95,7 @@ impl From for JsValue { Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => { - let buffer = Uint8Array::new(&bs.len().into()); - for (index, item) in bs.iter().enumerate() { - buffer.set_index(index as u32, *item); - } - JsValue::from(buffer) - } + Ipld::Bytes(bs) => Bytes::from(bs).into(), Ipld::List(ls) => { let mut arr = Array::new(); for ipld in ls { @@ -97,9 +110,88 @@ impl From for JsValue { } JsValue::from(map) } - // FIXME unclear if this is the correct approach, since the object loses - // a bunch of info (I presume) -- the JsIpld enum above may be better? Or a class? - Ipld::Link(cid) => CidWrapper::from(cid).into(), + Ipld::Link(cid) => NewCid::from(cid).into(), } } } + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = (); // FIXME + + fn try_from(js_val: JsValue) -> Result { + if js_val.is_null() { + return Ok(Newtype(Ipld::Null)); + } + + if let Some(b) = js_val.as_bool() { + return Ok(Newtype(Ipld::Bool(b))); + } + + if let Some(f) = js_val.as_f64() { + return Ok(Newtype(Ipld::Float(f))); + } + + if let Some(s) = js_val.as_string() { + return Ok(Newtype(Ipld::String(s))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut ls = Vec::with_capacity(arr.length() as usize); + for a in arr.iter() { + ls.push(Newtype::try_from(a)?.into()); + } + return Ok(Newtype(Ipld::List(ls))); + } + + if let Some(bytes) = js_val.dyn_ref::() { + let arr = Uint8Array::new(&bytes.raw.len().into()); + + for (index, item) in bytes.raw.iter().enumerate() { + arr.set_index(index as u32, *item); + } + + return Ok(Newtype(Ipld::Bytes(arr))); + } + + if let Some(map) = js_val.dyn_ref::() { + let mut m = std::collections::BTreeMap::new(); + + for iter_item in map.entries() { + match iter_item { + Err(_) => return Err(()), + Ok(k) => match k.as_string() { + None => return Err(()), + Some(s) => { + m.insert(s, Newtype::try_from(v)?.0); + }, + } + } + } + + return Ok(Newtype(Ipld::Map(m))); + } + + // NOTE *must* come before `is_object` (which is hopefully below) + if let Some(link) = js_val.dyn_ref::() { + return Ok(Newtype(Ipld::Link(link.into().clone()))); + } + + if js_val.is_object() { + let mut m = std::collections::BTreeMap::new(); + for (k, v) in js_val { + m.insert(k.as_string.ok_or(())?, Newtype::try_from(v)?); + } + + return Ok(Newtype(Ipld::Map(m))); + } + + // // FIXME hmmmm + // if let Some(undefined) = js_val.is_undefined() { + // return Self(Ipld::Null); + // } + + Err(()) + } +} From 5fb875f490f7341708dc004dc5f8f353c0e5161a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 15:54:21 -0800 Subject: [PATCH 103/234] okay well this time it compiles :P --- Cargo.toml | 1 + src/ipld.rs | 151 ++++++++++++++++++++++++++++++++-------------------- src/lib.rs | 3 ++ 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72735375..a1b96af6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" +wasm-bindgen-derive = "0.2" wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } diff --git a/src/ipld.rs b/src/ipld.rs index ffafd65b..39a1085d 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -12,6 +12,9 @@ use libipld_core::multihash::MultihashGeneric; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Uint8Array}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_derive::TryFromJsValue; + pub struct Newtype(pub Ipld); impl From for Newtype { @@ -26,23 +29,34 @@ impl From for Ipld { } } +// FIXME better name #[cfg(target_arch = "wasm32")] +#[derive(TryFromJsValue, Debug, PartialEq, Eq, Clone)] #[wasm_bindgen] pub struct NewCid { - // FIXME Link? - version_code: u64, - multicodec_code: u64, - multihash_code: u64, - hash_bytes: Vec, + cid: Cid, +} + +#[wasm_bindgen] +extern "C" { + /// This is here because the TryFromJsValue derivation macro + /// doesn't automatically support `Option`. + /// + /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] + #[wasm_bindgen(typescript_type = "NewCid | undefined")] + pub type OptionNewCid; } -// FIXME better name #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl NewCid { - pub fn from_string(cid_string: String) -> Result { + pub fn from_string(cid_string: String) -> Result { NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) } + + pub fn to_string(&self) -> String { + self.cid.to_string() + } } #[cfg(target_arch = "wasm32")] @@ -57,31 +71,14 @@ impl TryFrom for NewCid { #[cfg(target_arch = "wasm32")] impl From for Cid { fn from(wrapper: NewCid) -> Self { - Cid::new( - wrapper.version_code.try_into().expect( - "must be a valid Cid::Version because it was deconstructed from a valid one", - ), - wrapper - .multicodec_code - .try_into() - .expect("must be a valid Multicodec because it was deconstructed from a valid one"), - MultihashGeneric::<64>::wrap(wrapper.multihash_code, wrapper.hash_bytes.as_ref()) - .expect("a valid Multihash because it was deconstructed from a valid one") - .into(), - ) - .expect("the only way to get a Link is by deconstructing a valid Cid") + wrapper.cid } } #[cfg(target_arch = "wasm32")] impl From for NewCid { fn from(cid: Cid) -> Self { - Self { - version_code: cid.version().into(), - multicodec_code: cid.codec().into(), - multihash_code: cid.hash().code(), - hash_bytes: cid.hash().digest().to_vec(), - } + Self { cid } } } @@ -95,7 +92,13 @@ impl From for JsValue { Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => Bytes::from(bs).into(), + Ipld::Bytes(bs) => { + let mut arr = js_sys::Uint8Array::new(&bs.len().into()); + for (i, b) in bs.iter().enumerate() { + arr.set_index(i as u32, *b); + } + arr.into() + } Ipld::List(ls) => { let mut arr = Array::new(); for ipld in ls { @@ -138,59 +141,93 @@ impl TryFrom for Newtype { } if let Some(arr) = js_val.dyn_ref::() { - let mut ls = Vec::with_capacity(arr.length() as usize); - for a in arr.iter() { - ls.push(Newtype::try_from(a)?.into()); + let mut list = vec![]; + for x in arr.to_vec().iter() { + let ipld = Newtype::try_from(x.clone())?.into(); + list.push(ipld); } - return Ok(Newtype(Ipld::List(ls))); - } - if let Some(bytes) = js_val.dyn_ref::() { - let arr = Uint8Array::new(&bytes.raw.len().into()); + return Ok(Newtype(Ipld::List(list))); + } - for (index, item) in bytes.raw.iter().enumerate() { - arr.set_index(index as u32, *item); + if let Some(arr) = js_val.dyn_ref::() { + let mut v = vec![]; + for item in arr.to_vec().iter() { + v.push(item.clone()); } - return Ok(Newtype(Ipld::Bytes(arr))); + return Ok(Newtype(Ipld::Bytes(v))); } if let Some(map) = js_val.dyn_ref::() { let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); - for iter_item in map.entries() { - match iter_item { - Err(_) => return Err(()), - Ok(k) => match k.as_string() { - None => return Err(()), - Some(s) => { - m.insert(s, Newtype::try_from(v)?.0); - }, + // Weird order, but correct per the docs + // vvvvvvvvvv + map.for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match key.as_string() { + None => { + acc = Err(()); } + Some(k) => match Newtype::try_from(value.clone()) { + Err(_) => { + acc = Err(()); + } + Ok(v) => match Newtype::try_from(value) { + Err(_) => { + acc = Err(()); + } + Ok(v) => { + m.insert(k, v.0); + } + }, + }, } - } + }); - return Ok(Newtype(Ipld::Map(m))); + return acc.map(|_| Newtype(Ipld::Map(m))); } // NOTE *must* come before `is_object` (which is hopefully below) - if let Some(link) = js_val.dyn_ref::() { - return Ok(Newtype(Ipld::Link(link.into().clone()))); + if let Ok(new_cid) = NewCid::try_from(&js_val) { + return Ok(Newtype(Ipld::Link(new_cid.cid))); } if js_val.is_object() { let mut m = std::collections::BTreeMap::new(); - for (k, v) in js_val { - m.insert(k.as_string.ok_or(())?, Newtype::try_from(v)?); - } + let mut acc = Ok(()); + + // This order is correct per the docs + // vvvvvvvvvv + js_sys::Map::from(js_val).for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match key.as_string() { + None => { + acc = Err(()); + } + Some(k) => match Newtype::try_from(value) { + Err(_) => { + acc = Err(()); + } + Ok(v) => { + m.insert(k, v.0); + } + }, + } + }); - return Ok(Newtype(Ipld::Map(m))); + return acc.map(|_| Newtype(Ipld::Map(m))); } - // // FIXME hmmmm - // if let Some(undefined) = js_val.is_undefined() { - // return Self(Ipld::Null); - // } + // NOTE fails on `undefined` and `function` Err(()) } diff --git a/src/lib.rs b/src/lib.rs index 0cb8c3e5..50e0d87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ //! ucan +#[cfg(target_arch = "wasm32")] +extern crate alloc; + // use std::str::FromStr; // // use cid::{multihash, Cid}; From 9461d8d8f8f176d2d71a7895f297a36d344f717c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 18:24:50 -0800 Subject: [PATCH 104/234] Save debugging bindgen --- flake.nix | 12 ++ src/ability/arguments.rs | 66 +++++++- src/ability/dynamic.rs | 47 +++++- src/ability/js.rs | 343 +++++++++++++++++++++----------------- src/invocation/payload.rs | 22 +-- src/ipld.rs | 102 ++---------- src/ipld/cid.rs | 61 +++++++ 7 files changed, 393 insertions(+), 260 deletions(-) create mode 100644 src/ipld/cid.rs diff --git a/flake.nix b/flake.nix index 3e573152..a14844a0 100644 --- a/flake.nix +++ b/flake.nix @@ -293,12 +293,24 @@ category = "dev"; command = "${cargo} doc --features=mermaid_docs"; } + { + name = "docs:wasm:build"; + help = "Refresh the docs with the wasm32-unknown-unknown target"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; + } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --open"; } + { + name = "docs:wasm:open"; + help = "Open refreshed docs for wasm32-unknown-unknown"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown --open"; + } ]; }; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 75dc7bba..90eb798d 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -2,8 +2,36 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Uint8Array}; + +#[cfg(target_arch = "wasm32")] +use crate::ipld; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Pair { + key: String, + value: ipld::Newtype, +} + +// #[wasm_bindgen] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Arguments(pub BTreeMap); +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Arguments( + // #[wasm_bindgen(skip)] pub BTreeMap, + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub BTreeMap, +); + +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +// pub struct Arguments1 { +// // #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] +// #[wasm_bindgen(skip)] +// pub one: Vec, +// } impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { @@ -40,3 +68,39 @@ impl TryFrom for Arguments { ipld_serde::from_ipld(ipld) } } + +impl From for Ipld { + fn from(arguments: Arguments) -> Self { + ipld_serde::to_ipld(arguments).unwrap() + } +} + +// #[cfg(target_arch = "wasm32")] +// impl From for JsValue { +// fn from(arguments: Arguments) -> Self { +// arguments +// .0 +// .iter() +// .fold(Map::new(), |map, (ref k, v)| { +// map.set( +// &JsValue::from_str(k), +// &JsValue::from(ipld::Newtype(v.clone())), +// ); +// map +// }) +// .into() +// } +// } +// +// #[cfg(target_arch = "wasm32")] +// impl TryFrom for Arguments { +// type Error = (); // FIXME +// +// fn try_from(js: JsValue) -> Result { +// match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { +// Err(()) => Err(()), // FIXME surface that we can't parse at all +// Ok(Ipld::Map(map)) => Ok(Arguments(map)), +// Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type +// } +// } +// } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index dd8bbfb1..5f7eb83f 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -21,15 +21,44 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -// #[derive(Clone, PartialEq)] -// pub struct Generic { -// pub cmd: String, -// pub args: Args, -// // pub is_nonce_meaningful: Fn(&String) -> bool, -// // pub same_validator: Fn(&String, &Arguments) -> Result<(), String>, -// // pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void -// // pub shape_validator: Fn(&String, &Arguments) -> Result<(), String>, // FIXME needs to be a different type -// } +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Dynamic { + pub cmd: String, + pub args: Arguments, +} + +pub struct ValidateWithoutParents { + ability: Dynamic, + config: Config0, +} + +pub struct ValidateWithParents { + ability: Dynamic, + config: Config1, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Config0 { + pub is_nonce_meaningful: bool, + pub validate_shape: ValShape, + pub check_same: ValSame, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Config1 { + // #[wasm_bindgen(readonly)] + pub is_nonce_meaningful: bool, + + // #[wasm_bindgen(skip)] + pub validate_shape: ValShape, + + //#[wasm_bindgen(skip)] + pub check_same: ValSame, + + //#[wasm_bindgen(skip)] + pub check_parent: ValParent, // FIXME explore concrete types + an enum +} // // pub struct DynamicValidator { // // fn check_shape(self) -> (); diff --git a/src/ability/js.rs b/src/ability/js.rs index d015dff9..13e389d0 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,24 +1,24 @@ -use super::arguments::Arguments; -use crate::proof::same::CheckSame; +use super::{arguments::Arguments, dynamic}; +use crate::{ipld, proof::same::CheckSame}; use js_sys::Object; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::prelude::*; -// FIXME dynamic +// FIXME now we can just use dynamic again (yay) #[wasm_bindgen] pub struct Ability { cmd: String, // FIXME don't need this field because it's on the validator? // FIXME JsCast for Args or WrappedIpld, esp for Cids - args: BTreeMap, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args + args: Arguments, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args } -impl From for js_sys::Object { +impl From for js_sys::Map { fn from(ability: Ability) -> Self { let args = js_sys::Map::new(); - for (k, v) in ability.args { - args.set(&k.into(), &v); + for (k, v) in ability.args.0 { + args.set(&k.into(), &ipld::Newtype(v).into()); } let map = js_sys::Map::new(); @@ -40,11 +40,10 @@ impl TryFrom for Ability { let keys = Object::keys(obj_args); let values = Object::values(obj_args); - // FIXME come back when TryInto is done... if it matters - let mut args = BTreeMap::new(); + let mut btree = BTreeMap::new(); for (k, v) in keys.iter().zip(values) { if let Some(k) = k.as_string() { - args.insert(k, v); + btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); } else { return Err(k); } @@ -52,7 +51,7 @@ impl TryFrom for Ability { Ok(Ability { cmd, - args: args.clone(), // FIXME kill clone + args: Arguments(btree), // FIXME kill clone }) } else { Err(JsValue::NULL) // FIXME @@ -60,24 +59,48 @@ impl TryFrom for Ability { } } -#[wasm_bindgen] -#[derive(Debug, Clone, PartialEq)] -pub struct Validator { - #[wasm_bindgen(skip)] - pub cmd: String, - - #[wasm_bindgen(readonly)] - pub is_nonce_meaningful: bool, - - #[wasm_bindgen(skip)] - pub validate_shape: js_sys::Function, +pub type Abc = dynamic::ValidateWithoutParents; +pub type Xyz = dynamic::ValidateWithParents; - #[wasm_bindgen(skip)] - pub check_same: js_sys::Function, +// #[wasm_bindgen] +// #[derive(Debug, Clone, PartialEq)] +// pub struct ValidateWithoutParents { +// ability: dynamic::Dynamic, +// +// #[wasm_bindgen(skip)] +// config: u32, // dynamic::Config0, +// } +// +// // pub struct ValidateWithParents { +// // ability: Dynamic, +// // config: Config1, +// // } +// +// #[wasm_bindgen] +// impl ValidateWithoutParents { +// pub fn foo(x: u32) -> ValidateWithoutParents { +// todo!() +// } +// } - #[wasm_bindgen(skip)] - pub check_parent: Option, // FIXME explore concrete types + an enum -} +// #[wasm_bindgen] +// #[derive(Debug, Clone, PartialEq)] +// pub struct Validator { +// #[wasm_bindgen(skip)] +// pub cmd: String, +// +// #[wasm_bindgen(readonly)] +// pub is_nonce_meaningful: bool, +// +// #[wasm_bindgen(skip)] +// pub validate_shape: js_sys::Function, +// +// #[wasm_bindgen(skip)] +// pub check_same: js_sys::Function, +// +// #[wasm_bindgen(skip)] +// pub check_parent: Option, // FIXME explore concrete types + an enum +// } // Helper pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { @@ -92,136 +115,150 @@ pub fn invoke(f: &js_sys::Function, args: Vec) -> Result, - ) -> Validator { - // FIXME chec that JsErr doesn't auto-throw - Validator { - cmd, - is_nonce_meaningful, - validate_shape, - check_same, - check_parent, - } - } - - pub fn command(&self) -> String { - self.cmd.clone() - } - - // e.g. reject extra fields - pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.validate_shape.call1(&this, args)?; - Ok(()) - } - - // FIXME only dynamic? - pub fn check_same( - &self, - target: &js_sys::Object, - proof: &js_sys::Object, - ) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.check_same.call2(&this, target, proof)?; - Ok(()) - } - - pub fn check_parents( - &self, - target: &js_sys::Object, // FIXME better type, esp for TS? - proof: &js_sys::Object, - ) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - if let Some(checker) = &self.check_parent { - checker.call2(&this, target, proof)?; - return Ok(()); - } - - Err(this) - } -} - -pub struct Foo { - ability: Ability, - validator: Validator, -} - -impl From for Arguments { - fn from(foo: Foo) -> Self { - todo!() // FIXME - } -} - -use crate::delegation::Delegatable; - -impl Delegatable for Foo { - type Builder = Foo; -} - -impl CheckSame for Foo { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); - - let mut this_args = js_sys::Map::new(); - for (k, v) in this_it { - this_args.set(&k, v); - } - - let proof_it = proof - .ability - .args - .iter() - .map(|(k, v)| (JsValue::from(k), v)); - - let mut proof_args = js_sys::Map::new(); - for (k, v) in proof_it { - proof_args.set(&k, v); - } - - self.validator.check_same( - &Object::from_entries(&this_args)?, - &Object::from_entries(&proof_args)?, - ) - } -} - -// pub struct Ability { -// pub cmd: String, -// pub args: BTreeMap, // FIXME args -// pub val: JsValidator, -// } -// +// // NOTE more like a config object // #[wasm_bindgen] -// impl Ability { +// impl Validator { +// // FIXME wrap in func that checks the jsval or better: converts form Ipld +// // FIXME notes about checking shape on the way in // #[wasm_bindgen(constructor)] -// fn new( +// pub fn new( // cmd: String, -// args: BTreeMap, -// validator: JsValidator, -// ) -> Result { -// let args = args -// .iter() -// .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) -// .collect(); +// is_nonce_meaningful: bool, +// validate_shape: js_sys::Function, +// check_same: js_sys::Function, +// check_parent: Option, +// ) -> Validator { +// // FIXME chec that JsErr doesn't auto-throw +// Validator { +// cmd, +// is_nonce_meaningful, +// validate_shape, +// check_same, +// check_parent, +// } +// } +// +// pub fn command(&self) -> String { +// self.cmd.clone() +// } +// +// // e.g. reject extra fields +// pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// self.validate_shape.call1(&this, args)?; +// Ok(()) +// } +// +// // FIXME only dynamic? +// pub fn check_same( +// &self, +// target: &js_sys::Object, +// proof: &js_sys::Object, +// ) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// self.check_same.call2(&this, target, proof)?; +// Ok(()) +// } +// +// pub fn check_parents( +// &self, +// target: &js_sys::Object, // FIXME better type, esp for TS? +// proof: &js_sys::Object, +// ) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// if let Some(checker) = &self.check_parent { +// checker.call2(&this, target, proof)?; +// return Ok(()); +// } +// +// Err(this) +// } +// } +// +// pub struct Quux { +// quux: T, +// } +// +// type Bez = Quux; +// +// #[wasm_bindgen] +// impl Bez {} +// +// pub struct Baz { +// pub ability: Ability, +// pub validator: T, +// } +// +// pub struct Foo { +// pub ability: Ability, +// pub validator: Validator, +// } // -// validator.check_shape(args)?; -// Ok(Ability { cmd, args, val }) +// impl From for Arguments { +// fn from(foo: Foo) -> Self { +// todo!() // FIXME // } // } // -// pub struct Pipeline { -// pub validators: Vec, +// use crate::delegation::Delegatable; +// +// impl Delegatable for Foo { +// type Builder = Foo; // } +// +// impl CheckSame for Foo { +// type Error = JsValue; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); +// +// let mut this_args = js_sys::Map::new(); +// for (k, v) in this_it { +// this_args.set(&k, &ipld::Newtype(v.clone()).into()); +// } +// +// let proof_it = proof +// .ability +// .args +// .iter() +// .map(|(k, v)| (JsValue::from(k), v)); +// +// let mut proof_args = js_sys::Map::new(); +// for (k, v) in proof_it { +// proof_args.set(&k, &ipld::Newtype(v.clone()).into()); +// } +// +// self.validator.check_same( +// &Object::from_entries(&this_args)?, +// &Object::from_entries(&proof_args)?, +// ) +// } +// } +// +// // pub struct Ability { +// // pub cmd: String, +// // pub args: BTreeMap, // FIXME args +// // pub val: JsValidator, +// // } +// // +// // #[wasm_bindgen] +// // impl Ability { +// // #[wasm_bindgen(constructor)] +// // fn new( +// // cmd: String, +// // args: BTreeMap, +// // validator: JsValidator, +// // ) -> Result { +// // let args = args +// // .iter() +// // .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) +// // .collect(); +// // +// // validator.check_shape(args)?; +// // Ok(Ability { cmd, args, val }) +// // } +// // } +// // +// // pub struct Pipeline { +// // pub validators: Vec, +// // } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2ee9805c..72921dcd 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -34,17 +34,17 @@ pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum MaybeResolved + Into> -where - Payload: From, - Unresolved: From, - T::Promised: Clone + Command + Debug + PartialEq, -{ - Resolved(Payload), - Unresolved(Unresolved), -} +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(untagged)] +// pub enum MaybeResolved + Into> +// where +// Payload: From, +// Unresolved: From, +// T::Promised: Clone + Command + Debug + PartialEq, +// { +// Resolved(Payload), +// Unresolved(Unresolved), +// } impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; diff --git a/src/ipld.rs b/src/ipld.rs index 39a1085d..7a8c6281 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,20 +1,13 @@ use libipld_core::ipld::Ipld; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +pub mod cid; #[cfg(target_arch = "wasm32")] -use libipld_core::cid::Cid; - -#[cfg(target_arch = "wasm32")] -use libipld_core::multihash::MultihashGeneric; +use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Uint8Array}; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_derive::TryFromJsValue; - pub struct Newtype(pub Ipld); impl From for Newtype { @@ -29,59 +22,6 @@ impl From for Ipld { } } -// FIXME better name -#[cfg(target_arch = "wasm32")] -#[derive(TryFromJsValue, Debug, PartialEq, Eq, Clone)] -#[wasm_bindgen] -pub struct NewCid { - cid: Cid, -} - -#[wasm_bindgen] -extern "C" { - /// This is here because the TryFromJsValue derivation macro - /// doesn't automatically support `Option`. - /// - /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] - #[wasm_bindgen(typescript_type = "NewCid | undefined")] - pub type OptionNewCid; -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl NewCid { - pub fn from_string(cid_string: String) -> Result { - NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) - } - - pub fn to_string(&self) -> String { - self.cid.to_string() - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for NewCid { - type Error = >::Error; - - fn try_from(cid_string: String) -> Result { - Cid::try_from(cid_string).map(Into::into) - } -} - -#[cfg(target_arch = "wasm32")] -impl From for Cid { - fn from(wrapper: NewCid) -> Self { - wrapper.cid - } -} - -#[cfg(target_arch = "wasm32")] -impl From for NewCid { - fn from(cid: Cid) -> Self { - Self { cid } - } -} - // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { @@ -93,27 +33,27 @@ impl From for JsValue { Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), Ipld::Bytes(bs) => { - let mut arr = js_sys::Uint8Array::new(&bs.len().into()); + let u8arr = Uint8Array::new(&bs.len().into()); for (i, b) in bs.iter().enumerate() { - arr.set_index(i as u32, *b); + u8arr.set_index(i as u32, *b); } - arr.into() + JsValue::from(u8arr) } Ipld::List(ls) => { - let mut arr = Array::new(); + let arr = Array::new(); for ipld in ls { arr.push(&JsValue::from(Newtype(ipld))); } JsValue::from(arr) } Ipld::Map(m) => { - let mut map = Map::new(); + let map = Map::new(); for (k, v) in m { map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); } JsValue::from(map) } - Ipld::Link(cid) => NewCid::from(cid).into(), + Ipld::Link(cid) => cid::Newtype::from(cid).into(), } } } @@ -170,23 +110,13 @@ impl TryFrom for Newtype { return; } - match key.as_string() { - None => { + match (key.as_string(), Newtype::try_from(value.clone())) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + _ => { acc = Err(()); } - Some(k) => match Newtype::try_from(value.clone()) { - Err(_) => { - acc = Err(()); - } - Ok(v) => match Newtype::try_from(value) { - Err(_) => { - acc = Err(()); - } - Ok(v) => { - m.insert(k, v.0); - } - }, - }, } }); @@ -194,8 +124,8 @@ impl TryFrom for Newtype { } // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(new_cid) = NewCid::try_from(&js_val) { - return Ok(Newtype(Ipld::Link(new_cid.cid))); + if let Ok(nt) = cid::Newtype::try_from(&js_val) { + return Ok(Newtype(Ipld::Link(nt.into()))); } if js_val.is_object() { @@ -204,7 +134,7 @@ impl TryFrom for Newtype { // This order is correct per the docs // vvvvvvvvvv - js_sys::Map::from(js_val).for_each(&mut |value, key| { + Map::from(js_val).for_each(&mut |value, key| { if acc.is_err() { return; } diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs new file mode 100644 index 00000000..9a812402 --- /dev/null +++ b/src/ipld/cid.rs @@ -0,0 +1,61 @@ +use libipld_core::cid::Cid; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_derive::TryFromJsValue; + +// FIXME better name +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Newtype { + cid: Cid, +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +extern "C" { + /// This is here because the TryFromJsValue derivation macro + /// doesn't automatically support `Option`. + /// + /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] + #[wasm_bindgen(typescript_type = "Newtype | undefined")] + pub type OptionNewtype; +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Newtype { + pub fn from_string(cid_string: String) -> Result { + Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + } + + pub fn to_string(&self) -> String { + self.cid.to_string() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = >::Error; + + fn try_from(cid_string: String) -> Result { + Cid::try_from(cid_string).map(Into::into) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: Newtype) -> Self { + wrapper.cid + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Newtype { + fn from(cid: Cid) -> Self { + Self { cid } + } +} From d435ee4b46fda29757931bad300ca10a80ce8d69 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 3 Feb 2024 01:20:34 -0800 Subject: [PATCH 105/234] Needs lots of cleanup, but the wasm startegy seems to work --- src/ability/arguments.rs | 114 ++++++++------- src/ability/dynamic.rs | 114 ++++++++------- src/ability/js.rs | 266 +---------------------------------- src/ability/js/parentful.rs | 128 +++++++++++++++++ src/ability/js/parentless.rs | 100 +++++++++++++ src/ability/ucan.rs | 2 + src/ipld.rs | 26 ++-- 7 files changed, 378 insertions(+), 372 deletions(-) create mode 100644 src/ability/js/parentful.rs create mode 100644 src/ability/js/parentless.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 90eb798d..b67f6608 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -6,32 +6,22 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Uint8Array}; +use js_sys::{Array, Map, Object, Reflect, Uint8Array}; #[cfg(target_arch = "wasm32")] use crate::ipld; -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct Pair { - key: String, - value: ipld::Newtype, -} +use super::wasm; -// #[wasm_bindgen] +// FIXME yes I'm seriously considering laying this out in the wasm abi by han d +// #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct Arguments( - // #[wasm_bindgen(skip)] pub BTreeMap, - #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub BTreeMap, -); +pub struct Arguments(pub BTreeMap); +// #[cfg(target_arch = "wasm32")] // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -// pub struct Arguments1 { -// // #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] -// #[wasm_bindgen(skip)] -// pub one: Vec, -// } +// #[wasm_bindgen] +// pub struct Arguments(#[wasm_bindgen(skip)] pub BTreeMap); impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { @@ -75,32 +65,62 @@ impl From for Ipld { } } -// #[cfg(target_arch = "wasm32")] -// impl From for JsValue { -// fn from(arguments: Arguments) -> Self { -// arguments -// .0 -// .iter() -// .fold(Map::new(), |map, (ref k, v)| { -// map.set( -// &JsValue::from_str(k), -// &JsValue::from(ipld::Newtype(v.clone())), -// ); -// map -// }) -// .into() -// } -// } -// -// #[cfg(target_arch = "wasm32")] -// impl TryFrom for Arguments { -// type Error = (); // FIXME -// -// fn try_from(js: JsValue) -> Result { -// match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { -// Err(()) => Err(()), // FIXME surface that we can't parse at all -// Ok(Ipld::Map(map)) => Ok(Arguments(map)), -// Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type -// } -// } -// } +#[cfg(target_arch = "wasm32")] +impl From for Object { + fn from(arguments: Arguments) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); + } + obj + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl From<&Object> for Arguments { + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = ipld::Newtype::try_from(entry.get(1)).unwrap().0; + (key, value) + }) + .collect::>(); + + Arguments(btree) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(arguments: Arguments) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set( + &JsValue::from_str(k), + &JsValue::from(ipld::Newtype(v.clone())), + ); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Arguments { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Arguments(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 5f7eb83f..4ad98974 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -4,6 +4,7 @@ use super::{arguments::Arguments, command::ToCommand}; use crate::{ delegation::Delegatable, invocation::Resolvable, + ipld, promise::Promise, proof::{ checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, @@ -11,6 +12,7 @@ use crate::{ }, task::DefaultTrue, }; +use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, @@ -21,69 +23,87 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[derive(Clone, PartialEq, Debug)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, pub args: Arguments, } -pub struct ValidateWithoutParents { - ability: Dynamic, - config: Config0, +impl From for Arguments { + fn from(dynamic: Dynamic) -> Self { + dynamic.args + } } -pub struct ValidateWithParents { - ability: Dynamic, - config: Config1, +#[cfg(target_arch = "wasm32")] +impl From for js_sys::Map { + fn from(ability: Dynamic) -> Self { + let args = js_sys::Map::new(); + for (k, v) in ability.args.0 { + args.set(&k.into(), &ipld::Newtype(v).into()); + } + + let map = js_sys::Map::new(); + map.set(&"args".into(), &js_sys::Object::from(args).into()); + map.set(&"cmd".into(), &ability.cmd.into()); + map + } } -#[derive(Debug, Clone, PartialEq)] -pub struct Config0 { - pub is_nonce_meaningful: bool, - pub validate_shape: ValShape, - pub check_same: ValSame, +#[cfg(target_arch = "wasm32")] +impl TryFrom for Dynamic { + type Error = JsValue; + + fn try_from(map: js_sys::Map) -> Result { + if let (Some(cmd), js_args) = ( + map.get(&("cmd".into())).as_string(), + &map.get(&("args".into())), + ) { + let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; + let keys = js_sys::Object::keys(obj_args); + let values = js_sys::Object::values(obj_args); + + let mut btree = BTreeMap::new(); + for (k, v) in keys.iter().zip(values) { + if let Some(k) = k.as_string() { + btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); + } else { + return Err(k); + } + } + + Ok(Dynamic { + cmd, + args: Arguments(btree), // FIXME kill clone + }) + } else { + Err(JsValue::NULL) // FIXME + } + } } -#[derive(Debug, Clone, PartialEq)] -pub struct Config1 { - // #[wasm_bindgen(readonly)] - pub is_nonce_meaningful: bool, +impl CheckSame for Dynamic { + type Error = String; // FIXME better err - // #[wasm_bindgen(skip)] - pub validate_shape: ValShape, + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.cmd != proof.cmd { + return Err("Command mismatch".into()); + } - //#[wasm_bindgen(skip)] - pub check_same: ValSame, + self.args.0.iter().try_for_each(|(k, v)| { + if let Some(proof_v) = proof.args.0.get(k) { + if v != proof_v { + return Err("Arguments mismatch".into()); + } + } else { + return Err("Arguments mismatch".into()); + } - //#[wasm_bindgen(skip)] - pub check_parent: ValParent, // FIXME explore concrete types + an enum + Ok(()) + }) + } } -// // pub struct DynamicValidator { -// // fn check_shape(self) -> (); -// // // fn check_same: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, -// // // fn check_parents: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, -// // } -// // -// // -// // -// // // FIXME Actually, we shoudl go back to wrapping? -// // // impl CheckSame for Dynamic -// // // where -// // // F: Fn(&String, &Arguments) -> Result<(), String>, -// // // { -// // // type Error = String; -// // // -// // // fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// // // let chain_checker = &self.same_validator; -// // // let shape_checker = &self.same_validator; -// // // -// // // shape_checker(&proof.cmd, &proof.args)?; -// // // chain_checker(&proof.cmd, &proof.args) -// // // } -// // // } -// // impl Debug for Generic { // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // f.debug_struct("Generic") diff --git a/src/ability/js.rs b/src/ability/js.rs index 13e389d0..8422e6d3 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,264 +1,2 @@ -use super::{arguments::Arguments, dynamic}; -use crate::{ipld, proof::same::CheckSame}; -use js_sys::Object; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; -use wasm_bindgen::prelude::*; - -// FIXME now we can just use dynamic again (yay) -#[wasm_bindgen] -pub struct Ability { - cmd: String, // FIXME don't need this field because it's on the validator? - // FIXME JsCast for Args or WrappedIpld, esp for Cids - args: Arguments, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args -} - -impl From for js_sys::Map { - fn from(ability: Ability) -> Self { - let args = js_sys::Map::new(); - for (k, v) in ability.args.0 { - args.set(&k.into(), &ipld::Newtype(v).into()); - } - - let map = js_sys::Map::new(); - map.set(&"args".into(), &js_sys::Object::from(args).into()); - map.set(&"cmd".into(), &ability.cmd.into()); - map.into() - } -} - -impl TryFrom for Ability { - type Error = JsValue; - - fn try_from(map: js_sys::Map) -> Result { - if let (Some(cmd), js_args) = ( - map.get(&("cmd".into())).as_string(), - &map.get(&("args".into())), - ) { - let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; - let keys = Object::keys(obj_args); - let values = Object::values(obj_args); - - let mut btree = BTreeMap::new(); - for (k, v) in keys.iter().zip(values) { - if let Some(k) = k.as_string() { - btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); - } else { - return Err(k); - } - } - - Ok(Ability { - cmd, - args: Arguments(btree), // FIXME kill clone - }) - } else { - Err(JsValue::NULL) // FIXME - } - } -} - -pub type Abc = dynamic::ValidateWithoutParents; -pub type Xyz = dynamic::ValidateWithParents; - -// #[wasm_bindgen] -// #[derive(Debug, Clone, PartialEq)] -// pub struct ValidateWithoutParents { -// ability: dynamic::Dynamic, -// -// #[wasm_bindgen(skip)] -// config: u32, // dynamic::Config0, -// } -// -// // pub struct ValidateWithParents { -// // ability: Dynamic, -// // config: Config1, -// // } -// -// #[wasm_bindgen] -// impl ValidateWithoutParents { -// pub fn foo(x: u32) -> ValidateWithoutParents { -// todo!() -// } -// } - -// #[wasm_bindgen] -// #[derive(Debug, Clone, PartialEq)] -// pub struct Validator { -// #[wasm_bindgen(skip)] -// pub cmd: String, -// -// #[wasm_bindgen(readonly)] -// pub is_nonce_meaningful: bool, -// -// #[wasm_bindgen(skip)] -// pub validate_shape: js_sys::Function, -// -// #[wasm_bindgen(skip)] -// pub check_same: js_sys::Function, -// -// #[wasm_bindgen(skip)] -// pub check_parent: Option, // FIXME explore concrete types + an enum -// } - -// Helper -pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { - // FIXME annoying number of steps... so I guess that's why they have the numbered versions... - // but those end at 3 :/ - // Hmm I guess this is reasonable, since it needs to copy the `Vec` to the JsArray - let arr = js_sys::Array::new_with_length(args.len() as u32); - for (i, arg) in args.iter().enumerate() { - arr.set(i as u32, arg.clone()); - } - - f.apply(&wasm_bindgen::JsValue::NULL, &arr) -} - -// // NOTE more like a config object -// #[wasm_bindgen] -// impl Validator { -// // FIXME wrap in func that checks the jsval or better: converts form Ipld -// // FIXME notes about checking shape on the way in -// #[wasm_bindgen(constructor)] -// pub fn new( -// cmd: String, -// is_nonce_meaningful: bool, -// validate_shape: js_sys::Function, -// check_same: js_sys::Function, -// check_parent: Option, -// ) -> Validator { -// // FIXME chec that JsErr doesn't auto-throw -// Validator { -// cmd, -// is_nonce_meaningful, -// validate_shape, -// check_same, -// check_parent, -// } -// } -// -// pub fn command(&self) -> String { -// self.cmd.clone() -// } -// -// // e.g. reject extra fields -// pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// self.validate_shape.call1(&this, args)?; -// Ok(()) -// } -// -// // FIXME only dynamic? -// pub fn check_same( -// &self, -// target: &js_sys::Object, -// proof: &js_sys::Object, -// ) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// self.check_same.call2(&this, target, proof)?; -// Ok(()) -// } -// -// pub fn check_parents( -// &self, -// target: &js_sys::Object, // FIXME better type, esp for TS? -// proof: &js_sys::Object, -// ) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// if let Some(checker) = &self.check_parent { -// checker.call2(&this, target, proof)?; -// return Ok(()); -// } -// -// Err(this) -// } -// } -// -// pub struct Quux { -// quux: T, -// } -// -// type Bez = Quux; -// -// #[wasm_bindgen] -// impl Bez {} -// -// pub struct Baz { -// pub ability: Ability, -// pub validator: T, -// } -// -// pub struct Foo { -// pub ability: Ability, -// pub validator: Validator, -// } -// -// impl From for Arguments { -// fn from(foo: Foo) -> Self { -// todo!() // FIXME -// } -// } -// -// use crate::delegation::Delegatable; -// -// impl Delegatable for Foo { -// type Builder = Foo; -// } -// -// impl CheckSame for Foo { -// type Error = JsValue; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); -// -// let mut this_args = js_sys::Map::new(); -// for (k, v) in this_it { -// this_args.set(&k, &ipld::Newtype(v.clone()).into()); -// } -// -// let proof_it = proof -// .ability -// .args -// .iter() -// .map(|(k, v)| (JsValue::from(k), v)); -// -// let mut proof_args = js_sys::Map::new(); -// for (k, v) in proof_it { -// proof_args.set(&k, &ipld::Newtype(v.clone()).into()); -// } -// -// self.validator.check_same( -// &Object::from_entries(&this_args)?, -// &Object::from_entries(&proof_args)?, -// ) -// } -// } -// -// // pub struct Ability { -// // pub cmd: String, -// // pub args: BTreeMap, // FIXME args -// // pub val: JsValidator, -// // } -// // -// // #[wasm_bindgen] -// // impl Ability { -// // #[wasm_bindgen(constructor)] -// // fn new( -// // cmd: String, -// // args: BTreeMap, -// // validator: JsValidator, -// // ) -> Result { -// // let args = args -// // .iter() -// // .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) -// // .collect(); -// // -// // validator.check_shape(args)?; -// // Ok(Ability { cmd, args, val }) -// // } -// // } -// // -// // pub struct Pipeline { -// // pub validators: Vec, -// // } +pub mod parentful; +pub mod parentless; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs new file mode 100644 index 00000000..ed103b7b --- /dev/null +++ b/src/ability/js/parentful.rs @@ -0,0 +1,128 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand, dynamic}, + ipld, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, +}; +use js_sys::{Function, Map, Object, Reflect}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +// FIXME rename to module +#[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen] +pub struct JsWithParents { + // FIXME just inline and use from + #[wasm_bindgen(skip)] + pub ability: dynamic::Dynamic, + + #[wasm_bindgen(skip)] + pub config: Config, + + #[wasm_bindgen(skip)] + pub check_parents: Function, +} + +// FIXME just inline +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub is_nonce_meaningful: bool, + pub validate_shape: Function, + pub check_same: Function, +} + +#[wasm_bindgen] +impl JsWithParents { + // FIXME consider using an object with named fields + // FIXME needs borrows? + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + args: Object, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, // FIXME Need to actuallyrun this on create + check_same: js_sys::Function, + check_parents: js_sys::Function, // FIXME what is an object? i.e. {cmd: handler}? + ) -> JsWithParents { + JsWithParents { + ability: dynamic::Dynamic { + cmd, + args: (&args).into(), + }, + config: Config { + is_nonce_meaningful, + validate_shape, + check_same, + }, + check_parents, + } + } + + #[wasm_bindgen(getter)] + pub fn is_nonce_meaningful(&self) -> bool { + self.config.is_nonce_meaningful + } + + pub fn check_shape(&self) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .validate_shape + .call1(&this, &self.ability.args.clone().into()) + .map(|_| ()) + } + + // FIXME throws on Err + pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2(&this, &self.ability.args.clone().into(), proof) + .map(|_| ()) + } + + // FIXME work with native Rust abilities, too + pub fn check_parents(&self, parent: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.check_parents + .call2(&this, &self.ability.args.clone().into(), parent) + .map(|_| ()) + } +} + +// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? +impl CheckSame for JsWithParents { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.check_same(&proof.ability.args.clone().into()) + } +} + +impl CheckParents for JsWithParents { + type Parents = dynamic::Dynamic; + type ParentError = JsValue; + + fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { + let obj = Object::new(); + Reflect::set(&obj, &"cmd".into(), &parent.cmd.clone().into())?; + Reflect::set(&obj, &"args".into(), &parent.args.clone().into())?; + + self.check_parents(&obj) + } +} + +impl ToCommand for JsWithParents { + fn to_command(&self) -> String { + self.ability.cmd.clone() + } +} + +impl From for Arguments { + fn from(js: JsWithParents) -> Self { + js.ability.into() + } +} + +impl Checkable for JsWithParents { + type Hierarchy = Parentful; +} diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs new file mode 100644 index 00000000..75296b92 --- /dev/null +++ b/src/ability/js/parentless.rs @@ -0,0 +1,100 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand, dynamic}, + ipld, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, +}; +use js_sys::{Function, Map, Object, Reflect}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +#[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen] +pub struct JsWithoutParents { + #[wasm_bindgen(skip)] + pub ability: dynamic::Dynamic, + + #[wasm_bindgen(skip)] + pub config: Config, +} + +// FIXME just inline +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub is_nonce_meaningful: bool, + pub validate_shape: Function, + pub check_same: Function, +} + +#[wasm_bindgen] +impl JsWithoutParents { + // FIXME consider using an object with named fields + // FIXME needs borrows? + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + args: Object, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, + check_same: js_sys::Function, + ) -> JsWithoutParents { + JsWithoutParents { + ability: dynamic::Dynamic { + cmd, + args: (&args).into(), + }, + config: Config { + is_nonce_meaningful, + validate_shape, + check_same, + }, + } + } + + #[wasm_bindgen(getter)] + pub fn is_nonce_meaningful(&self) -> bool { + self.config.is_nonce_meaningful + } + + pub fn check_shape(&self) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .validate_shape + .call1(&this, &self.ability.args.clone().into()) + .map(|_| ()) + } + + // FIXME throws on Err + pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2(&this, &self.ability.args.clone().into(), proof) + .map(|_| ()) + } +} + +// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? +impl CheckSame for JsWithoutParents { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.check_same(&proof.ability.args.clone().into()) + } +} + +impl ToCommand for JsWithoutParents { + fn to_command(&self) -> String { + self.ability.cmd.clone() + } +} + +impl From for Arguments { + fn from(js: JsWithoutParents) -> Self { + js.ability.into() + } +} + +impl Checkable for JsWithoutParents { + type Hierarchy = Parentless; +} diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index e2ee2867..af18fe21 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -6,6 +6,8 @@ use std::fmt::Debug; // NOTE This one is primarily for enabling delegationd recipets +// FIXME aslo add revokation, so thsi module needs to be broken up + // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] struct Generic { diff --git a/src/ipld.rs b/src/ipld.rs index 7a8c6281..1c781e26 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,7 +6,7 @@ pub mod cid; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Uint8Array}; +use js_sys::{Array, Map, Object, Uint8Array}; pub struct Newtype(pub Ipld); @@ -129,28 +129,26 @@ impl TryFrom for Newtype { } if js_val.is_object() { + let obj = Object::from(js_val); let mut m = std::collections::BTreeMap::new(); let mut acc = Ok(()); - // This order is correct per the docs - // vvvvvvvvvv - Map::from(js_val).for_each(&mut |value, key| { + Object::entries(&obj).for_each(&mut |js_val, _, _| { if acc.is_err() { return; } - match key.as_string() { - None => { + // By definition this must be the array [value, key], in that order + let arr = Array::from(&js_val); + + match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + // FIXME more specific errors + _ => { acc = Err(()); } - Some(k) => match Newtype::try_from(value) { - Err(_) => { - acc = Err(()); - } - Ok(v) => { - m.insert(k, v.0); - } - }, } }); From 1b3667459b789a315b439e9143a4006128728738 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 3 Feb 2024 16:02:25 -0800 Subject: [PATCH 106/234] Abstracted out lots of dynamic parts / added hooks for lots of things --- src/ability/dynamic.rs | 225 ++++++++++++++++------------------- src/ability/js/parentful.rs | 155 ++++++++++++------------ src/ability/js/parentless.rs | 101 +++++++--------- src/promise.rs | 33 ++++- 4 files changed, 253 insertions(+), 261 deletions(-) diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 4ad98974..0ced56ff 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -23,18 +23,106 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq, Debug)] // FIXME serialize / deserilaize? +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, pub args: Arguments, } +// NOTE plug this into Configured like: Configured> +pub struct Builder(pub T); +pub struct Promised(pub T); + +impl> From> for Arguments { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl From> for Builder> { + fn from(configured: Configured) -> Self { + Builder(configured) + } +} + +impl From>> for Configured { + fn from(builder: Builder>) -> Self { + builder.0 + } +} + +impl> From> for Arguments { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + +impl From> for Promised> { + fn from(configured: Configured) -> Self { + Promised(configured) + } +} + +impl From>> for Configured { + fn from(promised: Promised>) -> Self { + promised.0 + } +} + +// NOTE to self: this is helpful as a common container to lift various FFI into +#[derive(Clone, PartialEq, Debug)] +pub struct Configured { + pub arguments: Arguments, + pub config: T, +} + +impl Delegatable for Configured { + type Builder = Builder>; +} + +impl Resolvable for Configured { + type Promised = Promised>; +} + +impl ToCommand for Configured { + fn to_command(&self) -> String { + self.config.to_command() + } +} + +impl CheckSame for Configured { + type Error = T::Error; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.config.check_same(&proof.config) + } +} + +impl CheckParents for Configured { + type Parents = Dynamic; + type ParentError = T::ParentError; + + fn check_parents(&self, parent: &Dynamic) -> Result<(), Self::ParentError> { + self.check_parents(parent) + } +} + +impl From> for Arguments { + fn from(reader: Configured) -> Self { + reader.arguments + } +} + impl From for Arguments { fn from(dynamic: Dynamic) -> Self { dynamic.args } } +impl Checkable for Configured { + type Hierarchy = T::Hierarchy; +} + #[cfg(target_arch = "wasm32")] impl From for js_sys::Map { fn from(ability: Dynamic) -> Self { @@ -104,125 +192,16 @@ impl CheckSame for Dynamic { } } -// impl Debug for Generic { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// f.debug_struct("Generic") -// .field("cmd", &self.cmd) -// .field("args", &self.args) -// .field("is_nonce_meaningful", &self.is_nonce_meaningful) -// .finish() -// } -// } -// -// pub type Dynamic = Generic; -// pub type Promised = Generic, F>; -// -// impl Serialize for Generic -// where -// Arguments: From, -// { -// fn serialize(&self, serializer: S) -> Result -// where -// S: Serializer, -// { -// let mut map = serializer.serialize_map(Some(2))?; -// map.serialize_entry("cmd", &self.cmd)?; -// map.serialize_entry("args", &Arguments::from(self.args.clone()))?; -// map.end() -// } -// } -// -// impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { -// fn deserialize(deserializer: D) -> Result, D::Error> -// where -// D: Deserializer<'de>, -// { -// // FIXME -// todo!() -// // let btree = BTreeMap::deserialize(deserializer)?; -// // Ok(Generic { -// // cmd: btree.get("cmd")?.to_string(), -// // args: btree.get("args")?.clone(), -// // is_nonce_meaningful: DefaultTrue::default(), -// // -// // same_validator: (), -// // parent_validator: (), -// // shape_validator: (), -// // }) -// } -// } -// -// impl ToCommand for Generic { -// fn to_command(&self) -> String { -// self.cmd.clone() -// } -// } -// -// impl Delegatable for Dynamic { -// type Builder = Dynamic; -// } -// -// impl Resolvable for Dynamic { -// type Promised = Promised; -// } -// -// impl From> for Ipld { -// fn from(generic: Generic) -> Self { -// generic.into() -// } -// } -// -// impl TryFrom for Generic { -// type Error = SerdeError; -// -// fn try_from(ipld: Ipld) -> Result { -// ipld_serde::from_ipld(ipld) -// } -// } -// -// impl, F> From> for Arguments { -// fn from(generic: Generic) -> Self { -// generic.args.into() -// } -// } -// -// impl TryFrom> for Dynamic { -// type Error = (); // FIXME -// -// fn try_from(awaiting: Promised) -> Result { -// if let Promise::Resolved(args) = &awaiting.args { -// Ok(Dynamic { -// cmd: awaiting.cmd, -// args: args.clone(), -// -// same_validator: awaiting.same_validator, -// parent_validator: awaiting.parent_validator, -// shape_validator: awaiting.shape_validator, -// is_nonce_meaningful: awaiting.is_nonce_meaningful, -// }) -// } else { -// Err(()) -// } -// } -// } -// -// impl From> for Promised { -// fn from(d: Dynamic) -> Self { -// Promised { -// cmd: d.cmd, -// args: Promise::Resolved(d.args), -// -// same_validator: d.same_validator, -// parent_validator: d.parent_validator, -// shape_validator: d.shape_validator, -// is_nonce_meaningful: d.is_nonce_meaningful, -// } -// } -// } -// -// impl Checkable for Dynamic -// where -// F: Fn(&String, &Arguments) -> Result<(), String>, -// { -// type Hierarchy = Parentless>; // FIXME I bet we can revover parents -// } +impl From for Ipld { + fn from(dynamic: Dynamic) -> Self { + dynamic.into() + } +} + +impl TryFrom for Dynamic { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index ed103b7b..cb41549a 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -3,89 +3,81 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use js_sys::{Function, Map, Object, Reflect}; +use js_sys::{Function, JsString, Map, Object, Reflect}; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -// FIXME rename to module -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct JsWithParents { - // FIXME just inline and use from - #[wasm_bindgen(skip)] - pub ability: dynamic::Dynamic, - - #[wasm_bindgen(skip)] - pub config: Config, +// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, +// and we (Rust) wire it up and run it for you +// NOTE becuase of the above, no need to export JsWithParents to JS +// FIXME rename +type JsWithParents = dynamic::Configured; - #[wasm_bindgen(skip)] - pub check_parents: Function, -} +// Promise = Promise? Ah, nope becuase we need that CID on the promise +// FIXME represent promises (for Promised) and options (for builder) -// FIXME just inline +// FIXME rename ability? abilityconfig? leave as is? #[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen(getter_with_clone)] pub struct Config { + pub command: String, pub is_nonce_meaningful: bool, + pub validate_shape: Function, pub check_same: Function, + + #[wasm_bindgen(skip)] + pub check_parents: BTreeMap>, } #[wasm_bindgen] -impl JsWithParents { - // FIXME consider using an object with named fields - // FIXME needs borrows? +impl Config { + // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( - cmd: String, - args: Object, + command: String, is_nonce_meaningful: bool, - validate_shape: js_sys::Function, // FIXME Need to actuallyrun this on create - check_same: js_sys::Function, - check_parents: js_sys::Function, // FIXME what is an object? i.e. {cmd: handler}? - ) -> JsWithParents { - JsWithParents { - ability: dynamic::Dynamic { - cmd, - args: (&args).into(), + validate_shape: Function, + check_same: Function, + check_parents: Map, // FIXME swap for an object? + ) -> Result { + Ok(Config { + command, + is_nonce_meaningful, + validate_shape, + check_same, + check_parents: { + let mut btree = BTreeMap::new(); + let mut acc = Ok(()); + // Correct order + check_parents.for_each(&mut |value, key| { + if let Ok(_) = &acc { + match key.as_string() { + None => acc = Err(JsString::from("Key is not a string")), // FIXME better err + Some(str_key) => match value.dyn_ref::() { + None => acc = Err("Value is not a function".into()), + Some(f) => { + btree.insert(str_key, Box::new(f.clone())); + acc = Ok(()); + } + }, + } + } + }); + + acc.map(|_| btree)? }, - config: Config { - is_nonce_meaningful, - validate_shape, - check_same, - }, - check_parents, - } - } - - #[wasm_bindgen(getter)] - pub fn is_nonce_meaningful(&self) -> bool { - self.config.is_nonce_meaningful - } - - pub fn check_shape(&self) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .validate_shape - .call1(&this, &self.ability.args.clone().into()) - .map(|_| ()) - } - - // FIXME throws on Err - pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .check_same - .call2(&this, &self.ability.args.clone().into(), proof) - .map(|_| ()) + }) } +} - // FIXME work with native Rust abilities, too - pub fn check_parents(&self, parent: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.check_parents - .call2(&this, &self.ability.args.clone().into(), parent) - .map(|_| ()) +impl From for dynamic::Dynamic { + fn from(js: JsWithParents) -> Self { + dynamic::Dynamic { + cmd: js.config.command, + args: js.arguments, + } } } @@ -94,7 +86,15 @@ impl CheckSame for JsWithParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.check_same(&proof.ability.args.clone().into()) + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2( + &this, + &self.arguments.clone().into(), + &Arguments::from(proof.clone()).into(), + ) + .map(|_| ()) } } @@ -103,23 +103,24 @@ impl CheckParents for JsWithParents { type ParentError = JsValue; fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - let obj = Object::new(); - Reflect::set(&obj, &"cmd".into(), &parent.cmd.clone().into())?; - Reflect::set(&obj, &"args".into(), &parent.args.clone().into())?; - - self.check_parents(&obj) + if let Some(handler) = self.config.check_parents.get(&parent.cmd) { + let this = wasm_bindgen::JsValue::NULL; + handler + .call2( + &this, + &self.arguments.clone().into(), + &parent.args.clone().into(), + ) + .map(|_| ()) + } else { + Err(JsValue::from("No handler for parent")) + } } } -impl ToCommand for JsWithParents { +impl ToCommand for Config { fn to_command(&self) -> String { - self.ability.cmd.clone() - } -} - -impl From for Arguments { - fn from(js: JsWithParents) -> Self { - js.ability.into() + self.command.clone() } } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 75296b92..e486fd23 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,76 +1,57 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, ipld, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{checkable::Checkable, parentless::Parentless, parents::CheckParents, same::CheckSame}, }; -use js_sys::{Function, Map, Object, Reflect}; +use js_sys::{Function, JsString, Map, Object, Reflect}; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct JsWithoutParents { - #[wasm_bindgen(skip)] - pub ability: dynamic::Dynamic, - - #[wasm_bindgen(skip)] - pub config: Config, -} +// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, +// and we (Rust) wire it up and run it for you +// NOTE becuase of the above, no need to export JsWithParents to JS +// FIXME rename +type JsWithoutParents = dynamic::Configured; -// FIXME just inline +// FIXME rename ability? abilityconfig? leave as is? #[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen(getter_with_clone)] pub struct Config { + pub command: String, pub is_nonce_meaningful: bool, + pub validate_shape: Function, pub check_same: Function, } +// FIXME represent promises (for Promised) and options (for builder) + #[wasm_bindgen] -impl JsWithoutParents { - // FIXME consider using an object with named fields - // FIXME needs borrows? +impl Config { + // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( - cmd: String, - args: Object, + command: String, is_nonce_meaningful: bool, - validate_shape: js_sys::Function, - check_same: js_sys::Function, - ) -> JsWithoutParents { - JsWithoutParents { - ability: dynamic::Dynamic { - cmd, - args: (&args).into(), - }, - config: Config { - is_nonce_meaningful, - validate_shape, - check_same, - }, + validate_shape: Function, + check_same: Function, + ) -> Config { + Config { + command, + is_nonce_meaningful, + validate_shape, + check_same, } } +} - #[wasm_bindgen(getter)] - pub fn is_nonce_meaningful(&self) -> bool { - self.config.is_nonce_meaningful - } - - pub fn check_shape(&self) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .validate_shape - .call1(&this, &self.ability.args.clone().into()) - .map(|_| ()) - } - - // FIXME throws on Err - pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .check_same - .call2(&this, &self.ability.args.clone().into(), proof) - .map(|_| ()) +impl From for dynamic::Dynamic { + fn from(js: JsWithoutParents) -> Self { + dynamic::Dynamic { + cmd: js.config.command, + args: js.arguments, + } } } @@ -79,19 +60,21 @@ impl CheckSame for JsWithoutParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.check_same(&proof.ability.args.clone().into()) + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2( + &this, + &self.arguments.clone().into(), + &Arguments::from(proof.clone()).into(), + ) + .map(|_| ()) } } -impl ToCommand for JsWithoutParents { +impl ToCommand for Config { fn to_command(&self) -> String { - self.ability.cmd.clone() - } -} - -impl From for Arguments { - fn from(js: JsWithoutParents) -> Self { - js.ability.into() + self.command.clone() } } diff --git a/src/promise.rs b/src/promise.rs index 3a0792cc..78b0f0f9 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // FIXME move under invocation? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -12,6 +15,32 @@ pub enum Promise { Waiting(Selector), } +#[cfg(target_arch = "wasm32")] +pub enum JsStatus { + Resolved, + Waiting, +} + +// FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? +// FIXME consider wrapping in a trait +#[cfg(target_arch = "wasm32")] +pub struct JsPromise { + status: JsStatus, + selector: Selector, + value: Option, +} + +// TODO remove; I'd rather have liine 70 blanket than this +// #[cfg(target_arch = "wasm32")] +// impl> From for Promise { +// fn from(js: JsPromise) -> Self { +// match js.status { +// JsStatus::Resolved => Promise::Resolved(js.value.unwrap().into()), +// JsStatus::Waiting => Promise::Waiting(js.selector), +// } +// } +// } + impl Promise { pub fn map(self, f: F) -> Promise where @@ -43,8 +72,8 @@ impl Promise { } impl From for Promise { - fn from(t: T) -> Self { - Promise::Resolved(t) + fn from(value: T) -> Self { + Promise::Resolved(value) } } From 9209f75128ee3ecc617dde988cefe42f5dda36af Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 11:25:57 -0800 Subject: [PATCH 107/234] Save before compiling JS bindings --- src/ability/crud/read.rs | 20 +++++++++++++++++++- src/ability/js/parentful.rs | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f98fc969..d04d887b 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -8,6 +8,9 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -16,7 +19,22 @@ pub struct Read { pub uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, + pub args: Option>, // FIXME rename Argumenst to get the traits? +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct Js(#[wasm_bindgen(skip)] pub Read); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Js { + // FIXME + pub fn check_same(&self, proof: &Js) -> Result<(), JsValue> { + self.0 + .check_same(&proof.0) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + } } impl Command for Read { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index cb41549a..726e2c15 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -99,7 +99,7 @@ impl CheckSame for JsWithParents { } impl CheckParents for JsWithParents { - type Parents = dynamic::Dynamic; + type Parents = dynamic::Dynamic; // FIXME actually no? What if we want to plug in random stuff? type ParentError = JsValue; fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { From d24f8f4c8f2ef1d74a7ea6a566e4be37905d162c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 15:14:59 -0800 Subject: [PATCH 108/234] Happily builds for wasm including JS bindgings --- flake.nix | 22 +++++++++++---- src/ability/js/parentful.rs | 52 +++++++++++++++++++++++++++--------- src/ability/js/parentless.rs | 27 +++++++++++-------- src/ability/ucan.rs | 2 +- tsconfig.json | 21 +++++++++++++++ 5 files changed, 95 insertions(+), 29 deletions(-) create mode 100644 tsconfig.json diff --git a/flake.nix b/flake.nix index a14844a0..7527ff89 100644 --- a/flake.nix +++ b/flake.nix @@ -131,10 +131,16 @@ command = "${cargo} build --release"; } { - name = "release:wasm"; + name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${cargo} build --release --target=wasm32-unknown-unknown"; + command = "${wasm-pack} build --release --target=web"; + } + { + name = "release:wasm:nodejs"; + help = "Release for current host target"; + category = "release"; + command = "${wasm-pack} build --release --target=nodejs"; } # Build { @@ -150,10 +156,16 @@ command = "${cargo} build"; } { - name = "build:wasm"; - help = "Build for wasm32-unknown-unknown"; + name = "build:wasm:web"; + help = "Build for wasm32-unknown-unknown with web bindings"; + category = "build"; + command = "${wasm-pack} build --dev --target=web"; + } + { + name = "build:wasm:nodejs"; + help = "Build for wasm32-unknown-unknown with Node.js bindgings"; category = "build"; - command = "${cargo} build --target=wasm32-unknown-unknown"; + command = "${wasm-pack} build --dev --target=nodejs"; } { name = "build:node"; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 726e2c15..5593c975 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -18,7 +18,7 @@ type JsWithParents = dynamic::Configured; // FIXME represent promises (for Promised) and options (for builder) // FIXME rename ability? abilityconfig? leave as is? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] pub struct Config { pub command: String, @@ -31,27 +31,55 @@ pub struct Config { pub check_parents: BTreeMap>, } +#[wasm_bindgen(typescript_custom_section)] +const CONSTRUCTOR_WITH_MAP: &str = r#" +interface ConfigArgs { + command: string, + is_nonce_meaningful: boolean, + validate_shape: Function, + check_same: Function, + check_parents: Map +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ConfigArgs")] + pub type ConfigArgs; + + pub fn command(this: &ConfigArgs) -> String; + + pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; + + pub fn validate_shape(this: &ConfigArgs) -> Function; + + pub fn check_same(this: &ConfigArgs) -> Function; + + pub fn check_parents(this: &ConfigArgs) -> Map; +} + #[wasm_bindgen] impl Config { // FIXME object args as an option - #[wasm_bindgen(constructor)] + #[wasm_bindgen(constructor, typescript_type = "ConfigArgs")] pub fn new( - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, - check_parents: Map, // FIXME swap for an object? + js: ConfigArgs, + // command: String, + // is_nonce_meaningful: bool, + // validate_shape: Function, + // check_same: Function, + // check_parents: Map, // FIXME swap for an object? ) -> Result { Ok(Config { - command, - is_nonce_meaningful, - validate_shape, - check_same, + command: command(&js), + is_nonce_meaningful: is_nonce_meaningful(&js), + validate_shape: validate_shape(&js), + check_same: check_same(&js), check_parents: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); // Correct order - check_parents.for_each(&mut |value, key| { + check_parents(&js).for_each(&mut |value, key| { if let Ok(_) = &acc { match key.as_string() { None => acc = Err(JsString::from("Key is not a string")), // FIXME better err diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index e486fd23..3ac77556 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -12,23 +12,28 @@ use wasm_bindgen::{prelude::*, JsValue}; // and we (Rust) wire it up and run it for you // NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type JsWithoutParents = dynamic::Configured; +type JsWithoutParents = dynamic::Configured; // FIXME rename ability? abilityconfig? leave as is? +// #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen(getter_with_clone)] -pub struct Config { - pub command: String, - pub is_nonce_meaningful: bool, +#[wasm_bindgen] +pub struct Config1 { + // #[wasm_bindgen(skip)] + command: String, + is_nonce_meaningful: bool, + + // #[wasm_bindgen(skip)] + validate_shape: Function, - pub validate_shape: Function, - pub check_same: Function, + //#[wasm_bindgen(skip)] + check_same: Function, } // FIXME represent promises (for Promised) and options (for builder) #[wasm_bindgen] -impl Config { +impl Config1 { // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( @@ -36,8 +41,8 @@ impl Config { is_nonce_meaningful: bool, validate_shape: Function, check_same: Function, - ) -> Config { - Config { + ) -> Config1 { + Config1 { command, is_nonce_meaningful, validate_shape, @@ -72,7 +77,7 @@ impl CheckSame for JsWithoutParents { } } -impl ToCommand for Config { +impl ToCommand for Config1 { fn to_command(&self) -> String { self.command.clone() } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index af18fe21..59ddbe7d 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -10,7 +10,7 @@ use std::fmt::Debug; // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -struct Generic { +pub struct Generic { pub cmd: String, pub args: Args, // FIXME Does this have specific fields? } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..e40fe060 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "module": "es2015", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} From e5fdd538be6983e947d4fed36879f38677b84f82 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 15:48:30 -0800 Subject: [PATCH 109/234] Configure wasm-pack & wasm-opt --- flake.nix | 5 +++-- src/ability/js/parentful.rs | 30 ++++++++++-------------------- tsconfig.json | 35 ++++++++++++++++------------------- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/flake.nix b/flake.nix index 7527ff89..2b63841f 100644 --- a/flake.nix +++ b/flake.nix @@ -86,6 +86,7 @@ cargo = "${pkgs.cargo}/bin/cargo"; node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; + wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; in rec { devShells.default = pkgs.devshell.mkShell { name = "ucan"; @@ -134,13 +135,13 @@ name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=web"; + command = "${wasm-pack} build --release --target=web && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lh pkg/*.wasm | cut -d ' ' -f 8,13"; } { name = "release:wasm:nodejs"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=nodejs"; + command = "${wasm-pack} build --release --target=nodejs && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lah pkg/*.wasm | cut -d ' ' -f 8,13"; } # Build { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 5593c975..b96c0101 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -32,7 +32,7 @@ pub struct Config { } #[wasm_bindgen(typescript_custom_section)] -const CONSTRUCTOR_WITH_MAP: &str = r#" +const CONFIG_ARGS: &str = r#" interface ConfigArgs { command: string, is_nonce_meaningful: boolean, @@ -48,38 +48,28 @@ extern "C" { pub type ConfigArgs; pub fn command(this: &ConfigArgs) -> String; - pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; - pub fn validate_shape(this: &ConfigArgs) -> Function; - pub fn check_same(this: &ConfigArgs) -> Function; - pub fn check_parents(this: &ConfigArgs) -> Map; } #[wasm_bindgen] impl Config { // FIXME object args as an option - #[wasm_bindgen(constructor, typescript_type = "ConfigArgs")] - pub fn new( - js: ConfigArgs, - // command: String, - // is_nonce_meaningful: bool, - // validate_shape: Function, - // check_same: Function, - // check_parents: Map, // FIXME swap for an object? - ) -> Result { + #[wasm_bindgen(constructor)] + pub fn new(js_obj: ConfigArgs) -> Result { Ok(Config { - command: command(&js), - is_nonce_meaningful: is_nonce_meaningful(&js), - validate_shape: validate_shape(&js), - check_same: check_same(&js), + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + check_same: check_same(&js_obj), check_parents: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); - // Correct order - check_parents(&js).for_each(&mut |value, key| { + + check_parents(&js_obj).for_each(&mut |value, key| { + // |value, key| is correct ------^^^^^^^^^^^^ if let Ok(_) = &acc { match key.as_string() { None => acc = Err(JsString::from("Key is not a string")), // FIXME better err diff --git a/tsconfig.json b/tsconfig.json index e40fe060..b6828dc2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,18 @@ { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "module": "es2015", - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - } + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "module": "es2015", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2015" + ] + } } From 220c9a4c4d845815d72aab8c89e9ee2f27f0b092 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 23:59:07 -0800 Subject: [PATCH 110/234] Starting on high-level interfaces :D --- flake.nix | 4 +- src/ability.rs | 2 +- src/ability/arguments.rs | 4 +- src/ability/command.rs | 5 +- src/ability/crud/any.rs | 22 ++++++ src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 6 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/parents.rs | 2 +- src/ability/crud/read.rs | 74 ++++++++++++++------ src/ability/crud/update.rs | 2 +- src/ability/dynamic.rs | 102 ++------------------------- src/ability/js/parentful.rs | 73 +++++++++----------- src/ability/js/parentless.rs | 79 +++++++++++---------- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 17 +++-- src/ability/ucan.rs | 86 +---------------------- src/ability/ucan/proxy.rs | 87 +++++++++++++++++++++++ src/agent.rs | 59 ++++++++++++++++ src/delegation.rs | 31 ++++++++- src/delegation/payload.rs | 9 +-- src/delegation/store.rs | 83 ++++++++++++++++++++++ src/did.rs | 13 ++-- src/invocation.rs | 4 +- src/invocation/payload.rs | 40 ++++++----- src/{ => invocation}/promise.rs | 103 ++++++++++++++++++--------- src/lib.rs | 3 +- src/nonce.rs | 3 - src/proof/parentful.rs | 8 +-- src/proof/parents.rs | 2 +- src/reader.rs | 119 ++++++++++++++++++++++++++++++++ src/task.rs | 25 +------ src/time.rs | 3 +- 33 files changed, 672 insertions(+), 404 deletions(-) create mode 100644 src/ability/ucan/proxy.rs create mode 100644 src/agent.rs create mode 100644 src/delegation/store.rs rename src/{ => invocation}/promise.rs (54%) create mode 100644 src/reader.rs diff --git a/flake.nix b/flake.nix index 2b63841f..9cbbda01 100644 --- a/flake.nix +++ b/flake.nix @@ -135,13 +135,13 @@ name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=web && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lh pkg/*.wasm | cut -d ' ' -f 8,13"; + command = "${wasm-pack} build --release --target=web"; } { name = "release:wasm:nodejs"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=nodejs && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lah pkg/*.wasm | cut -d ' ' -f 8,13"; + command = "${wasm-pack} build --release --target=nodejs"; } # Build { diff --git a/src/ability.rs b/src/ability.rs index a6b095ad..fe0363ba 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -10,7 +10,7 @@ pub mod command; #[cfg(target_arch = "wasm32")] pub mod js; -// // TODO move to crate::wasm? or hide behind feature flag? +// // TODO move to crate::wasm? or hide behind "dynamic" feature flag? #[cfg(target_arch = "wasm32")] pub mod dynamic; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index b67f6608..9a6e28cb 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -6,13 +6,11 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Reflect, Uint8Array}; +use js_sys::{Array, Map, Object, Reflect}; #[cfg(target_arch = "wasm32")] use crate::ipld; -use super::wasm; - // FIXME yes I'm seriously considering laying this out in the wasm abi by han d // #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/ability/command.rs b/src/ability/command.rs index bd86bf36..a158221e 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -2,8 +2,9 @@ pub trait Command { const COMMAND: &'static str; } -// NOTE do not export // FIXME move to internal? -pub(super) trait ToCommand { +// NOTE do not export; this is used to limit the Hierarchy +// interface to [Parentful] and [Parentless] while enabling [Dynamic] +pub(crate) trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 909d09a6..15149fa3 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -8,6 +8,9 @@ use crate::{ use serde::{Deserialize, Serialize}; use url::Url; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -29,3 +32,22 @@ impl CheckSame for Builder { self.uri.check_same(&proof.uri) } } + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); + +// FIXME macro this away +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl CrudAny { + pub fn command(&self) -> String { + Builder::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsValue> { + self.0 + .check_same(&proof.0) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index a1452d29..5270fb2c 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -56,7 +56,7 @@ impl CheckParents for Create { type Parents = Mutable; type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_uri) = &self.uri { match other { Mutable::Any(any) => { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 4432d2e2..6d9bd003 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -49,10 +49,10 @@ impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(mutate) => Ok(()), // FIXME - Mutable::Any(any) => Ok(()), // FIXME + Mutable::Mutate(_mutate) => Ok(()), // FIXME + Mutable::Any(_any) => Ok(()), // FIXME } } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 9c1fd8fe..0e5590ec 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -48,7 +48,7 @@ impl CheckParents for MutateBuilder { type Parents = any::Builder; type ParentError = (); - fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index f42b25a8..bd03b9e8 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -15,7 +15,7 @@ impl CheckSame for Mutable { match self { Mutable::Mutate(mutate) => match proof { Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), - Mutable::Any(any) => Ok(()), + Mutable::Any(_any) => Ok(()), }, _ => Err(()), } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index d04d887b..f07a2b61 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -3,9 +3,10 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use thiserror::Error; use url::Url; #[cfg(target_arch = "wasm32")] @@ -22,21 +23,6 @@ pub struct Read { pub args: Option>, // FIXME rename Argumenst to get the traits? } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct Js(#[wasm_bindgen(skip)] pub Read); - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl Js { - // FIXME - pub fn check_same(&self, proof: &Js) -> Result<(), JsValue> { - self.0 - .check_same(&proof.0) - .map_err(|err| JsValue::from_str(&format!("{:?}", err))) - } -} - impl Command for Read { const COMMAND: &'static str = "crud/read"; } @@ -48,28 +34,72 @@ impl From for Ipld { } impl TryFrom for Read { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } +// FIXME +#[derive(Debug, Error)] +pub enum E { + #[error("Some error")] + SomeErrMsg(String), +} + impl Checkable for Read { type Hierarchy = Parentful; } impl CheckSame for Read { - type Error = (); + type Error = E; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(uri) = &self.uri { + if uri != proof.uri.as_ref().unwrap() { + return Err(E::SomeErrMsg("".into())); + } + } + + if let Some(args) = &self.args { + if let Some(proof_args) = &proof.args { + for (k, v) in args { + if proof_args.get(k) != Some(v) { + return Err(E::SomeErrMsg("".into())); + } + } + } + } + Ok(()) } } impl CheckParents for Read { type Parents = any::Builder; - type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) + type ParentError = E; + + fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) // FIXME + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl CrudRead { + pub fn command(&self) -> String { + Read::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(Into::into) + } + + pub fn check_parent(&self, proof: &any::CrudAny) -> Result<(), JsError> { + self.0.check_parent(&proof.0).map_err(Into::into) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 83249052..62378050 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -78,7 +78,7 @@ impl CheckParents for UpdateBuilder { type Parents = Mutable; type ParentError = (); // FIXME - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 0ced56ff..8d3acb06 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,22 +1,10 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{ - delegation::Delegatable, - invocation::Resolvable, - ipld, - promise::Promise, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, - same::CheckSame, - }, - task::DefaultTrue, -}; +use crate::{ipld, proof::same::CheckSame}; use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; use wasm_bindgen::prelude::*; @@ -29,87 +17,9 @@ pub struct Dynamic { pub args: Arguments, } -// NOTE plug this into Configured like: Configured> -pub struct Builder(pub T); -pub struct Promised(pub T); - -impl> From> for Arguments { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Builder> { - fn from(configured: Configured) -> Self { - Builder(configured) - } -} - -impl From>> for Configured { - fn from(builder: Builder>) -> Self { - builder.0 - } -} - -impl> From> for Arguments { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - -impl From> for Promised> { - fn from(configured: Configured) -> Self { - Promised(configured) - } -} - -impl From>> for Configured { - fn from(promised: Promised>) -> Self { - promised.0 - } -} - -// NOTE to self: this is helpful as a common container to lift various FFI into -#[derive(Clone, PartialEq, Debug)] -pub struct Configured { - pub arguments: Arguments, - pub config: T, -} - -impl Delegatable for Configured { - type Builder = Builder>; -} - -impl Resolvable for Configured { - type Promised = Promised>; -} - -impl ToCommand for Configured { +impl ToCommand for Dynamic { fn to_command(&self) -> String { - self.config.to_command() - } -} - -impl CheckSame for Configured { - type Error = T::Error; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.config.check_same(&proof.config) - } -} - -impl CheckParents for Configured { - type Parents = Dynamic; - type ParentError = T::ParentError; - - fn check_parents(&self, parent: &Dynamic) -> Result<(), Self::ParentError> { - self.check_parents(parent) - } -} - -impl From> for Arguments { - fn from(reader: Configured) -> Self { - reader.arguments + self.cmd.clone() } } @@ -119,10 +29,6 @@ impl From for Arguments { } } -impl Checkable for Configured { - type Hierarchy = T::Hierarchy; -} - #[cfg(target_arch = "wasm32")] impl From for js_sys::Map { fn from(ability: Dynamic) -> Self { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index b96c0101..72140b27 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,18 +1,17 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, - ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + reader::Reader, }; -use js_sys::{Function, JsString, Map, Object, Reflect}; -use libipld_core::ipld::Ipld; +use js_sys::{Function, JsString, Map}; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; // NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, // and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export JsWithParents to JS +// NOTE becuase of the above, no need to export WithParents to JS // FIXME rename -type JsWithParents = dynamic::Configured; +type WithParents = Reader; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) @@ -28,47 +27,47 @@ pub struct Config { pub check_same: Function, #[wasm_bindgen(skip)] - pub check_parents: BTreeMap>, + pub check_parent: BTreeMap, } #[wasm_bindgen(typescript_custom_section)] const CONFIG_ARGS: &str = r#" -interface ConfigArgs { +interface ParentfulArgs { command: string, is_nonce_meaningful: boolean, validate_shape: Function, check_same: Function, - check_parents: Map + check_parent: Map } "#; #[wasm_bindgen] extern "C" { - #[wasm_bindgen(typescript_type = "ConfigArgs")] - pub type ConfigArgs; - - pub fn command(this: &ConfigArgs) -> String; - pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; - pub fn validate_shape(this: &ConfigArgs) -> Function; - pub fn check_same(this: &ConfigArgs) -> Function; - pub fn check_parents(this: &ConfigArgs) -> Map; + #[wasm_bindgen(typescript_type = "ParentfulArgs")] + pub type ParentfulArgs; + + pub fn command(this: &ParentfulArgs) -> String; + pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + pub fn validate_shape(this: &ParentfulArgs) -> Function; + pub fn check_same(this: &ParentfulArgs) -> Function; + pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] impl Config { // FIXME object args as an option #[wasm_bindgen(constructor)] - pub fn new(js_obj: ConfigArgs) -> Result { + pub fn new(js_obj: ParentfulArgs) -> Result { Ok(Config { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), check_same: check_same(&js_obj), - check_parents: { + check_parent: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); - check_parents(&js_obj).for_each(&mut |value, key| { + check_parent(&js_obj).for_each(&mut |value, key| { // |value, key| is correct ------^^^^^^^^^^^^ if let Ok(_) = &acc { match key.as_string() { @@ -76,7 +75,7 @@ impl Config { Some(str_key) => match value.dyn_ref::() { None => acc = Err("Value is not a function".into()), Some(f) => { - btree.insert(str_key, Box::new(f.clone())); + btree.insert(str_key, f.clone()); acc = Ok(()); } }, @@ -90,46 +89,42 @@ impl Config { } } -impl From for dynamic::Dynamic { - fn from(js: JsWithParents) -> Self { +impl From for dynamic::Dynamic { + fn from(js: WithParents) -> Self { dynamic::Dynamic { - cmd: js.config.command, - args: js.arguments, + cmd: js.env.command, + args: js.val, } } } // FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for JsWithParents { +impl CheckSame for WithParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { let this = wasm_bindgen::JsValue::NULL; - self.config + self.env .check_same .call2( &this, - &self.arguments.clone().into(), + &self.val.clone().into(), &Arguments::from(proof.clone()).into(), ) .map(|_| ()) } } -impl CheckParents for JsWithParents { - type Parents = dynamic::Dynamic; // FIXME actually no? What if we want to plug in random stuff? +impl CheckParents for WithParents { + type Parents = dynamic::Dynamic; type ParentError = JsValue; - fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - if let Some(handler) = self.config.check_parents.get(&parent.cmd) { + fn check_parent(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { + if let Some(handler) = self.env.check_parent.get(&parent.cmd) { let this = wasm_bindgen::JsValue::NULL; handler - .call2( - &this, - &self.arguments.clone().into(), - &parent.args.clone().into(), - ) - .map(|_| ()) + .call2(&this, &self.val.clone().into(), &parent.args.clone().into()) + .map(|_| ()) // FIXME } else { Err(JsValue::from("No handler for parent")) } @@ -142,6 +137,6 @@ impl ToCommand for Config { } } -impl Checkable for JsWithParents { - type Hierarchy = Parentful; +impl Checkable for WithParents { + type Hierarchy = Parentful; } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 3ac77556..1897fa22 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,88 +1,95 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, - ipld, - proof::{checkable::Checkable, parentless::Parentless, parents::CheckParents, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, + reader::Reader, }; -use js_sys::{Function, JsString, Map, Object, Reflect}; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; -use wasm_bindgen::{prelude::*, JsValue}; +use js_sys::Function; +use wasm_bindgen::prelude::*; // NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, // and we (Rust) wire it up and run it for you // NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type JsWithoutParents = dynamic::Configured; +type WithoutParents = Reader; // FIXME rename ability? abilityconfig? leave as is? // #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone, PartialEq)] #[wasm_bindgen] -pub struct Config1 { - // #[wasm_bindgen(skip)] +pub struct ParentlessConfig { command: String, is_nonce_meaningful: bool, - - // #[wasm_bindgen(skip)] validate_shape: Function, - - //#[wasm_bindgen(skip)] check_same: Function, } // FIXME represent promises (for Promised) and options (for builder) +#[wasm_bindgen(typescript_custom_section)] +const PARENTLESS_ARGS: &str = r#" +interface ParentlessArgs { + command: string, + is_nonce_meaningful: boolean, + validate_shape: Function, + check_same: Function, +} +"#; + #[wasm_bindgen] -impl Config1 { +extern "C" { + #[wasm_bindgen(typescript_type = "ParentlessArgs")] + pub type ParentlessArgs; + + pub fn command(this: &ParentlessArgs) -> String; + pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; + pub fn validate_shape(this: &ParentlessArgs) -> Function; + pub fn check_same(this: &ParentlessArgs) -> Function; +} + +#[wasm_bindgen] +impl ParentlessConfig { // FIXME object args as an option #[wasm_bindgen(constructor)] - pub fn new( - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, - ) -> Config1 { - Config1 { - command, - is_nonce_meaningful, - validate_shape, - check_same, + pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { + ParentlessConfig { + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + check_same: check_same(&js_obj), } } } -impl From for dynamic::Dynamic { - fn from(js: JsWithoutParents) -> Self { +impl From for dynamic::Dynamic { + fn from(js: WithoutParents) -> Self { dynamic::Dynamic { - cmd: js.config.command, - args: js.arguments, + cmd: js.env.command, + args: js.val, } } } // FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for JsWithoutParents { +impl CheckSame for WithoutParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { let this = wasm_bindgen::JsValue::NULL; - self.config + self.env .check_same .call2( &this, - &self.arguments.clone().into(), + &self.val.clone().into(), &Arguments::from(proof.clone()).into(), ) .map(|_| ()) } } -impl ToCommand for Config1 { +impl ToCommand for ParentlessConfig { fn to_command(&self) -> String { self.command.clone() } } -impl Checkable for JsWithoutParents { - type Hierarchy = Parentless; -} +impl NoParents for ParentlessConfig {} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c7fb6d68..5583331d 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -33,7 +33,7 @@ impl CheckParents for Receive { type Parents = msg::Any; type ParentError = ::Error; - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&proof.from).map_err(|_| ()) } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 1d7c81a1..194f6d7b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,8 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, delegation::Delegatable, - invocation::Resolvable, - promise::Promise, + invocation::{Promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -58,9 +57,9 @@ impl From for Arguments { impl From for Builder { fn from(awaiting: Promised) -> Self { Builder { - to: awaiting.to.try_extract().ok(), - from: awaiting.from.try_extract().ok(), - message: awaiting.message.try_extract().ok(), + to: awaiting.to.try_resolve().ok(), + from: awaiting.from.try_resolve().ok(), + message: awaiting.message.try_resolve().ok(), } } } @@ -87,7 +86,7 @@ impl CheckParents for Builder { type ParentError = ::Error; // FIXME rename other to proof - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&other.from).map_err(|_| ()) } } @@ -117,9 +116,9 @@ impl TryFrom for Resolved { fn try_from(awaiting: Promised) -> Result { Ok(Generic { - to: awaiting.to.try_extract().map_err(|_| ())?, - from: awaiting.from.try_extract().map_err(|_| ())?, - message: awaiting.message.try_extract().map_err(|_| ())?, + to: awaiting.to.try_resolve().map_err(|_| ())?, + from: awaiting.from.try_resolve().map_err(|_| ())?, + message: awaiting.message.try_resolve().map_err(|_| ())?, }) } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 59ddbe7d..830f7c22 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,84 +1,2 @@ -use super::arguments::Arguments; -use crate::{ability::command::Command, delegation::Delegatable, promise::Promise}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -// NOTE This one is primarily for enabling delegationd recipets - -// FIXME aslo add revokation, so thsi module needs to be broken up - -// FIXME -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - pub cmd: String, - pub args: Args, // FIXME Does this have specific fields? -} - -pub type Resolved = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; - -impl Command for Generic { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl Delegatable for Resolved { - type Builder = Builder; -} - -impl From for Builder { - fn from(resolved: Resolved) -> Builder { - Builder { - cmd: resolved.cmd, - args: Some(resolved.args), - } - } -} - -impl TryFrom for Resolved { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Resolved { - cmd: b.cmd, - args: b.args.ok_or(())?, - }) - } -} - -impl From for Arguments { - fn from(b: Builder) -> Arguments { - let mut args = b.args.unwrap_or_default(); - args.insert("cmd".into(), Ipld::String(b.cmd)); - args - } -} - -// // FIXME hmmm -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } +pub mod proxy; +// FIXME pub mod revoke; diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs new file mode 100644 index 00000000..4851356a --- /dev/null +++ b/src/ability/ucan/proxy.rs @@ -0,0 +1,87 @@ +use crate::{ + ability::{arguments::Arguments, command::Command}, + delegation::Delegatable, + invocation::Promise, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +// NOTE This one is primarily for enabling delegationd recipets + +// FIXME can this *only* be a builder? +// NOTE UNLIKE the dynamic ability, this has cmd as an argument *at runtime* +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + pub cmd: String, + pub args: Args, // FIXME Does this have specific fields? + // FIXME should args just be a CID +} + +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; + +impl Command for Generic { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl Delegatable for Resolved { + type Builder = Builder; +} + +impl From for Builder { + fn from(resolved: Resolved) -> Builder { + Builder { + cmd: resolved.cmd, + args: Some(resolved.args), + } + } +} + +impl TryFrom for Resolved { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Resolved { + cmd: b.cmd, + args: b.args.ok_or(())?, + }) + } +} + +impl From for Arguments { + fn from(b: Builder) -> Arguments { + let mut args = b.args.unwrap_or_default(); + args.insert("cmd".into(), Ipld::String(b.cmd)); + args + } +} + +// // FIXME hmmm need to decide on the exact shape of this +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 00000000..aed278bd --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,59 @@ +use crate::{ + ability::command::ToCommand, + delegation::{traits::Condition, Delegatable, Delegation}, + did::Did, + invocation::Invocation, + metadata as meta, + proof::parents::CheckParents, +}; + +pub struct Agent { + pub did: Did, + // pub key: signature::Key, + pub store: S, +} + +impl Agent { + // pub fn delegate(&self, payload: Payload) -> Delegation { + // let signature = self.key.sign(payload); + // signature::Envelope::new(payload, signature) + // } + + pub fn invoke( + &self, + delegation: Delegation, + proof_chain: Vec>, // FIXME T must also accept Self and * + ) -> () + where + T::Parents: Delegatable, + { + todo!() + } + + pub fn try_invoke(&self, ability: A) { + todo!() + } + + pub fn revoke( + &self, + delegation: Delegation, + ) -> () +// where +// T::Parents: Delegatable, + { + todo!() + } + + pub fn receive_delegation( + &self, + delegation: Delegation, + ) -> () { + todo!() + } + + pub fn receive_invocation(&self, invocation: Invocation) -> () { + todo!() + } + + // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache +} diff --git a/src/delegation.rs b/src/delegation.rs index 945f90f7..01584561 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -2,12 +2,16 @@ mod condition; mod delegatable; mod payload; -pub use condition::*; +pub mod store; +pub use condition::*; pub use delegatable::Delegatable; pub use payload::Payload; -use crate::signature; +use condition::traits::Condition; +use store::IndexedStore; + +use crate::{metadata as meta, signature}; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -17,4 +21,25 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; -// FIXME add a store with delegation indexing +// FIXME +impl Delegation { + // FIXME include cache + //pub fn check>(&self, store: &S) -> Result<(), ()> { + // if let Ok(is_valid) = store.previously_checked(self) { + // if is_valid { + // return Ok(()); + // } + // } + + // if let Ok(chains) = store.chains_for(self) { + // for chain in chains { + // todo!() + // // if self.check_self(self).is_ok() { + // // return Ok(()); + // // } + // } + // } + + // Err(()) + //} +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2161dc63..752c3fe7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,6 @@ use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::{arguments::Arguments, command::Command, dynamic}, + ability::{arguments::Arguments, command::Command}, capsule::Capsule, did::Did, invocation, @@ -92,18 +92,19 @@ impl From> for } } +// FIXME this likely should move to invocation impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegation/store.rs b/src/delegation/store.rs new file mode 100644 index 00000000..438e8272 --- /dev/null +++ b/src/delegation/store.rs @@ -0,0 +1,83 @@ +use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; +use crate::{did::Did, metadata as meta}; +use libipld_core::cid::Cid; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use web_time::SystemTime; + +// NOTE can already look up by CID in other traits +pub trait IndexedStore { + type Error; + + fn get_by(query: Query) -> Result>, Self::Error>; + + fn previously_checked(cid: Cid) -> Result; + + // NOTE you can override this with something much more efficient in e.g. SQL + // FIXME "should" be checked and indexed on the way in + fn chains_for( + &self, + subject: Did, + command: String, + audience: Did, + ) -> Result)>>, Self::Error>; + // if let Ok(possible) = self.get_by(Query { + // audience: Some(audience), + // command: Some(command), + // after_not_before: Some(SystemTime::now()), + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) { + // let acc = Ok(vec![]); + // let iss = possible.iter().next().unwrap().1.issuer; + + // // FIXME actually more complex than this: + // // sicne the chain also has to be valid + // // ...we shoud probably index on the way in + // while acc.is_ok() { + // if let Ok(latest) = get_one(Query { + // subject: Some(subject), + // command: Some(command), + // audience: Some(latest_iss), + // after_not_before: Some(SystemTime::now()), + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) { + // acc.push(latest); + + // if delegation. + // latest_iss = delegation.issuer; + // } else { + // acc = Err(()); // FIXME + // } + // } + // } + + fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { + todo!() + //let mut results = Self::get_by(query)?; + //results.pop().ok_or_else(|_| todo!()) + } + + fn expired(&self) -> Result>, Self::Error> { + todo!() + // self.get_by(Query { + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) + } +} + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Query { + pub subject: Option, + pub command: Option, + pub issuer: Option, + pub audience: Option, + + pub prior_to_not_before: Option, // FIXME time + pub after_not_before: Option, // FIXME time + + pub expires_before: Option, // FIXME time + pub expires_aftre: Option, // FIXME time +} diff --git a/src/did.rs b/src/did.rs index 8b856104..2f1aae78 100644 --- a/src/did.rs +++ b/src/did.rs @@ -14,12 +14,10 @@ impl From for String { } impl TryFrom for Did { - type Error = String; // FIXME + type Error = >::Error; fn try_from(string: String) -> Result { - DID::parse(&string) - .map_err(|err| format!("Failed to parse DID: {}", err)) - .map(Self) + DID::parse(&string).map(Did) } } @@ -36,9 +34,12 @@ impl From for Ipld { } impl TryFrom for Did { - type Error = (); // FIXME + type Error = >::Error; // FIXME also include the "can't parse form ipld" case; seems like someythjing taht can be abstrcated out, too fn try_from(ipld: Ipld) -> Result { - Self::try_from(ipld) + match ipld { + Ipld::String(string) => Did::try_from(string), + _ => todo!(), // Err(()), + } } } diff --git a/src/invocation.rs b/src/invocation.rs index 5cb2b95e..1e2a8a40 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,10 +1,12 @@ mod payload; +mod promise; mod resolvable; mod serializer; pub use payload::{Payload, Unresolved}; +pub use promise::Promise; pub use resolvable::Resolvable; use crate::signature; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 72921dcd..ee9c0ed0 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,19 +1,21 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments::Arguments, command::Command, dynamic}, + ability::{arguments::Arguments, command::Command}, capsule::Capsule, did::Did, + metadata as meta, + metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -22,7 +24,7 @@ pub struct Payload { pub proofs: Vec, pub cause: Option, - pub metadata: BTreeMap, // FIXME parameterize? + pub metadata: Metadata, pub nonce: Nonce, pub not_before: Option, @@ -30,7 +32,7 @@ pub struct Payload { } // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions @@ -46,14 +48,14 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -64,10 +66,10 @@ where } } -impl<'de, T> serde::Deserialize<'de> for Payload +impl<'de, T, E: meta::Entries> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -82,9 +84,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -94,8 +96,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -193,8 +195,8 @@ impl TryFrom for InternalSerializer { // } // } -impl> From> for InternalSerializer { - fn from(payload: Payload) -> Self { +impl, E: meta::Entries> From> for InternalSerializer { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -205,7 +207,7 @@ impl> From> for InternalSerializer { proofs: payload.proofs, cause: payload.cause, - metadata: payload.metadata, + metadata: payload.metadata.merge(), nonce: payload.nonce, diff --git a/src/promise.rs b/src/invocation/promise.rs similarity index 54% rename from src/promise.rs rename to src/invocation/promise.rs index 78b0f0f9..ac260249 100644 --- a/src/promise.rs +++ b/src/invocation/promise.rs @@ -11,35 +11,72 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { - Resolved(T), - Waiting(Selector), + Fulfilled(T), + Pending(Selector), } #[cfg(target_arch = "wasm32")] -pub enum JsStatus { - Resolved, - Waiting, +#[derive(Clone, Debug, PartialEq, Eq)] +#[wasm_bindgen] +pub enum UcanPromiseStatus { + Fulfilled, + Pending, } // FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? // FIXME consider wrapping in a trait #[cfg(target_arch = "wasm32")] -pub struct JsPromise { - status: JsStatus, - selector: Selector, +#[derive(Clone, Debug, PartialEq)] +#[wasm_bindgen] +pub struct UcanPromise { + status: UcanPromiseStatus, + selector: Option, value: Option, } -// TODO remove; I'd rather have liine 70 blanket than this -// #[cfg(target_arch = "wasm32")] -// impl> From for Promise { -// fn from(js: JsPromise) -> Self { -// match js.status { -// JsStatus::Resolved => Promise::Resolved(js.value.unwrap().into()), -// JsStatus::Waiting => Promise::Waiting(js.selector), -// } -// } -// } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(getter_with_clone)] +pub struct IncoherentPromise(pub UcanPromise); + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Promise { + type Error = IncoherentPromise; + + fn try_from(js: UcanPromise) -> Result { + match js.status { + UcanPromiseStatus::Fulfilled => { + if let Some(val) = &js.value { + return Ok(Promise::Fulfilled(val.clone())); + } + } + UcanPromiseStatus::Pending => { + if let Some(selector) = &js.selector { + return Ok(Promise::Pending(selector.clone())); + } + } + } + + Err(IncoherentPromise(js)) + } +} + +#[cfg(target_arch = "wasm32")] +impl> From> for UcanPromise { + fn from(promise: Promise) -> Self { + match promise { + Promise::Fulfilled(val) => UcanPromise { + status: UcanPromiseStatus::Fulfilled, + selector: None, + value: Some(val.into()), + }, + Promise::Pending(cid) => UcanPromise { + status: UcanPromiseStatus::Pending, + selector: Some(cid), + value: None, + }, + } + } +} impl Promise { pub fn map(self, f: F) -> Promise @@ -47,8 +84,8 @@ impl Promise { F: FnOnce(T) -> U, { match self { - Promise::Resolved(t) => Promise::Resolved(f(t)), - Promise::Waiting(selector) => Promise::Waiting(selector), + Promise::Fulfilled(t) => Promise::Fulfilled(f(t)), + Promise::Pending(selector) => Promise::Pending(selector), } } } @@ -56,24 +93,24 @@ impl Promise { impl> From> for Arguments { fn from(promise: Promise) -> Self { match promise { - Promise::Resolved(t) => t.into(), - Promise::Waiting(selector) => selector.into(), + Promise::Fulfilled(t) => t.into(), + Promise::Pending(selector) => selector.into(), } } } -impl Promise { - pub fn try_extract(self) -> Result { - match self { - Promise::Resolved(t) => Ok(t), - Promise::Waiting(promise) => Err(Promise::Waiting(promise)), - } +impl From for Promise { + fn from(value: T) -> Self { + Promise::Fulfilled(value) } } -impl From for Promise { - fn from(value: T) -> Self { - Promise::Resolved(value) +impl Promise { + pub fn try_resolve(self) -> Result { + match self { + Promise::Fulfilled(t) => Ok(t), + Promise::Pending(selector) => Err(selector), + } } } @@ -83,8 +120,8 @@ where { fn from(promise: Promise) -> Self { match promise { - Promise::Resolved(t) => t.into(), - Promise::Waiting(selector) => selector.into(), + Promise::Fulfilled(t) => t.into(), + Promise::Pending(selector) => selector.into(), } } } diff --git a/src/lib.rs b/src/lib.rs index 50e0d87d..4bed52dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ extern crate alloc; // } pub mod ability; +pub mod agent; pub mod capsule; pub mod delegation; pub mod did; @@ -73,8 +74,8 @@ pub mod metadata; pub mod new_wasm; pub mod nonce; pub mod number; -pub mod promise; pub mod proof; +pub mod reader; pub mod receipt; pub mod signature; pub mod task; diff --git a/src/nonce.rs b/src/nonce.rs index bf64784f..89d6453b 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -7,9 +7,6 @@ use std::fmt; #[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; -#[cfg(target_arch = "wasm32")] -use web_sys; - // FIXME pub struct Unit; // FIXME diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index d18e8289..5bf611ce 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -56,7 +56,7 @@ where .check_same(their_parents) .map_err(ParentfulError::InvalidParents), Parentful::This(this) => this - .check_parents(their_parents) + .check_parent(their_parents) .map_err(ParentfulError::InvalidProofChain), }, Parentful::This(that) => match self { @@ -77,7 +77,7 @@ where type Parents = Parentful; type ParentError = ParentfulError::Error>; - fn check_parents(&self, proof: &Parentful) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Parentful) -> Result<(), Self::ParentError> { match proof { Parentful::Any => Ok(()), Parentful::Parents(their_parents) => match self { @@ -86,7 +86,7 @@ where .check_same(their_parents) .map_err(ParentfulError::InvalidParents), Parentful::This(this) => this - .check_parents(their_parents) + .check_parent(their_parents) .map_err(ParentfulError::InvalidProofChain), }, Parentful::This(that) => match self { @@ -119,7 +119,7 @@ where Ok(()) => Outcome::Proven, Err(e) => Outcome::InvalidParents(e), }, - Parentful::This(this) => match this.check_parents(their_parents) { + Parentful::This(this) => match this.check_parent(their_parents) { Ok(()) => Outcome::Proven, Err(e) => Outcome::InvalidProofChain(e), }, diff --git a/src/proof/parents.rs b/src/proof/parents.rs index a3a92b54..e947c201 100644 --- a/src/proof/parents.rs +++ b/src/proof/parents.rs @@ -4,5 +4,5 @@ pub trait CheckParents: CheckSame { type Parents; type ParentError; - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 00000000..bd913fe9 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,119 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand}, + delegation::Delegatable, + invocation::Resolvable, + proof::{checkable::Checkable, same::CheckSame}, +}; +use serde::{Deserialize, Serialize}; + +// NOTE to self: this is helpful as a common container to lift various FFI into +#[derive(Clone, PartialEq, Debug)] +pub struct Reader { + pub env: Env, + pub val: T, +} + +impl> From> for Arguments { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +// NOTE plug this into Reader like: Reader> +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl> From> for Arguments { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl> From> for Arguments { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + +impl Reader { + pub fn map(self, func: F) -> Reader + where + F: FnOnce(T) -> U, + { + Reader { + env: self.env, + val: func(self.val), + } + } + + pub fn map_env(self, func: F) -> Reader + where + F: FnOnce(Env) -> NewEnv, + { + Reader { + env: func(self.env), + val: self.val, + } + } + + pub fn local(&self, modify_env: F, closure: G) -> U + where + T: Clone, + Env: Clone, + F: Fn(Env) -> Env, + G: Fn(Reader) -> U, + { + closure(Reader { + val: self.val.clone(), + env: modify_env(self.env.clone()), + }) + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Builder) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|b| b.0) + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Promised) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|p| p.0) + } +} + +impl> Delegatable for Reader { + type Builder = Reader>; +} + +impl> Resolvable for Reader { + type Promised = Reader>; +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +impl Checkable for Reader +where + Reader: CheckSame, +{ + type Hierarchy = Env::Hierarchy; +} diff --git a/src/task.rs b/src/task.rs index 8afc7916..50d07e82 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,7 +21,7 @@ pub struct Task { pub nonce: Option, pub cmd: String, - pub args: BTreeMap, + pub args: BTreeMap, // FIXME change to Arguments? } impl From for Id { @@ -65,29 +65,6 @@ impl From for Ipld { } } -// FIXME move to differet module -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct DefaultTrue(pub bool); - -impl From for bool { - fn from(def_true: DefaultTrue) -> Self { - def_true.0 - } -} - -impl From for DefaultTrue { - fn from(b: bool) -> Self { - DefaultTrue(b) - } -} - -impl Default for DefaultTrue { - fn default() -> Self { - DefaultTrue(true) - } -} - impl From for Cid { fn from(task: Task) -> Cid { let mut buffer = vec![]; diff --git a/src/time.rs b/src/time.rs index 291f0aa9..987c6d23 100644 --- a/src/time.rs +++ b/src/time.rs @@ -34,7 +34,7 @@ impl Serialize for Timestamp { { match self { Timestamp::Sending(js_time) => js_time.serialize(serializer), - Timestamp::Receiving(sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + Timestamp::Receiving(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), } } } @@ -106,6 +106,7 @@ impl<'de> Deserialize<'de> for JsTime { } // FIXME just lifting this from Elixir for now +#[derive(Debug, Clone, PartialEq, Eq)] pub struct OutOfRangeError { pub tried: SystemTime, } From 1edf634f9607f0b032107bdfac9e0ada335be04a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 5 Feb 2024 22:26:50 -0800 Subject: [PATCH 111/234] Many docs. Very wow. --- Cargo.toml | 1 + flake.nix | 2 +- src/ability/arguments.rs | 94 +++++++--- src/ability/command.rs | 47 +++++ src/ability/crud/any.rs | 54 +++++- src/ability/crud/read.rs | 18 +- src/ability/dynamic.rs | 12 +- src/ability/js.rs | 17 ++ src/ability/js/parentful.rs | 95 ++++++++-- src/ability/js/parentless.rs | 72 +++++-- src/ability/msg.rs | 2 + src/ability/msg/any.rs | 34 ++++ src/ability/msg/receive.rs | 32 ++++ src/ability/msg/send.rs | 104 ++++++++++- src/ability/ucan/proxy.rs | 12 +- src/agent.rs | 8 +- src/capsule.rs | 52 ++++++ src/delegation.rs | 2 +- src/delegation/delegatable.rs | 4 +- src/delegation/payload.rs | 33 ++-- src/delegation/store.rs | 2 +- src/delegation/traits.rs | 4 +- src/did.rs | 61 +++++- src/invocation/payload.rs | 29 +-- src/invocation/promise.rs | 8 +- src/invocation/resolvable.rs | 4 +- src/ipld.rs | 50 ++++- src/ipld/error.rs | 1 + src/metadata.rs | 343 ++++++++++++++++++++++------------ src/metadata/keyed.rs | 50 +++++ src/proof/same.rs | 46 ++++- src/reader.rs | 12 +- src/receipt/payload.rs | 46 ++--- src/receipt/store.rs | 2 +- src/task.rs | 2 +- 35 files changed, 1058 insertions(+), 297 deletions(-) create mode 100644 src/ipld/error.rs create mode 100644 src/metadata/keyed.rs diff --git a/Cargo.toml b/Cargo.toml index a1b96af6..cec1b278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] multihash = "0.18" +libipld = "0.16" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" diff --git a/flake.nix b/flake.nix index 9cbbda01..c0c36b5c 100644 --- a/flake.nix +++ b/flake.nix @@ -291,7 +291,7 @@ name = "test:docs"; help = "Run Cargo doctests"; category = "test"; - command = "${cargo} test --doc"; + command = "${cargo} test --doc --features=mermaid_docs"; } # Docs { diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 9a6e28cb..a8f81fe0 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -1,3 +1,5 @@ +//! Utilities for ability arguments + use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,45 +13,81 @@ use js_sys::{Array, Map, Object, Reflect}; #[cfg(target_arch = "wasm32")] use crate::ipld; -// FIXME yes I'm seriously considering laying this out in the wasm abi by han d -// #[cfg(not(target_arch = "wasm32"))] +/// Named arguments +/// +/// Being such a common pattern, but with so few trait implementations, +/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::arguments::Named; +/// # use url::Url; +/// # use libipld::ipld; +/// # +/// struct Execute { +/// program: Url, +/// args: Named, +/// } +/// +/// let ability = Execute { +/// program: Url::parse("file://host.name/path/to/exe").unwrap(), +/// args: Named::try_from(ipld!({ +/// "bold": true, +/// "message": "hello world", +/// })).unwrap() +/// }; +/// +/// assert_eq!(ability.args.get("bold"), Some(&ipld!(true))); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Arguments(pub BTreeMap); - -// #[cfg(target_arch = "wasm32")] -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[wasm_bindgen] -// pub struct Arguments(#[wasm_bindgen(skip)] pub BTreeMap); - -impl Arguments { - pub fn from_iter(iterable: impl IntoIterator) -> Self { - Arguments(iterable.into_iter().collect()) - } +pub struct Named(pub BTreeMap); +impl Named { + /// Get the value associated with a key + /// + /// An alias for [`BTreeMap::insert`]. pub fn get(&self, key: &str) -> Option<&Ipld> { self.0.get(key) } + /// Inserts a key-value pair + /// + /// An alias for [`BTreeMap::insert`]. pub fn insert(&mut self, key: String, value: Ipld) -> Option { self.0.insert(key, value) } + /// Gets an iterator over the entries, sorted by key. + /// + /// A wrapper around [`BTreeMap::iter`]. pub fn iter(&self) -> impl Iterator { self.0.iter() } +} - pub fn into_iter(self) -> impl Iterator { +impl Default for Named { + fn default() -> Self { + Named(BTreeMap::new()) + } +} + +impl IntoIterator for Named { + type Item = (String, Ipld); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl Default for Arguments { - fn default() -> Self { - Arguments(BTreeMap::new()) +impl FromIterator<(String, Ipld)> for Named { + fn from_iter>(iter: T) -> Self { + Named(iter.into_iter().collect()) } } -impl TryFrom for Arguments { +impl TryFrom for Named { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -57,15 +95,15 @@ impl TryFrom for Arguments { } } -impl From for Ipld { - fn from(arguments: Arguments) -> Self { +impl From for Ipld { + fn from(arguments: Named) -> Self { ipld_serde::to_ipld(arguments).unwrap() } } #[cfg(target_arch = "wasm32")] -impl From for Object { - fn from(arguments: Arguments) -> Self { +impl From for Object { + fn from(arguments: Named) -> Self { let obj = Object::new(); for (k, v) in arguments.0 { Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); @@ -77,7 +115,7 @@ impl From for Object { // NOTE saves a few cycles while calling by not cloning // the extra Object fields that we're not going to use #[cfg(target_arch = "wasm32")] -impl From<&Object> for Arguments { +impl From<&Object> for Named { fn from(obj: &Object) -> Self { let btree = Object::entries(obj) .iter() @@ -89,13 +127,13 @@ impl From<&Object> for Arguments { }) .collect::>(); - Arguments(btree) + Named(btree) } } #[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(arguments: Arguments) -> Self { +impl From for JsValue { + fn from(arguments: Named) -> Self { arguments .0 .iter() @@ -111,13 +149,13 @@ impl From for JsValue { } #[cfg(target_arch = "wasm32")] -impl TryFrom for Arguments { +impl TryFrom for Named { type Error = (); // FIXME fn try_from(js: JsValue) -> Result { match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Arguments(map)), + Ok(Ipld::Map(map)) => Ok(Named(map)), Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type } } diff --git a/src/ability/command.rs b/src/ability/command.rs index a158221e..756562cb 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -1,4 +1,51 @@ +//! Ability command utilities +//! +//! Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. +//! +//! ```js +//! // Here is a UCAN payload: +//! { +//! "iss": "did:example:123", +//! "aud": "did:example:456", +//! "cmd": "msg/send", // <--- This is the command +//! "args": { // ┐ +//! "to": "mailto:alice@example.com", // ├─ These are determined by the command +//! "message": "Hello, World!", // │ +//! } // ┘ +//! "exp": 1234567890 +//! } +//! ``` + +/// Attach a `cmd` field to a type +/// +/// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. +/// The `COMMAND` attaches this to types so that they can be serialized appropriately. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::command::Command; +/// # +/// struct Upload { +/// pub gb_quota: u64, +/// pub mime_types: Vec, +/// } +/// +/// impl Command for Upload { +/// const COMMAND: &'static str = "storage/upload"; +/// } +/// +/// assert_eq!(Upload::COMMAND, "storage/upload"); +/// ``` pub trait Command { + /// The value that will be placed in the UCAN's `cmd` field for the given type + /// + /// This is a `const` because it *must not*[^dynamic] depend on the runtime values of a type + /// in order to ensure type safety. + /// + /// [^dynamic]: Note that if the `dynamic` feature is enabled, the exception is + /// a special ability called [`Dynamic`][super::dynamic::Dynamic] (for e.g. JS FFI) + /// that uses a non-exported code path separate from the [`Command`] trait. const COMMAND: &'static str; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 15149fa3..6554c30f 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -5,9 +5,14 @@ use crate::{ same::{CheckSame, OptionalFieldErr}, }, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use url::Url; +#[cfg(target_arch = "wasm32")] +use crate::{ipld, proof::same::OptionalFieldReason}; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -28,11 +33,38 @@ impl NoParents for Builder {} impl CheckSame for Builder { type Error = OptionalFieldErr; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri) + self.uri + .check_same(&proof.uri) + .map_err(|err| OptionalFieldErr { + field: "uri".into(), + err, + }) + } +} + +impl TryFrom for Builder { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(builder: Builder) -> Self { + builder.into() } } +// FIXME +#[derive(Debug, Error)] +pub enum E { + #[error("Some error")] + SomeErrMsg(String), +} + #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); @@ -41,13 +73,25 @@ pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl CrudAny { + pub fn to_js(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_js(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudAny) + } + pub fn command(&self) -> String { Builder::COMMAND.to_string() } - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsValue> { - self.0 - .check_same(&proof.0) - .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(|_| { + OptionalFieldErr { + field: "uri".into(), + err: OptionalFieldReason::MissingField, + } + .into() + }) } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f07a2b61..3d8c409c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,17 +1,19 @@ use super::any; use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use thiserror::Error; use url::Url; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use crate::ipld; + // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -20,7 +22,7 @@ pub struct Read { pub uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, // FIXME rename Argumenst to get the traits? + pub args: Option, } impl Command for Read { @@ -63,7 +65,7 @@ impl CheckSame for Read { if let Some(args) = &self.args { if let Some(proof_args) = &proof.args { - for (k, v) in args { + for (k, v) in args.iter() { if proof_args.get(k) != Some(v) { return Err(E::SomeErrMsg("".into())); } @@ -91,6 +93,14 @@ pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl CrudRead { + pub fn to_jsvalue(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_jsvalue(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + } + pub fn command(&self) -> String { Read::COMMAND.to_string() } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 8d3acb06..10d4d2b7 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,6 +1,6 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support -use super::{arguments::Arguments, command::ToCommand}; +use super::{arguments, command::ToCommand}; use crate::{ipld, proof::same::CheckSame}; use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -14,7 +14,7 @@ use wasm_bindgen::prelude::*; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, - pub args: Arguments, + pub args: arguments::Named, } impl ToCommand for Dynamic { @@ -23,7 +23,7 @@ impl ToCommand for Dynamic { } } -impl From for Arguments { +impl From for arguments::Named { fn from(dynamic: Dynamic) -> Self { dynamic.args } @@ -68,7 +68,7 @@ impl TryFrom for Dynamic { Ok(Dynamic { cmd, - args: Arguments(btree), // FIXME kill clone + args: arguments::Named(btree), // FIXME kill clone }) } else { Err(JsValue::NULL) // FIXME @@ -87,10 +87,10 @@ impl CheckSame for Dynamic { self.args.0.iter().try_for_each(|(k, v)| { if let Some(proof_v) = proof.args.0.get(k) { if v != proof_v { - return Err("Arguments mismatch".into()); + return Err("arguments::Named mismatch".into()); } } else { - return Err("Arguments mismatch".into()); + return Err("arguments::Named mismatch".into()); } Ok(()) diff --git a/src/ability/js.rs b/src/ability/js.rs index 8422e6d3..e66d92f2 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,2 +1,19 @@ +//! Bindings for the JavaScript via Wasm +//! +//! Note that these are all [`wasm_bindgen`]-specific, +//! and are not recommended elsewhere due to limited +//! type safety, poorer performance, and restrictions +//! on the API placed by [`wasm_bindgen`]. +//! +//! The overall pattern is roughly: "JS code hands the +//! Rust code a config object with handlers at runtime". +//! The Rust takes those handlers, and dispatches them +//! as part of the normal flow. +//! +//! When compiled for Wasm, the other abilities in this +//! crate export JS bindings. This allows them to be +//! plugged into e.g. ability hierarchies from the JS +//! side as an extension mechanism. + pub mod parentful; pub mod parentless; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 72140b27..ee34effe 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,5 +1,7 @@ +//! JavaScript interafce for abilities that *do* require a parent hierarchy + use crate::{ - ability::{arguments::Arguments, command::ToCommand, dynamic}, + ability::{arguments, command::ToCommand, dynamic}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, reader::Reader, }; @@ -7,19 +9,16 @@ use js_sys::{Function, JsString, Map}; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, -// and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export WithParents to JS // FIXME rename -type WithParents = Reader; +type WithParents = Reader; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) -// FIXME rename ability? abilityconfig? leave as is? +/// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] -pub struct Config { +pub struct ParentfulConfig { pub command: String, pub is_nonce_meaningful: bool, @@ -30,35 +29,95 @@ pub struct Config { pub check_parent: BTreeMap, } +// NOTE if changed, please update this in the docs for `ParentfulArgs` below #[wasm_bindgen(typescript_custom_section)] -const CONFIG_ARGS: &str = r#" +const PARENTFUL_ARGS: &str = r#" interface ParentfulArgs { command: string, - is_nonce_meaningful: boolean, - validate_shape: Function, - check_same: Function, - check_parent: Map + isNonceMeaningful: boolean, + validateShape: Function, + checkSame: Function, + checkParent: Map } "#; #[wasm_bindgen] extern "C" { + /// Named constructor arguments for `ParentfulConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentfulArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// checkSame: Function, + /// checkParent: Map + /// } + /// ``` #[wasm_bindgen(typescript_type = "ParentfulArgs")] pub type ParentfulArgs; + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] pub fn command(this: &ParentfulArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentfulArgs) -> Function; + + /// Validate an instance against a candidate proof of the same shape + #[wasm_bindgen(js_name = checkSame)] pub fn check_same(this: &ParentfulArgs) -> Function; + + /// Validate an instance against a candidate proof containing a parent + #[wasm_bindgen(js_name = checkParent)] pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] -impl Config { - // FIXME object args as an option +impl ParentfulConfig { + /// Construct a new `ParentfulConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentfulConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// }, + /// checkSame: (proof, args) => { + /// if (proof.to === args.to && proof.message === args.message) { + /// return true; + /// } + /// return false; + /// }, + /// checkParent: new Map([ + /// ["msg/*", (proof, args) => { + /// proof.to === args.to && proof.message === args.message + /// }] + /// ]) + /// } + /// ); + /// ``` #[wasm_bindgen(constructor)] - pub fn new(js_obj: ParentfulArgs) -> Result { - Ok(Config { + pub fn new(js_obj: ParentfulArgs) -> Result { + Ok(ParentfulConfig { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), @@ -109,7 +168,7 @@ impl CheckSame for WithParents { .call2( &this, &self.val.clone().into(), - &Arguments::from(proof.clone()).into(), + &arguments::Named::from(proof.clone()).into(), ) .map(|_| ()) } @@ -131,7 +190,7 @@ impl CheckParents for WithParents { } } -impl ToCommand for Config { +impl ToCommand for ParentfulConfig { fn to_command(&self) -> String { self.command.clone() } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 1897fa22..21f7e586 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,19 +1,17 @@ +//! JavaScript interafce for abilities that *do not* require a parent hierarchy + use crate::{ - ability::{arguments::Arguments, command::ToCommand, dynamic}, + ability::{arguments, command::ToCommand, dynamic}, proof::{parentless::NoParents, same::CheckSame}, reader::Reader, }; use js_sys::Function; use wasm_bindgen::prelude::*; -// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, -// and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type WithoutParents = Reader; +type WithoutParents = Reader; -// FIXME rename ability? abilityconfig? leave as is? -// #[wasm_bindgen(getter_with_clone)] +/// The configuration object that expresses an ability (without parents) from JS #[derive(Debug, Clone, PartialEq)] #[wasm_bindgen] pub struct ParentlessConfig { @@ -25,30 +23,78 @@ pub struct ParentlessConfig { // FIXME represent promises (for Promised) and options (for builder) +// NOTE if changed, please update this in the docs for `ParentlessArgs` below #[wasm_bindgen(typescript_custom_section)] -const PARENTLESS_ARGS: &str = r#" +pub const PARENTLESS_ARGS: &str = r#" interface ParentlessArgs { command: string, - is_nonce_meaningful: boolean, - validate_shape: Function, - check_same: Function, + isNonceMeaningful: boolean, + validateShape: Function, + checkSame: Function, } "#; #[wasm_bindgen] extern "C" { + /// Named constructor arguments for `ParentlessConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentlessArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// checkSame: Function, + /// } + /// ``` #[wasm_bindgen(typescript_type = "ParentlessArgs")] pub type ParentlessArgs; + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] pub fn command(this: &ParentlessArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentlessArgs) -> Function; + + /// Validate an instance against a candidate proof of the same shape + #[wasm_bindgen(js_name = checkSame)] pub fn check_same(this: &ParentlessArgs) -> Function; } #[wasm_bindgen] impl ParentlessConfig { - // FIXME object args as an option + /// Construct a new `ParentlessConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentlessConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// }, + /// checkSame: (proof, args) => { + /// proof.to === args.to && proof.message === args.message + /// }, + /// } + /// ); + /// ``` #[wasm_bindgen(constructor)] pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { ParentlessConfig { @@ -80,7 +126,7 @@ impl CheckSame for WithoutParents { .call2( &this, &self.val.clone().into(), - &Arguments::from(proof.clone()).into(), + &arguments::Named::from(proof.clone()).into(), ) .map(|_| ()) } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 33792dd3..0f5bdd0f 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,3 +1,5 @@ +//! Message abilities + pub mod any; pub mod receive; pub mod send; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index ae603aa4..5e35b424 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,3 +1,5 @@ +//! "Any" message ability (superclass of all message abilities) + use crate::{ ability::command::Command, proof::{parentless::NoParents, same::CheckSame}, @@ -6,6 +8,38 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The [`Any`] message ability may not be invoked, but it is the superclass of +/// all other message abilities. For example, +/// the [`message::Receive`][super::receive::Receive] ability may be +/// proven by the [`Any`] ability in a delegation chain. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// rec("msg/receive") +/// end +/// end +/// +/// sendrun{{"invoke"}} +/// recrun{{"invoke"}} +/// +/// top --> any +/// any --> send -.-> sendrun +/// any --> rec -.-> recrun +/// +/// style any stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 5583331d..07c86aea 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,3 +1,5 @@ +//! The ability to receive messages + use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -8,9 +10,39 @@ use url::Url; use super::any as msg; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The ability to receive messages +/// +/// This ability is used to receive messages from other actors. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// rec("msg/receive") +/// end +/// end +/// +/// recrun{{"invoke"}} +/// +/// top --> any +/// any --> rec -.-> recrun +/// +/// style rec stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Receive { + /// An *optional* URL (e.g. email, DID, socket) to receive messages from. + /// This assumes that the `subject` has the authority to issue such a capability. pub from: Option, } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 194f6d7b..63c31ca6 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,5 +1,7 @@ +//! The ability to send messages + use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, delegation::Delegatable, invocation::{Promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -14,13 +16,105 @@ use super::any as msg; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { + /// The recipient of the message pub to: To, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. pub from: From, + + /// The main body of the message pub message: Message, } +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `msg/send` ability. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendpromise("msg::send::Promised") +/// sendrun("msg::send::Resolved") +/// +/// top --> any +/// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} +/// +/// style sendrun stroke:orange; +/// ``` pub type Resolved = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable variant of the `msg/send` ability. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendrun{{"invoke"}} +/// +/// top --> any +/// any --> send -.-> sendrun +/// +/// style send stroke:orange; +/// ``` pub type Builder = Generic, Option, Option>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The invoked variant of the `msg/send` ability +/// +/// This variant may be linked to other invoked abilities by [`Promise`][crate::invocation::Promise]s. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendpromise("msg::send::Promised") +/// sendrun("msg::send::Resolved") +/// +/// top --> any +/// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} +/// +/// style sendpromise stroke:orange; +/// ``` pub type Promised = Generic, Promise, Promise>; impl Delegatable for Resolved { @@ -31,7 +125,7 @@ impl Resolvable for Resolved { type Promised = Promised; } -impl From for Arguments { +impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); b.to.map(|to| btree.insert("to".into(), to.to_string().into())); @@ -40,13 +134,13 @@ impl From for Arguments { b.message .map(|msg| btree.insert("message".into(), msg.into())); - Arguments(btree) + arguments::Named(btree) } } -impl From for Arguments { +impl From for arguments::Named { fn from(promised: Promised) -> Self { - Arguments(BTreeMap::from_iter([ + arguments::Named(BTreeMap::from_iter([ ("to".into(), promised.to.map(String::from).into()), ("from".into(), promised.from.map(String::from).into()), ("message".into(), promised.message.into()), diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index 4851356a..da916a9c 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -1,5 +1,5 @@ use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, delegation::Delegatable, invocation::Promise, }; @@ -18,9 +18,9 @@ pub struct Generic { // FIXME should args just be a CID } -pub type Resolved = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; @@ -50,8 +50,8 @@ impl TryFrom for Resolved { } } -impl From for Arguments { - fn from(b: Builder) -> Arguments { +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { let mut args = b.args.unwrap_or_default(); args.insert("cmd".into(), Ipld::String(b.cmd)); args diff --git a/src/agent.rs b/src/agent.rs index aed278bd..809414a3 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -19,7 +19,7 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( + pub fn invoke( &self, delegation: Delegation, proof_chain: Vec>, // FIXME T must also accept Self and * @@ -34,7 +34,7 @@ impl Agent { todo!() } - pub fn revoke( + pub fn revoke( &self, delegation: Delegation, ) -> () @@ -44,14 +44,14 @@ impl Agent { todo!() } - pub fn receive_delegation( + pub fn receive_delegation( &self, delegation: Delegation, ) -> () { todo!() } - pub fn receive_invocation(&self, invocation: Invocation) -> () { + pub fn receive_invocation(&self, invocation: Invocation) -> () { todo!() } diff --git a/src/capsule.rs b/src/capsule.rs index 357bc5ec..2b4a9e25 100644 --- a/src/capsule.rs +++ b/src/capsule.rs @@ -1,3 +1,55 @@ +//! Capsule type utilities +//! +//! Capsule types are a pattern where you associate a string to type, +//! and use the tag as a key and the payload as a value in a map. +//! This helps disambiguate types when serializing and deserializing. +//! +//! Unlike a `type` field, the fact that it's on the outside of the payload +//! is often helpful in improving serializaion and deserialization performance. +//! It also avoids needing fields on nested structures where the inner types are known. +//! +//! Some simple examples include: +//! +//! ```javascript +//! {"u32": 42} +//! {"i64": 99} +//! {"coord": {"x": 1, "y": 2}} +//! { +//! "boundary": [ +//! {"x": 1, "y": 2}, // ─┐ +//! {"x": 3, "y": 4}, // ├─ Untagged coords inside "boundary" capsule +//! {"x": 5, "y": 6}, // │ +//! {"x": 7, "y": 8} // ─┘ +//! ] +//! } +//! ``` +//! +//! UCAN uses these in payload wrappers, such as [`Delegation`][crate::delegation::Delegation]. + +/// The primary capsule trait +/// +/// # Examples +/// +/// ```rust +/// # use ucan::capsule::Capsule; +/// # use std::collections::BTreeMap; +/// # +/// # #[derive(Debug, PartialEq)] +/// struct Coord { +/// x: i32, +/// y: i32 +/// } +/// +/// impl Capsule for Coord { +/// const TAG: &'static str = "coordinate"; +/// } +/// +/// let coord = Coord { x: 1, y: 2 }; +/// let capsuled = BTreeMap::from_iter([(Coord::TAG.to_string(), coord)]); +/// +/// assert_eq!(capsuled.get("coordinate"), Some(&Coord { x: 1, y: 2 })); +/// ```` pub trait Capsule { + /// The tag to use when constructing or matching on the capsule const TAG: &'static str; } diff --git a/src/delegation.rs b/src/delegation.rs index 01584561..3a8b3e3b 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -22,7 +22,7 @@ use crate::{metadata as meta, signature}; pub type Delegation = signature::Envelope>; // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 151d4fab..757976c8 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,5 +1,5 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 752c3fe7..8a3616e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,12 +1,13 @@ use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, capsule::Capsule, did::Did, invocation, invocation::Resolvable, metadata as meta, - metadata::{Mergable, Metadata}, + metadata::Metadata, + // metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -21,7 +22,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -36,11 +37,11 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where InternalSerializer: From>, Payload: Clone, @@ -54,7 +55,7 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::Entries> Deserialize<'de> +impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::MultiKeyed> Deserialize<'de> for Payload where Payload: TryFrom, @@ -73,7 +74,7 @@ where } } -impl TryFrom +impl TryFrom for Payload where Payload: TryFrom, @@ -86,14 +87,14 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::MultiKeyed> Payload { pub fn check( @@ -154,7 +155,7 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::Entries>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::MultiKeyed>( prev: &Acc<'a, T>, proof: &Payload, invoked_ipld: &Ipld, @@ -226,7 +227,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: Arguments, + arguments: arguments::Named, #[serde(rename = "cond")] conditions: Vec, @@ -241,11 +242,11 @@ struct InternalSerializer { expiration: Timestamp, } -impl, E: meta::Entries + Clone> +impl, E: meta::MultiKeyed> From> for InternalSerializer where BTreeMap: From, - Metadata: Mergable, + Ipld: From, { fn from(payload: Payload) -> Self { InternalSerializer { @@ -257,7 +258,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata.merge(), + metadata: payload.metadata.into(), nonce: payload.nonce, not_before: payload.not_before, @@ -275,7 +276,7 @@ impl TryFrom for InternalSerializer { } // FIXME -// impl, E: meta::Entries + Clone> TryFrom +// impl, E: meta::MultiKeyed + Clone> TryFrom // for Payload, C, E> // { // type Error = (); // FIXME @@ -310,7 +311,7 @@ impl TryFrom for InternalSerializer { // } // } // -// impl, E: meta::Entries + Clone, F> +// impl, E: meta::MultiKeyed + Clone, F> // From, C, E>> for InternalSerializer // where // Metadata: Mergable, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 438e8272..a75db20f 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, HashMap}; use web_time::SystemTime; // NOTE can already look up by CID in other traits -pub trait IndexedStore { +pub trait IndexedStore { type Error; fn get_by(query: Query) -> Result>, Self::Error>; diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs index b442734a..0d102d22 100644 --- a/src/delegation/traits.rs +++ b/src/delegation/traits.rs @@ -1,5 +1,5 @@ -use crate::{ability::arguments::Arguments, did::Did, nonce::Nonce, task, task::Task}; +use crate::{ability::arguments, did::Did, nonce::Nonce, task, task::Task}; pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into; } diff --git a/src/did.rs b/src/did.rs index 2f1aae78..66f8beaa 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,10 +2,25 @@ use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(into = "String", try_from = "String")] -pub struct Did(DID); +/// A [Decentralized Identifier (DID)][wiki] +/// +/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::did::Did; +/// # +/// let did = Did::try_from("did:example:123".to_string()).unwrap(); +/// assert_eq!(did.0.method(), "example"); +/// ``` +/// +/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub struct Did(pub DID); impl From for String { fn from(did: Did) -> Self { @@ -34,12 +49,50 @@ impl From for Ipld { } impl TryFrom for Did { - type Error = >::Error; // FIXME also include the "can't parse form ipld" case; seems like someythjing taht can be abstrcated out, too + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { - Ipld::String(string) => Did::try_from(string), - _ => todo!(), // Err(()), + Ipld::String(string) => Did::try_from(string).map_err(FromIpldError::StructuralError), + other => Err(FromIpldError::NotAnIpldString(other)), } } } + +/// Errors that can occur when converting to or from a [`Did`] +#[derive(Debug, Clone, PartialEq, Error)] +pub enum FromIpldError { + /// Strutural errors in the [`Did`] + StructuralError(did_url::Error), + + /// The [`Ipld`] was not a string + NotAnIpldString(Ipld), +} + +impl fmt::Display for FromIpldError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FromIpldError::StructuralError(e) => write!(f, "DID Error: {}", e), + FromIpldError::NotAnIpldString(_ipld) => write!(f, "Not an IPLD String"), // FIXME include the bad ipld, but needs a Display instance + } + } +} + +impl Serialize for FromIpldError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for FromIpldError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ipld = Ipld::deserialize(deserializer)?; + Ok(FromIpldError::NotAnIpldString(ipld)) + } +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ee9c0ed0..58c4e892 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,10 +1,11 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, capsule::Capsule, did::Did, metadata as meta, - metadata::{Mergable, Metadata}, + metadata::Metadata, + // metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; @@ -15,7 +16,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -32,13 +33,13 @@ pub struct Payload { } // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // #[serde(untagged)] -// pub enum MaybeResolved + Into> +// pub enum MaybeResolved + Into> // where // Payload: From, // Unresolved: From, @@ -48,11 +49,11 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, InternalSerializer: From>, @@ -66,7 +67,7 @@ where } } -impl<'de, T, E: meta::Entries> serde::Deserialize<'de> for Payload +impl<'de, T, E: meta::MultiKeyed> serde::Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -84,7 +85,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -96,7 +97,7 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -115,7 +116,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: Arguments, + arguments: arguments::Named, #[serde(rename = "prf")] proofs: Vec, @@ -195,7 +196,9 @@ impl TryFrom for InternalSerializer { // } // } -impl, E: meta::Entries> From> for InternalSerializer { +impl, E: meta::MultiKeyed> From> + for InternalSerializer +{ fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, @@ -207,7 +210,7 @@ impl, E: meta::Entries> From> for Int proofs: payload.proofs, cause: payload.cause, - metadata: payload.metadata.merge(), + metadata: payload.metadata.into(), nonce: payload.nonce, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index ac260249..f2dcfa0a 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,4 +1,4 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -90,7 +90,7 @@ impl Promise { } } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(promise: Promise) -> Self { match promise { Promise::Fulfilled(t) => t.into(), @@ -158,7 +158,7 @@ impl TryFrom for Selector { } } -impl From for Arguments { +impl From for arguments::Named { fn from(selector: Selector) -> Self { let mut btree = BTreeMap::new(); @@ -174,6 +174,6 @@ impl From for Arguments { } } - Arguments(btree) + arguments::Named(btree) } } diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 232f2538..d9570d98 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,5 +1,5 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; pub trait Resolvable: Sized { - type Promised: TryInto + From + Into; + type Promised: TryInto + From + Into; } diff --git a/src/ipld.rs b/src/ipld.rs index 1c781e26..04f40ad8 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,15 +1,50 @@ -use libipld_core::ipld::Ipld; +//! Helpers for working with [`Ipld`] pub mod cid; +use libipld_core::ipld::Ipld; +use std::fmt; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Uint8Array}; +/// A wrapper around [`Ipld`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// let ipld = Ipld::String("hello".into()); +/// let wrapped = ipld::Newtype(ipld.clone()); +/// // wrapped.some_trait_method(); +/// ``` +// / +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// # let ipld = Ipld::String("hello".into()); +/// # let wrapped = ipld::Newtype(ipld.clone()); +/// # +/// assert_eq!(wrapped.0, ipld); +/// ``` +#[derive(Debug, Clone, PartialEq)] pub struct Newtype(pub Ipld); +// FIXME +// impl fmt::Display for Newtype { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!(f, "{}", String::from(self.0)) +// } +// } + impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) @@ -22,6 +57,19 @@ impl From for Ipld { } } +#[cfg(target_arch = "wasm32")] +impl Newtype { + pub fn try_into_jsvalue>(js_val: JsValue) -> Result + where + JsError: From<>::Error>, + { + match Newtype::try_from(js_val) { + Err(_err) => Err(JsError::new("can't convert")), // FIXME + Ok(nt) => nt.0.try_into().map_err(JsError::from), + } + } +} + // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { diff --git a/src/ipld/error.rs b/src/ipld/error.rs new file mode 100644 index 00000000..4752292a --- /dev/null +++ b/src/ipld/error.rs @@ -0,0 +1 @@ +// pub enum diff --git a/src/metadata.rs b/src/metadata.rs index a16e2f97..1e222a13 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,65 +1,217 @@ -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, convert::Infallible}; - -// FIXME rename modeule to metadata - -pub trait Entry { - const KEY: &'static str; -} +//! Metadata (i.e. the UCAN `meta` field) -pub trait Entries: TryFrom + Into { - const KEYS: &'static [&'static str]; -} +mod keyed; +pub use keyed::{Keyed, MultiKeyed}; -pub trait Mergable { - fn merge(self) -> BTreeMap; - fn extract(merged: BTreeMap) -> Self; -} +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize, Serializer}; +use std::{collections::BTreeMap, convert::Infallible}; +/// An uninhabited type that signals no known metadata fields. +/// +/// This uses a similar technique as [`Infallible`]; +/// it is not possible to create a runtime value of this type, so merely stubs out code paths. +/// +/// ``` +/// # use ucan::metadata::{Metadata, Empty}; +/// # use std::collections::BTreeMap; +/// # use libipld_core::ipld::Ipld; +/// # +/// let kv: BTreeMap = BTreeMap::from_iter([ +/// ("foo".into(), Ipld::String("hello world".to_string())), +/// ("bar".into(), Ipld::Integer(42)), +/// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) +/// ]); +/// +/// let meta: Metadata = Metadata::try_from(kv.clone()).unwrap(); +/// +/// assert_eq!(meta.known().len(), 0); +/// assert_eq!(meta.unknown(), &kv); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Empty {} +/// A type alias for [`Metadata`] with no known fields. +pub type Unstructured = Metadata; + // NOTE no Serde +/// Parsed metadata fields. +/// +/// If you don't have any known fields, you can set `T ` to [`Empty`] (or [`Unstructured`]) #[derive(Debug, Clone, PartialEq)] pub struct Metadata { + /// Structured metadata, selected by matching `T` known: BTreeMap, + + /// Unstructured metadata unknown: BTreeMap, } -impl Mergable for Metadata { - fn merge(self) -> BTreeMap { - self.unknown +impl Metadata { + /// Constructor for [`Metadata`] + /// + /// This checks that no duplicate keys are present + /// + /// # Examples + /// + /// ```rust + /// # use ucan::metadata::{Metadata, Empty}; + /// # use std::collections::BTreeMap; + /// # use libipld_core::ipld::Ipld; + /// # + /// #[derive(Debug, Clone, PartialEq)] + /// pub enum MyFacts { + /// Timeout(u32), + /// Retry{max: u32, delay: u32}, + /// NotGoingToUseThisOne(String), + /// } + /// + /// let known: BTreeMap = BTreeMap::from_iter([ + /// ("timeout".into(), MyFacts::Timeout(1000)), + /// ("retry".into(), MyFacts::Retry{max: 5, delay: 100}) + /// ]); + /// + /// let unknown: BTreeMap = BTreeMap::from_iter([ + /// ("foo".into(), Ipld::String("hello world".to_string())), + /// ("bar".into(), Ipld::Integer(42)), + /// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) + /// ]); + /// + /// let meta = Metadata::new(known.clone(), unknown.clone()).unwrap(); + /// + /// assert_eq!(meta.known(), &known.clone()); + /// assert_eq!(meta.unknown(), &unknown.clone()); + /// + /// let collision: BTreeMap = BTreeMap::from_iter([ + /// ("timeout".into(), Ipld::String("not a timeout".to_string())), + /// ]); + /// + /// let meta = Metadata::new(known, collision); + /// + /// assert!(meta.is_err()); + /// ``` + pub fn new( + known: BTreeMap, + unknown: BTreeMap, + ) -> Result { + for k in known.keys() { + if unknown.contains_key(k) { + return Err(k.into()); + } + } + + Ok(Self { known, unknown }) } - // FIXME better error - fn extract(unknown: BTreeMap) -> Self { + /// Getter for the `known` field + pub fn known<'a>(&'a self) -> &'a BTreeMap { + &self.known + } + + /// Getter for the `unknown` field + pub fn unknown<'a>(&'a self) -> &'a BTreeMap { + &self.unknown + } + + /// Insert a value into the `known` field + /// + /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already + /// exists in the `unknown` field. + /// + /// It will return `Some(t: T)` if you insert a key that was already present + /// in the `known` field. + pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Option> { + if let Some(ipld) = self.unknown.get(&key) { + self.known.insert(key, value); + return Some(Entry::Unknown(ipld.clone())); + } + + self.known.insert(key, value).map(Entry::Known) + } + + /// Insert a value into the `unknown` field + /// + /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already + /// exists in the `unknown` field. + /// + /// It will return `Some(t: T)` if you insert a key that was already present + /// in the `known` field. + pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Option> + where + T: Clone, + { + if let Some(t) = self.known.get(&key) { + self.unknown.insert(key, value); + return Some(Entry::Known(t.clone())); + } + + self.unknown.insert(key, value).map(Entry::Unknown) + } + + /// Remove a field from either field + /// + /// This will return `Some(Entry::Unknown(ipld))` if there was a key in the `unknown` field. + /// + /// It will return `Some(t: T)` if there was a key in the `known` field. + pub fn remove_key<'a>(&'a mut self, key: &str) -> Option> { + if let Some(ipld) = self.unknown.remove(key) { + return Some(Entry::Unknown(ipld)); + } + + self.known.remove(key).map(Entry::Known) + } +} + +/// Tag values as belonging to the `known` or `unknown` field. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Entry { + /// The tag for a value in the `known` field + Known(T), + + /// The tag for a value in the `unknown` field + Unknown(Ipld), +} + +impl Default for Metadata { + fn default() -> Self { Metadata { known: BTreeMap::new(), - unknown, + unknown: BTreeMap::new(), } } } -impl Mergable for Metadata { - fn merge(self) -> BTreeMap { - let mut meta = self.unknown; +impl From> for BTreeMap { + fn from(meta: Metadata) -> Self { + meta.unknown + } +} - // FIXME kill the clone - for (k, v) in self.known { - meta.insert(k, v.into()); +impl> From> for BTreeMap { + // NOTE duplicate keys "shouldn't" be possible (because this roughly follows GDP) + // ...so we can just merge + fn from(meta: Metadata) -> Self { + let mut btree = meta.unknown; + for (k, v) in meta.known { + btree.insert(k, v.into()); } - - meta + btree } +} +impl From> for Metadata { // FIXME better error - fn extract(merged: BTreeMap) -> Self { + fn from(merged: BTreeMap) -> Self { let mut known = BTreeMap::new(); let mut unknown = BTreeMap::new(); for (k, v) in merged { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); + if T::KEYS.contains(&k.as_str()) { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } } else { unknown.insert(k, v); } @@ -69,15 +221,24 @@ impl Mergable for Metadata { } } +impl From> for Metadata { + fn from(btree: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown: btree, + } + } +} + impl TryFrom> for Ipld { type Error = Infallible; fn try_from(meta: Metadata) -> Result { - Ok(Ipld::Map(meta.merge())) + Ok(Ipld::Map(meta.unknown)) } } -impl TryFrom> for Ipld { +impl> TryFrom> for Ipld { type Error = String; // FIXME fn try_from(meta: Metadata) -> Result { @@ -95,17 +256,17 @@ impl TryFrom> for Ipld { } } -impl Serialize for Metadata { +impl Serialize for Metadata { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = Ipld::Map((*self).clone().merge()); // FIXME kill that clone with tons of refs? + let s = Ipld::Map((*self).clone().into()); serde::Serialize::serialize(&s, serializer) } } -impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { +impl<'de, T: MultiKeyed + Clone> Deserialize<'de> for Metadata { fn deserialize(d: D) -> Result where D: serde::Deserializer<'de>, @@ -114,7 +275,7 @@ impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { } } -impl TryFrom for Metadata { +impl TryFrom for Metadata { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -142,82 +303,28 @@ impl TryFrom for Metadata { } } -impl Metadata { - pub fn new( - known: BTreeMap, - unknown: BTreeMap, - ) -> Result { - for k in known.keys() { - if unknown.contains_key(k) { - return Err(k.into()); - } - } - - Ok(Self { known, unknown }) - } - - pub fn known<'a>(&'a self) -> &'a BTreeMap { - &self.known - } - - pub fn unknown<'a>(&'a self) -> &'a BTreeMap { - &self.unknown - } - - // FIXME types - pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Result<(), Option> { - if let Some(_) = self.unknown.get(&key) { - return Err(None); - } - - match self.known.insert(key, value) { - Some(v) => Err(Some(v)), - _ => Ok(()), - } - } - - pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Result<(), Option> { - if let Some(_) = self.known.get(&key) { - return Err(None); - } - - match self.unknown.insert(key, value) { - Some(v) => Err(Some(v)), - _ => Ok(()), - } - } -} - -impl Default for Metadata { - fn default() -> Self { - Metadata { - known: BTreeMap::new(), - unknown: BTreeMap::new(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct IpvmConfig { - pub max_retries: u32, - pub workflow_fuel: u32, -} - -impl Entry for IpvmConfig { - const KEY: &'static str = "ipvm/config"; -} - -impl From for Ipld { - fn from(config: IpvmConfig) -> Self { - config.into() - } -} - -impl TryFrom for IpvmConfig { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +// // FIXME Just as an example, plz delete +// #[derive(Debug, Clone, Serialize, Deserialize)] +// #[serde(deny_unknown_fields)] +// pub struct IpvmConfig { +// pub max_retries: u32, +// pub workflow_fuel: u32, +// } +// +// impl Keyed for IpvmConfig { +// const KEY: &'static str = "ipvm/config"; +// } +// +// impl From for Ipld { +// fn from(config: IpvmConfig) -> Self { +// config.into() +// } +// } +// +// impl TryFrom for IpvmConfig { +// type Error = SerdeError; +// +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld) +// } +// } diff --git a/src/metadata/keyed.rs b/src/metadata/keyed.rs new file mode 100644 index 00000000..174023f1 --- /dev/null +++ b/src/metadata/keyed.rs @@ -0,0 +1,50 @@ +//! Keys for metadata fields (for parsing and guarding entries) + +use libipld_core::ipld::Ipld; + +/// A parser trait for types that can be used as metadata fields. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::metadata::Keyed; +/// # use std::collections::BTreeMap; +/// # use libipld::{ipld, ipld::Ipld}; +/// # +/// pub struct MyKeyed { +/// pub foo: String, +/// pub bar: u32, +/// } +/// +/// impl Keyed for MyKeyed { +/// const KEY: &'static str = "my/entry"; +/// } +/// assert_eq!(MyKeyed::KEY, "my/entry"); +/// +/// let kv: BTreeMap = BTreeMap::from_iter([ +/// (MyKeyed::KEY.into(), ipld!({"foo": "hello world", "bar": 42})) +/// ]); +/// +/// assert_eq!(kv.get(MyKeyed::KEY), Some(&ipld!({"foo": "hello world", "bar": 42}))); +/// ``` +pub trait Keyed { + /// The (string) key for this entry + /// + /// These should be unique per `Keyed` type to avoid parser collisions. + /// Even if a duplicate key is used, the shape of the contained data can + /// also be used to disambiguate. + const KEY: &'static str; +} + +// FIXME keyed? +/// A parser trait for one-or-more unioned types that can be used as metadata fields. +/// +/// [`MultiKeyed`] may be composed of a single entry, or the union of sevveral e.g. via enum. +pub trait MultiKeyed: TryFrom + Into { + /// The (string) keys for an enum merging multiple [`Keyed`]s + const KEYS: &'static [&'static str]; +} + +impl + Into> MultiKeyed for T { + const KEYS: &'static [&'static str] = &[T::KEY]; +} diff --git a/src/proof/same.rs b/src/proof/same.rs index b05f8cb6..22f2e060 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,5 +1,10 @@ use crate::did::Did; +use core::fmt; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; pub trait CheckSame { type Error; @@ -11,18 +16,41 @@ pub trait CheckSame { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unequal; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct OpionalFieldErr { - pub field: T, // Enum of fields - pub err: OptionalFieldErr, +// FIXME move under error.rs +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))] +pub struct OptionalFieldErr { + pub field: String, + pub err: OptionalFieldReason, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum OptionalFieldErr { +impl fmt::Display for OptionalFieldErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Field {} is {}", self.field, self.err) + } +} + +// FIXME at minimum the name is confusing +#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum OptionalFieldReason { MissingField, UnequalValue, } +impl fmt::Display for OptionalFieldReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + OptionalFieldReason::MissingField => "missing", + OptionalFieldReason::UnequalValue => "unequal", + } + ) + } +} + impl CheckSame for Did { type Error = Unequal; @@ -36,18 +64,18 @@ impl CheckSame for Did { } impl CheckSame for Option { - type Error = OptionalFieldErr; + type Error = OptionalFieldReason; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match proof { None => Ok(()), Some(proof_) => match self { - None => Err(OptionalFieldErr::MissingField), + None => Err(OptionalFieldReason::MissingField), Some(self_) => { if self_.eq(proof_) { Ok(()) } else { - Err(OptionalFieldErr::UnequalValue) + Err(OptionalFieldReason::UnequalValue) } } }, diff --git a/src/reader.rs b/src/reader.rs index bd913fe9..3e1de5fc 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,5 +1,5 @@ use crate::{ - ability::{arguments::Arguments, command::ToCommand}, + ability::{arguments, command::ToCommand}, delegation::Delegatable, invocation::Resolvable, proof::{checkable::Checkable, same::CheckSame}, @@ -13,7 +13,7 @@ pub struct Reader { pub val: T, } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(reader: Reader) -> Self { reader.val.into() } @@ -26,13 +26,13 @@ pub struct Builder(pub T); #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Promised(pub T); -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() } } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(promised: Promised) -> Self { promised.0.into() } @@ -97,11 +97,11 @@ impl From>> for Reader { } } -impl> Delegatable for Reader { +impl> Delegatable for Reader { type Builder = Reader>; } -impl> Resolvable for Reader { +impl> Resolvable for Reader { type Promised = Reader>; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 0600528d..83662cc2 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,25 +1,20 @@ use super::responds::Responds; use crate::{ - ability::arguments::Arguments, - capsule::Capsule, - did::Did, - metadata as meta, - metadata::{Mergable, Metadata}, - nonce::Nonce, - time::Timestamp, + ability::arguments, capsule::Capsule, did::Did, metadata as meta, metadata::Metadata, + nonce::Nonce, time::Timestamp, }; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, - pub out: Result, + pub out: Result, pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, @@ -29,20 +24,21 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, T::Success: Serialize + DeserializeOwned, + Ipld: From, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); // FIXME kill that clone with tons of refs? + let s = InternalSerializer::from((*self).clone()); // FIXME kill that clone with tons of refs? serde::Serialize::serialize(&s, serializer) } } @@ -50,7 +46,7 @@ where impl< 'de, T: Responds + Deserialize<'de>, - E: meta::Entries + Clone + DeserializeOwned + Serialize, + E: DeserializeOwned + Serialize + meta::MultiKeyed + TryFrom, > Deserialize<'de> for Payload where as TryFrom>>::Error: Debug, @@ -67,20 +63,20 @@ where } } -impl TryFrom +impl + meta::MultiKeyed> TryFrom for Payload where T::Success: Serialize + DeserializeOwned, { - type Error = (); // FIXME serde error + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + let s: InternalSerializer = ipld_serde::from_ipld(ipld)?; Ok(s.into()) } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -96,7 +92,7 @@ where issuer: Did, ran: Cid, - out: Result, + out: Result, next: Vec, // FIXME rename here or in spec? #[serde(rename = "prf")] @@ -109,11 +105,9 @@ where issued_at: Option, } -impl From> - for Payload +impl> From> for Payload where T::Success: Serialize + DeserializeOwned, - Metadata: Mergable, { fn from(s: InternalSerializer) -> Self { Payload { @@ -122,17 +116,17 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: Metadata::extract(s.metadata), + metadata: s.metadata.into(), nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where + Ipld: From, T::Success: Serialize + DeserializeOwned, - Metadata: Mergable, { fn from(s: Payload) -> Self { InternalSerializer { @@ -141,7 +135,7 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata.merge(), + metadata: s.metadata.into(), nonce: s.nonce, issued_at: s.issued_at, } diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 76d5ebc9..35c7d5ed 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -2,7 +2,7 @@ use super::{Receipt, Responds}; use crate::{metadata, task}; use libipld_core::ipld::Ipld; -pub trait Store { +pub trait Store { type Error; fn get(id: task::Id) -> Result, Self::Error> diff --git a/src/task.rs b/src/task.rs index 50d07e82..00411b7b 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,7 +21,7 @@ pub struct Task { pub nonce: Option, pub cmd: String, - pub args: BTreeMap, // FIXME change to Arguments? + pub args: BTreeMap, // FIXME change to Named? } impl From for Id { From 395cbc275acd0167de1c49748cc0e20be8a60582 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 5 Feb 2024 23:35:20 -0800 Subject: [PATCH 112/234] more doctests --- src/ability/dynamic.rs | 23 ++++++++- src/reader.rs | 103 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 10d4d2b7..f6998fa3 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -9,11 +9,30 @@ use std::{collections::BTreeMap, fmt::Debug}; use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! -// This is meant to be embedded inside of structs that have e.g. FFI bindings to -// a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc + +/// A "dynamic" ability with the bare minimum of statics +/// +///

+/// +/// Dynamic none of the typical ability traits directly. Rather, it must be wrapped +/// in [`Reader`][crate::reader::Reader], which wires up dynamic dispatch for the +/// relevant traits using a configuration struct. #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { + /// The `cmd` field (hooks into a dynamic version of [`Command`][crate::ability::command::Command]) pub cmd: String, + + /// Unstructured, named arguments + /// + /// The only requirement is that the keys are strings and the values are [`Ipld`] pub args: arguments::Named, } diff --git a/src/reader.rs b/src/reader.rs index 3e1de5fc..d87f0e41 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -7,9 +7,66 @@ use crate::{ use serde::{Deserialize, Serialize}; // NOTE to self: this is helpful as a common container to lift various FFI into + +/// A struct that attaches an ambient environment to a value +/// +/// This is helpful for dependency injection and/or passing around values that +/// would otherwise need to be threaded through next to the value. +/// +/// This is loosely based on the [`Reader`][SO] from Haskell, but is not implemented +/// monadically. The fully "ambient" features of the `Reader` monad are not present here. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::reader::Reader; +/// # use std::string::ToString; +/// # +/// struct Config { +/// name: String, +/// formatter: Box String>, +/// trimmer: Box String>, +/// } +/// +/// fn run(r: Reader) -> String { +/// let formatted = (r.env.formatter)(r.val.to_string()); +/// (r.env.trimmer)(formatted) +/// } +/// +/// let cfg1 = Config { +/// name: "cfg1".into(), +/// formatter: Box::new(|s| s.to_uppercase()), +/// trimmer: Box::new(|mut s| s.trim().into()) +/// }; +/// +/// let cfg2 = Config { +/// name: "cfg2".into(), +/// formatter: Box::new(|s| s.to_lowercase()), +/// trimmer: Box::new(|mut s| s.split_off(5).into()) +/// }; +/// +/// +/// let reader1 = Reader { +/// env: cfg1, +/// val: " value", +/// }; +/// +/// let reader2 = Reader { +/// env: cfg2, +/// val: " value", +/// }; +/// +/// assert_eq!(run(reader1), "VALUE"); +/// assert_eq!(run(reader2), "e"); +/// ``` +/// +/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad #[derive(Clone, PartialEq, Debug)] pub struct Reader { + /// The environment (or configuration) being passed with the value pub env: Env, + + /// The raw value pub val: T, } @@ -39,6 +96,7 @@ impl> From> for arguments::Named { } impl Reader { + /// Map a function over the `val` of the [`Reader`] pub fn map(self, func: F) -> Reader where F: FnOnce(T) -> U, @@ -49,6 +107,7 @@ impl Reader { } } + /// Modify the `env` field of the [`Reader`] pub fn map_env(self, func: F) -> Reader where F: FnOnce(Env) -> NewEnv, @@ -59,6 +118,50 @@ impl Reader { } } + /// Temporarily modify the environment + /// + /// # Examples + /// + /// ```rust + /// # use ucan::reader::Reader; + /// # use std::string::ToString; + /// # + /// # #[derive(Clone)] + /// struct Config<'a> { + /// name: String, + /// formatter: &'a dyn Fn(String) -> String, + /// trimmer: &'a dyn Fn(String) -> String, + /// } + /// + /// fn run(r: Reader) -> String { + /// let formatted = (r.env.formatter)(r.val.to_string()); + /// (r.env.trimmer)(formatted) + /// } + /// + /// let cfg = Config { + /// name: "cfg1".into(), + /// formatter: &|s| s.to_uppercase(), + /// trimmer: &|mut s| s.trim().into() + /// }; + /// + /// let my_reader = Reader { + /// env: cfg, + /// val: " value", + /// }; + /// + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// + /// // Modify the env locally + /// let observed = my_reader.clone().local(|mut env| { + /// // Modifying env + /// env.trimmer = &|mut s: String| s.split_off(5).into(); + /// env + /// }, |r| run(r)); // Running + /// assert_eq!(observed, "E"); + /// + /// // Back to normal (the above was in fact "local") + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// ``` pub fn local(&self, modify_env: F, closure: G) -> U where T: Clone, From 448fdfb01370d7dbc6b2506c87b18de84de8cd60 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 00:05:10 -0800 Subject: [PATCH 113/234] Save for the day; working on JS nonce gen --- src/ability/js/parentful.rs | 2 +- src/ability/js/parentless.rs | 2 +- src/ability/msg/any.rs | 7 ++++--- src/ipld/cid.rs | 8 +++++++- src/nonce.rs | 23 ++++++++++++++--------- src/number.rs | 9 +++++++++ src/proof.rs | 2 ++ src/proof/checkable.rs | 8 ++++++++ src/proof/internal.rs | 3 ++- src/reader.rs | 2 ++ 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index ee34effe..cfb7b61a 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,4 +1,4 @@ -//! JavaScript interafce for abilities that *do* require a parent hierarchy +//! JavaScript interface for abilities that *do* require a parent hierarchy use crate::{ ability::{arguments, command::ToCommand, dynamic}, diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 21f7e586..53dac266 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,4 +1,4 @@ -//! JavaScript interafce for abilities that *do not* require a parent hierarchy +//! JavaScript interface for abilities that *do not* require a parent hierarchy use crate::{ ability::{arguments, command::ToCommand, dynamic}, diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 5e35b424..4d7ece1b 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -10,9 +10,10 @@ use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The [`Any`] message ability may not be invoked, but it is the superclass of -/// all other message abilities. For example, -/// the [`message::Receive`][super::receive::Receive] ability may be -/// proven by the [`Any`] ability in a delegation chain. +/// all other message abilities. +/// +/// For example, the [`message::Receive`][super::receive::Receive] ability may +/// be proven by the [`Any`] ability in a delegation chain. /// /// # Delegation Hierarchy /// diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index 9a812402..b8765f86 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,3 +1,5 @@ +//! Utilities for [`Cid`]s + use libipld_core::cid::Cid; #[cfg(target_arch = "wasm32")] @@ -6,7 +8,9 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_derive::TryFromJsValue; -// FIXME better name +/// A newtype wrapper around a [`Cid`] +/// +/// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] @@ -28,10 +32,12 @@ extern "C" { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl Newtype { + /// Parse a [`Newtype`] from a string pub fn from_string(cid_string: String) -> Result { Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) } + /// Convert the [`Cid`] to a string pub fn to_string(&self) -> String { self.cid.to_string() } diff --git a/src/nonce.rs b/src/nonce.rs index 89d6453b..39d29bda 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -4,11 +4,12 @@ use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; use serde::{Deserialize, Serialize}; use std::fmt; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + #[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; -// FIXME -pub struct Unit; // FIXME pub struct Error(T); @@ -23,16 +24,20 @@ pub enum Nonce { Custom(Vec), } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] impl Nonce { - // pub fn new() -> Self { - // Self::generate_96() + // FIXME this is probably _tooo_ different from the non-wasm one + // Perhaps worth one to ingest nonces via JSValues. + // #[cfg(target_arch = "wasm32")] + // pub fn generate_wasm_96(info: &mut [u8], crypto: web_sys::Crypto) -> Result { + // let buf = &mut []; + // web_sys::Crypto::get_random_values_with_u8_array(&crypto, buf) // `Result` // } +} - // #[cfg(target_arch = "wasm32")] - // pub fn gen_wasm_96() -> Self { - // web_sys::Crypto::get_random_values_with_u8_array() - // } - +#[cfg(not(target_arch = "wasm32"))] +impl Nonce { /// Default generator, outputting an [`xid`] nonce, /// which is a 96-bit (12-byte) nonce. #[cfg(not(target_arch = "wasm32"))] diff --git a/src/number.rs b/src/number.rs index 20f3042f..f29978d4 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,10 +1,19 @@ +//! Helpers for working with [`Ipld`] numerics + use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// The union of [`Ipld`] numeric types +/// +/// This is helpful when working with JavaScript, or with +/// values that may be given as either an integer or a float. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { + /// Designate a floating point number Float(f64), + + /// Designate an integer Integer(i128), } diff --git a/src/proof.rs b/src/proof.rs index 75559c3b..122ca024 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,3 +1,5 @@ +//! Proof chains, checking, and utilities + pub mod checkable; pub mod parentful; pub mod parentless; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index f8a7cd62..9b04cf1f 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,13 @@ +//! Define the hierarchy of an ability (or mark as not having one) + use super::{internal::Checker, prove::Prove, same::CheckSame}; +/// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { + /// The type of hierarchy this ability has + /// + /// The only options are [`Parentful`][super::parentful::Parentful] + /// and [`Parentless`][super::parentless::Parentless], + /// (which are the only instances of the unexported `Checker`) type Hierarchy: Checker + CheckSame + Prove; } diff --git a/src/proof/internal.rs b/src/proof/internal.rs index b7adf7fa..42df1327 100644 --- a/src/proof/internal.rs +++ b/src/proof/internal.rs @@ -1 +1,2 @@ -pub trait Checker {} +// NOTE: Must not get exported +pub(crate) trait Checker {} diff --git a/src/reader.rs b/src/reader.rs index d87f0e41..60032406 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,3 +1,5 @@ +//! Configure & attach an ambient environment to a value + use crate::{ ability::{arguments, command::ToCommand}, delegation::Delegatable, From e81f5a52ff7a6f74530a44e819408e9f9c3d2438 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 13:35:54 -0800 Subject: [PATCH 114/234] Finished another round of fighting with feature flags --- Cargo.toml | 8 +- flake.nix | 12 +++ src/ability/js/parentful.rs | 5 ++ src/nonce.rs | 169 ++++++++++++++++++++++++++---------- tests/conformance.rs | 7 +- 5 files changed, 146 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cec1b278..34115ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,14 @@ ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } enum-as-inner = "0.6" erased-serde = "0.3.31" +getrandom = { version = "0.2", features = ["js", "rdrand"] } jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" -multibase = "0.9.1" +multibase = "0.9" +multihash = { version = "0.18", features = ["sha2"] } p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } @@ -66,19 +68,15 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = { version = "2.5", features = ["serde"] } -uuid = "1.7" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # `linkme` relies on linker features that aren't available in wasm32 linkme = "0.3.15" -uuid = { version = "1.7", features = ["v4"] } -xid = "1.0" # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = { version = "0.1" } -getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" diff --git a/flake.nix b/flake.nix index c0c36b5c..3415cbca 100644 --- a/flake.nix +++ b/flake.nix @@ -256,6 +256,18 @@ category = "watch"; command = "${cargo} watch --clear --exec test"; } + { + name = "watch:test:wasm"; + help = "Run all tests on save"; + category = "watch"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + } + { + name = "watch:test:docs:wasm"; + help = "Run all tests on save"; + category = "watch"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + } # Test { name = "test:all"; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index cfb7b61a..f7523382 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -20,9 +20,14 @@ type WithParents = Reader; #[wasm_bindgen(getter_with_clone)] pub struct ParentfulConfig { pub command: String, + + #[wasm_bindgen(js_name = isNonceMeaningful)] pub is_nonce_meaningful: bool, + #[wasm_bindgen(js_name = validateShape)] pub validate_shape: Function, + + #[wasm_bindgen(js_name = checkSame)] pub check_same: Function, #[wasm_bindgen(skip)] diff --git a/src/nonce.rs b/src/nonce.rs index 39d29bda..b9c775e5 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -1,64 +1,97 @@ -// use crate::{Error, Unit}; +//! Nonce utilities + use enum_as_inner::EnumAsInner; -use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; +use getrandom::getrandom; +use libipld_core::{ + ipld::Ipld, + multibase::Base::Base32HexLower, + multihash::{Hasher, Sha2_256}, +}; use serde::{Deserialize, Serialize}; use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(not(target_arch = "wasm32"))] -use uuid::Uuid; - -// FIXME -pub struct Error(T); - -/// Enumeration over allowed `nonce` types. +/// Known [`Nonce`] types #[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { - /// 96-bit, 12-byte nonce, e.g. [`xid`]. - Nonce96([u8; 12]), - /// 128-bit, 16-byte nonce. - Nonce128([u8; 16]), - /// No Nonce attributed. + /// 96-bit, 12-byte nonce + Nonce12([u8; 12]), + + /// 128-bit, 16-byte nonce + Nonce16([u8; 16]), + + /// Dynamic sized nonce Custom(Vec), } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl Nonce { - // FIXME this is probably _tooo_ different from the non-wasm one - // Perhaps worth one to ingest nonces via JSValues. - // #[cfg(target_arch = "wasm32")] - // pub fn generate_wasm_96(info: &mut [u8], crypto: web_sys::Crypto) -> Result { - // let buf = &mut []; - // web_sys::Crypto::get_random_values_with_u8_array(&crypto, buf) // `Result` - // } +impl From<[u8; 12]> for Nonce { + fn from(s: [u8; 12]) -> Self { + Nonce::Nonce12(s) + } +} + +impl From<[u8; 16]> for Nonce { + fn from(s: [u8; 16]) -> Self { + Nonce::Nonce16(s) + } +} + +impl From> for Nonce { + fn from(nonce: Vec) -> Self { + match nonce.len() { + 12 => Nonce::Nonce12( + nonce + .try_into() + .expect("12 bytes because we checked in the match"), + ), + 16 => Nonce::Nonce16( + nonce + .try_into() + .expect("16 bytes because we checked in the match"), + ), + _ => Nonce::Custom(nonce), + } + } } -#[cfg(not(target_arch = "wasm32"))] impl Nonce { - /// Default generator, outputting an [`xid`] nonce, - /// which is a 96-bit (12-byte) nonce. - #[cfg(not(target_arch = "wasm32"))] - pub fn generate_96() -> Self { - Nonce::Nonce96(*xid::new().as_bytes()) + // NOTE seed = domain-separator + pub fn generate_12(seed: &mut Vec) -> Nonce { + seed.append(&mut [0].repeat(12)); + + let buf = seed.as_mut_slice(); + getrandom(buf).expect("irrecoverable getrandom failure"); + + let mut hasher = Sha2_256::default(); + hasher.update(buf); + + let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + Nonce::Nonce12(bytes) } - /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. - #[cfg(not(target_arch = "wasm32"))] - pub fn generate_128() -> Self { - Nonce::Nonce128(*Uuid::new_v4().as_bytes()) + pub fn generate_16(seed: &mut Vec) -> Nonce { + seed.append(&mut [0].repeat(16)); + + let buf = seed.as_mut_slice(); + getrandom(buf).expect("irrecoverable getrandom failure"); + + let mut hasher = Sha2_256::default(); + hasher.update(buf); + + let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + Nonce::Nonce12(bytes) } } impl fmt::Display for Nonce { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Nonce::Nonce96(nonce) => { + Nonce::Nonce12(nonce) => { write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) } - Nonce::Nonce128(nonce) => { + Nonce::Nonce16(nonce) => { write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) } Nonce::Custom(nonce) => { @@ -71,8 +104,8 @@ impl fmt::Display for Nonce { impl From for Ipld { fn from(nonce: Nonce) -> Self { match nonce { - Nonce::Nonce96(nonce) => Ipld::Bytes(nonce.to_vec()), - Nonce::Nonce128(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce12(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce16(nonce) => Ipld::Bytes(nonce.to_vec()), Nonce::Custom(nonce) => Ipld::Bytes(nonce), } } @@ -84,11 +117,11 @@ impl TryFrom for Nonce { fn try_from(ipld: Ipld) -> Result { if let Ipld::Bytes(v) = ipld { match v.len() { - 12 => Ok(Nonce::Nonce96( + 12 => Ok(Nonce::Nonce12( v.try_into() .expect("12 bytes because we checked in the match"), )), - 16 => Ok(Nonce::Nonce128( + 16 => Ok(Nonce::Nonce16( v.try_into() .expect("16 bytes because we checked in the match"), )), @@ -108,16 +141,60 @@ impl TryFrom<&Ipld> for Nonce { } } +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct JsNonce(#[wasm_bindgen(skip)] pub Nonce); + +#[cfg(target_arch = "wasm32")] +impl From for Nonce { + fn from(newtype: JsNonce) -> Self { + newtype.0 + } +} + +#[cfg(target_arch = "wasm32")] +impl From for JsNonce { + fn from(nonce: Nonce) -> Self { + JsNonce(nonce) + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl JsNonce { + pub fn generate_12(mut seed: Vec) -> JsNonce { + Nonce::generate_12(&mut seed).into() + } + + pub fn generate_16(mut seed: Vec) -> JsNonce { + Nonce::generate_16(&mut seed).into() + } + + pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { + Nonce::from(arr.to_vec()).into() + } + + pub fn to_uint8_array(&self) -> Box<[u8]> { + match &self.0 { + Nonce::Nonce12(nonce) => nonce.to_vec().into_boxed_slice(), + Nonce::Nonce16(nonce) => nonce.to_vec().into_boxed_slice(), + Nonce::Custom(nonce) => nonce.clone().into_boxed_slice(), + } + } +} + #[cfg(test)] mod test { use super::*; + // FIXME prop test with lots of inputs #[test] fn ipld_roundtrip_12() { - let gen = Nonce::generate(); + let gen = Nonce::generate_12(&mut vec![]); let ipld = Ipld::from(gen.clone()); - let inner = if let Nonce::Nonce96(nonce) = gen { + let inner = if let Nonce::Nonce12(nonce) = gen { Ipld::Bytes(nonce.to_vec()) } else { panic!("No conversion!") @@ -127,12 +204,13 @@ mod test { assert_eq!(gen, ipld.try_into().unwrap()); } + // FIXME prop test with lots of inputs #[test] fn ipld_roundtrip_16() { - let gen = Nonce::generate_128(); + let gen = Nonce::generate_16(&mut vec![]); let ipld = Ipld::from(gen.clone()); - let inner = if let Nonce::Nonce128(nonce) = gen { + let inner = if let Nonce::Nonce16(nonce) = gen { Ipld::Bytes(nonce.to_vec()) } else { panic!("No conversion!") @@ -142,9 +220,10 @@ mod test { assert_eq!(gen, ipld.try_into().unwrap()); } + // FIXME prop test with lots of inputs #[test] fn ser_de() { - let gen = Nonce::generate_128(); + let gen = Nonce::generate_16(&mut vec![]); let ser = serde_json::to_string(&gen).unwrap(); let de = serde_json::from_str(&ser).unwrap(); diff --git a/tests/conformance.rs b/tests/conformance.rs index 46aa3a65..44518df3 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -1,6 +1,6 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; +use std::{collections::HashMap, fs::File, io::BufReader}; use ucan::{ capability::DefaultCapabilityParser, @@ -318,10 +318,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { - if ucan - .validate(ucan::time::now(), &did_verifier_map) - .is_ok() - { + if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { report.register_failure( &name, "expected token to fail validation, but it passed".to_string(), From e739577b2df16578ef1620bb3994f297b3539fa2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 15:38:31 -0800 Subject: [PATCH 115/234] Time! --- src/ipld.rs | 10 +---- src/time.rs | 123 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 51 deletions(-) diff --git a/src/ipld.rs b/src/ipld.rs index 04f40ad8..34a3e1e1 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,7 +3,6 @@ pub mod cid; use libipld_core::ipld::Ipld; -use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -23,7 +22,7 @@ use js_sys::{Array, Map, Object, Uint8Array}; /// let wrapped = ipld::Newtype(ipld.clone()); /// // wrapped.some_trait_method(); /// ``` -// / +/// /// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. /// /// ``` @@ -38,13 +37,6 @@ use js_sys::{Array, Map, Object, Uint8Array}; #[derive(Debug, Clone, PartialEq)] pub struct Newtype(pub Ipld); -// FIXME -// impl fmt::Display for Newtype { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "{}", String::from(self.0)) -// } -// } - impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/time.rs b/src/time.rs index 987c6d23..0e07098b 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,9 +1,14 @@ //! Time utilities -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use thiserror::Error; use web_time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { SystemTime::now() @@ -12,19 +17,17 @@ pub fn now() -> u64 { .as_secs() } +/// All timestamps that this library can handle +/// +/// Strictly speaking, UCAN only supports [`JsTime`] for JavaScript interoperability. #[derive(Debug, Clone, PartialEq)] pub enum Timestamp { - // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too - // Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) - // (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. - // - // This range can represent millions of years into the future, - // and is thus sufficient for nearly all use cases. - Sending(JsTime), + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numberic range + JsSafe(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [SystemTime] - Receiving(SystemTime), + /// received timestamps may be parsed as regular [`SystemTime`] + Postel(SystemTime), } impl Serialize for Timestamp { @@ -33,8 +36,8 @@ impl Serialize for Timestamp { S: Serializer, { match self { - Timestamp::Sending(js_time) => js_time.serialize(serializer), - Timestamp::Receiving(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + Timestamp::JsSafe(js_time) => js_time.serialize(serializer), + Timestamp::Postel(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), } } } @@ -45,7 +48,7 @@ impl<'de> Deserialize<'de> for Timestamp { D: Deserializer<'de>, { if let Ok(js_time) = JsTime::deserialize(deserializer) { - return Ok(Timestamp::Sending(js_time)); + return Ok(Timestamp::JsSafe(js_time)); } todo!() @@ -56,8 +59,8 @@ impl<'de> Deserialize<'de> for Timestamp { impl From for SystemTime { fn from(timestamp: Timestamp) -> Self { match timestamp { - Timestamp::Sending(js_time) => js_time.time, - Timestamp::Receiving(sys_time) => sys_time, + Timestamp::JsSafe(js_time) => js_time.time, + Timestamp::Postel(sys_time) => sys_time, } } } @@ -69,48 +72,47 @@ impl From for Ipld { } impl TryFrom for Timestamp { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct JsTime { time: SystemTime, } -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) +#[cfg(target_arch = "wasm32")] +impl JsTime { + /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) } -} -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + /// Lower the [`JsTime`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) } } -// FIXME just lifting this from Elixir for now -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OutOfRangeError { - pub tried: SystemTime, -} - impl JsTime { /// Create a [`JsTime`] from a [`SystemTime`] /// @@ -136,3 +138,40 @@ impl From for Ipld { .into() } } + +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} + +/// An error expressing when a time is larger than 2^53 seconds past the Unix epoch +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub struct OutOfRangeError { + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53) + pub tried: SystemTime, +} + +impl fmt::Display for OutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "time out of JsTime (2^53) range: {:?}", self.tried) + } +} From 0766cfea3d1862ddbe2bcc7118df1ae06ba7579f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 16:23:40 -0800 Subject: [PATCH 116/234] Save --- src/invocation/payload.rs | 3 ++ src/task.rs | 63 +++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 58c4e892..24e314f5 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -17,6 +17,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { + // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, pub audience: Option, @@ -32,6 +33,8 @@ pub struct Payload { pub expiration: Timestamp, } +// FIXME To TaskId + // NOTE This is the version that accepts promises pub type Unresolved = Payload; // type Dynamic = Payload; <- ? diff --git a/src/task.rs b/src/task.rs index 00411b7b..c3363f0a 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,4 +1,6 @@ -use crate::{did::Did, nonce::Nonce}; +//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup + +use crate::{ability::arguments, did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -9,27 +11,30 @@ use libipld_core::{ serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::fmt::Debug; const SHA2_256: u64 = 0x12; +/// The fields required to uniquely identify a [`Task`], potentially across multiple executors. +/// +/// This struct should not be used directly, but rather through a [`From`] instance +/// on the type. In particular, the `nonce` field should be constant for all of the same type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub sub: Option, // Is this optional? May as well make it so for now! + /// The `subject`: root issuer, and arbiter of the semantics/namespace + pub sub: Did, + + /// A unique identifier for the particular task run + /// + /// This is an [`Option`] because not all task types require a nonce. #[serde(default, skip_serializing_if = "Option::is_none")] pub nonce: Option, + /// The command identifier pub cmd: String, - pub args: BTreeMap, // FIXME change to Named? -} -impl From for Id { - fn from(task: Task) -> Id { - Id { - cid: Cid::from(task), - } - } + /// The arguments to the command + pub args: arguments::Named, } impl TryFrom for Task { @@ -46,7 +51,25 @@ impl From for Ipld { } } +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + + CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), + ) + } +} + +/// The unique identifier for a [`Task`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Id { pub cid: Cid, } @@ -65,16 +88,10 @@ impl From for Ipld { } } -impl From for Cid { - fn from(task: Task) -> Cid { - let mut buffer = vec![]; - let ipld: Ipld = task.into(); - ipld.encode(DagCborCodec, &mut buffer) - .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1( - DagCborCodec.into(), - MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) - .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), - ) +impl From for Id { + fn from(task: Task) -> Id { + Id { + cid: Cid::from(task), + } } } From a111ffb97bfec76cb6a20145903d0dd8fa9a24b2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 16:35:20 -0800 Subject: [PATCH 117/234] No more meta handling --- src/agent.rs | 17 +- src/delegation.rs | 6 +- src/delegation/payload.rs | 55 +++---- src/delegation/store.rs | 12 +- src/invocation.rs | 2 +- src/invocation/payload.rs | 39 ++--- src/lib.rs | 2 - src/metadata.rs | 330 -------------------------------------- src/metadata/keyed.rs | 50 ------ src/new_wasm.rs | 7 - src/receipt/payload.rs | 40 ++--- src/receipt/receipt.rs | 2 +- src/receipt/store.rs | 8 +- 13 files changed, 78 insertions(+), 492 deletions(-) delete mode 100644 src/metadata.rs delete mode 100644 src/metadata/keyed.rs delete mode 100644 src/new_wasm.rs diff --git a/src/agent.rs b/src/agent.rs index 809414a3..a0023741 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -3,7 +3,6 @@ use crate::{ delegation::{traits::Condition, Delegatable, Delegation}, did::Did, invocation::Invocation, - metadata as meta, proof::parents::CheckParents, }; @@ -19,10 +18,10 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( + pub fn invoke( &self, - delegation: Delegation, - proof_chain: Vec>, // FIXME T must also accept Self and * + delegation: Delegation, + proof_chain: Vec>, // FIXME T must also accept Self and * ) -> () where T::Parents: Delegatable, @@ -34,9 +33,9 @@ impl Agent { todo!() } - pub fn revoke( + pub fn revoke( &self, - delegation: Delegation, + delegation: Delegation, ) -> () // where // T::Parents: Delegatable, @@ -44,14 +43,14 @@ impl Agent { todo!() } - pub fn receive_delegation( + pub fn receive_delegation( &self, - delegation: Delegation, + delegation: Delegation, ) -> () { todo!() } - pub fn receive_invocation(&self, invocation: Invocation) -> () { + pub fn receive_invocation(&self, invocation: Invocation) -> () { todo!() } diff --git a/src/delegation.rs b/src/delegation.rs index 3a8b3e3b..f457be00 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,7 +11,7 @@ pub use payload::Payload; use condition::traits::Condition; use store::IndexedStore; -use crate::{metadata as meta, signature}; +use crate::signature; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -19,10 +19,10 @@ use crate::{metadata as meta, signature}; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8a3616e9..c23371c7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,9 +5,6 @@ use crate::{ did::Did, invocation, invocation::Resolvable, - metadata as meta, - metadata::Metadata, - // metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -22,7 +19,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -30,21 +27,21 @@ pub struct Payload { pub ability_builder: T::Builder, pub conditions: Vec, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where @@ -55,11 +52,10 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::MultiKeyed> Deserialize<'de> - for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -74,10 +70,9 @@ where } } -impl TryFrom - for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -87,25 +82,23 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::MultiKeyed> - Payload -{ +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version - proofs: Vec>, + invoked: &'a invocation::Payload, // FIXME promisory version + proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -155,9 +148,9 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::MultiKeyed>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, - proof: &Payload, + proof: &Payload, invoked_ipld: &Ipld, now: SystemTime, ) -> Outcome<(), (), ()> @@ -242,13 +235,11 @@ struct InternalSerializer { expiration: Timestamp, } -impl, E: meta::MultiKeyed> - From> for InternalSerializer +impl> From> for InternalSerializer where BTreeMap: From, - Ipld: From, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -258,7 +249,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata.into(), + metadata: payload.metadata, nonce: payload.nonce, not_before: payload.not_before, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index a75db20f..87e06108 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,15 +1,15 @@ use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; -use crate::{did::Did, metadata as meta}; +use crate::did::Did; use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use web_time::SystemTime; // NOTE can already look up by CID in other traits -pub trait IndexedStore { +pub trait IndexedStore { type Error; - fn get_by(query: Query) -> Result>, Self::Error>; + fn get_by(query: Query) -> Result>, Self::Error>; fn previously_checked(cid: Cid) -> Result; @@ -20,7 +20,7 @@ pub trait IndexedStore { subject: Did, command: String, audience: Did, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; // if let Ok(possible) = self.get_by(Query { // audience: Some(audience), // command: Some(command), @@ -53,13 +53,13 @@ pub trait IndexedStore { // } // } - fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { + fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { todo!() //let mut results = Self::get_by(query)?; //results.pop().ok_or_else(|_| todo!()) } - fn expired(&self) -> Result>, Self::Error> { + fn expired(&self) -> Result>, Self::Error> { todo!() // self.get_by(Query { // expires_before: Some(SystemTime::now()), diff --git a/src/invocation.rs b/src/invocation.rs index 1e2a8a40..993fd5d3 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -9,4 +9,4 @@ pub use resolvable::Resolvable; use crate::signature; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 24e314f5..caf2fffc 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,9 +3,6 @@ use crate::{ ability::{arguments, command::Command}, capsule::Capsule, did::Did, - metadata as meta, - metadata::Metadata, - // metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; @@ -16,7 +13,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, @@ -26,7 +23,7 @@ pub struct Payload { pub proofs: Vec, pub cause: Option, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub not_before: Option, @@ -36,7 +33,7 @@ pub struct Payload { // FIXME To TaskId // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions @@ -52,14 +49,14 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -70,10 +67,10 @@ where } } -impl<'de, T, E: meta::MultiKeyed> serde::Deserialize<'de> for Payload +impl<'de, T> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -88,9 +85,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -100,8 +97,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -199,10 +196,8 @@ impl TryFrom for InternalSerializer { // } // } -impl, E: meta::MultiKeyed> From> - for InternalSerializer -{ - fn from(payload: Payload) -> Self { +impl> From> for InternalSerializer { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -213,7 +208,7 @@ impl, E: meta::MultiKeyed> From = BTreeMap::from_iter([ -/// ("foo".into(), Ipld::String("hello world".to_string())), -/// ("bar".into(), Ipld::Integer(42)), -/// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) -/// ]); -/// -/// let meta: Metadata = Metadata::try_from(kv.clone()).unwrap(); -/// -/// assert_eq!(meta.known().len(), 0); -/// assert_eq!(meta.unknown(), &kv); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Empty {} - -/// A type alias for [`Metadata`] with no known fields. -pub type Unstructured = Metadata; - -// NOTE no Serde -/// Parsed metadata fields. -/// -/// If you don't have any known fields, you can set `T ` to [`Empty`] (or [`Unstructured`]) -#[derive(Debug, Clone, PartialEq)] -pub struct Metadata { - /// Structured metadata, selected by matching `T` - known: BTreeMap, - - /// Unstructured metadata - unknown: BTreeMap, -} - -impl Metadata { - /// Constructor for [`Metadata`] - /// - /// This checks that no duplicate keys are present - /// - /// # Examples - /// - /// ```rust - /// # use ucan::metadata::{Metadata, Empty}; - /// # use std::collections::BTreeMap; - /// # use libipld_core::ipld::Ipld; - /// # - /// #[derive(Debug, Clone, PartialEq)] - /// pub enum MyFacts { - /// Timeout(u32), - /// Retry{max: u32, delay: u32}, - /// NotGoingToUseThisOne(String), - /// } - /// - /// let known: BTreeMap = BTreeMap::from_iter([ - /// ("timeout".into(), MyFacts::Timeout(1000)), - /// ("retry".into(), MyFacts::Retry{max: 5, delay: 100}) - /// ]); - /// - /// let unknown: BTreeMap = BTreeMap::from_iter([ - /// ("foo".into(), Ipld::String("hello world".to_string())), - /// ("bar".into(), Ipld::Integer(42)), - /// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) - /// ]); - /// - /// let meta = Metadata::new(known.clone(), unknown.clone()).unwrap(); - /// - /// assert_eq!(meta.known(), &known.clone()); - /// assert_eq!(meta.unknown(), &unknown.clone()); - /// - /// let collision: BTreeMap = BTreeMap::from_iter([ - /// ("timeout".into(), Ipld::String("not a timeout".to_string())), - /// ]); - /// - /// let meta = Metadata::new(known, collision); - /// - /// assert!(meta.is_err()); - /// ``` - pub fn new( - known: BTreeMap, - unknown: BTreeMap, - ) -> Result { - for k in known.keys() { - if unknown.contains_key(k) { - return Err(k.into()); - } - } - - Ok(Self { known, unknown }) - } - - /// Getter for the `known` field - pub fn known<'a>(&'a self) -> &'a BTreeMap { - &self.known - } - - /// Getter for the `unknown` field - pub fn unknown<'a>(&'a self) -> &'a BTreeMap { - &self.unknown - } - - /// Insert a value into the `known` field - /// - /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already - /// exists in the `unknown` field. - /// - /// It will return `Some(t: T)` if you insert a key that was already present - /// in the `known` field. - pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Option> { - if let Some(ipld) = self.unknown.get(&key) { - self.known.insert(key, value); - return Some(Entry::Unknown(ipld.clone())); - } - - self.known.insert(key, value).map(Entry::Known) - } - - /// Insert a value into the `unknown` field - /// - /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already - /// exists in the `unknown` field. - /// - /// It will return `Some(t: T)` if you insert a key that was already present - /// in the `known` field. - pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Option> - where - T: Clone, - { - if let Some(t) = self.known.get(&key) { - self.unknown.insert(key, value); - return Some(Entry::Known(t.clone())); - } - - self.unknown.insert(key, value).map(Entry::Unknown) - } - - /// Remove a field from either field - /// - /// This will return `Some(Entry::Unknown(ipld))` if there was a key in the `unknown` field. - /// - /// It will return `Some(t: T)` if there was a key in the `known` field. - pub fn remove_key<'a>(&'a mut self, key: &str) -> Option> { - if let Some(ipld) = self.unknown.remove(key) { - return Some(Entry::Unknown(ipld)); - } - - self.known.remove(key).map(Entry::Known) - } -} - -/// Tag values as belonging to the `known` or `unknown` field. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Entry { - /// The tag for a value in the `known` field - Known(T), - - /// The tag for a value in the `unknown` field - Unknown(Ipld), -} - -impl Default for Metadata { - fn default() -> Self { - Metadata { - known: BTreeMap::new(), - unknown: BTreeMap::new(), - } - } -} - -impl From> for BTreeMap { - fn from(meta: Metadata) -> Self { - meta.unknown - } -} - -impl> From> for BTreeMap { - // NOTE duplicate keys "shouldn't" be possible (because this roughly follows GDP) - // ...so we can just merge - fn from(meta: Metadata) -> Self { - let mut btree = meta.unknown; - for (k, v) in meta.known { - btree.insert(k, v.into()); - } - btree - } -} - -impl From> for Metadata { - // FIXME better error - fn from(merged: BTreeMap) -> Self { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in merged { - if T::KEYS.contains(&k.as_str()) { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); - } else { - unknown.insert(k, v); - } - } else { - unknown.insert(k, v); - } - } - - Metadata { known, unknown } - } -} - -impl From> for Metadata { - fn from(btree: BTreeMap) -> Self { - Metadata { - known: BTreeMap::new(), - unknown: btree, - } - } -} - -impl TryFrom> for Ipld { - type Error = Infallible; - - fn try_from(meta: Metadata) -> Result { - Ok(Ipld::Map(meta.unknown)) - } -} - -impl> TryFrom> for Ipld { - type Error = String; // FIXME - - fn try_from(meta: Metadata) -> Result { - let mut btree = meta.unknown.clone(); - - for (k, v) in meta.known { - if let Some(_) = meta.unknown.get(&k) { - return Err(k); - } - - btree.insert(k, v.into()); - } - - Ok(Ipld::Map(btree)) - } -} - -impl Serialize for Metadata { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = Ipld::Map((*self).clone().into()); - serde::Serialize::serialize(&s, serializer) - } -} - -impl<'de, T: MultiKeyed + Clone> Deserialize<'de> for Metadata { - fn deserialize(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ipld::deserialize(d).and_then(|ipld| ipld.try_into().map_err(|_| todo!())) - } -} - -impl TryFrom for Metadata { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - match ipld { - Ipld::Map(btree) => { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in btree { - if T::KEYS.contains(&k.as_str()) { - if let Ok(fact) = T::try_from(v.clone()) { - known.insert(k, fact); - } else { - unknown.insert(k, v); - } - } else { - unknown.insert(k, v); - } - } - - Ok(Self { known, unknown }) - } - _ => Err(()), - } - } -} - -// // FIXME Just as an example, plz delete -// #[derive(Debug, Clone, Serialize, Deserialize)] -// #[serde(deny_unknown_fields)] -// pub struct IpvmConfig { -// pub max_retries: u32, -// pub workflow_fuel: u32, -// } -// -// impl Keyed for IpvmConfig { -// const KEY: &'static str = "ipvm/config"; -// } -// -// impl From for Ipld { -// fn from(config: IpvmConfig) -> Self { -// config.into() -// } -// } -// -// impl TryFrom for IpvmConfig { -// type Error = SerdeError; -// -// fn try_from(ipld: Ipld) -> Result { -// ipld_serde::from_ipld(ipld) -// } -// } diff --git a/src/metadata/keyed.rs b/src/metadata/keyed.rs deleted file mode 100644 index 174023f1..00000000 --- a/src/metadata/keyed.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Keys for metadata fields (for parsing and guarding entries) - -use libipld_core::ipld::Ipld; - -/// A parser trait for types that can be used as metadata fields. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::metadata::Keyed; -/// # use std::collections::BTreeMap; -/// # use libipld::{ipld, ipld::Ipld}; -/// # -/// pub struct MyKeyed { -/// pub foo: String, -/// pub bar: u32, -/// } -/// -/// impl Keyed for MyKeyed { -/// const KEY: &'static str = "my/entry"; -/// } -/// assert_eq!(MyKeyed::KEY, "my/entry"); -/// -/// let kv: BTreeMap = BTreeMap::from_iter([ -/// (MyKeyed::KEY.into(), ipld!({"foo": "hello world", "bar": 42})) -/// ]); -/// -/// assert_eq!(kv.get(MyKeyed::KEY), Some(&ipld!({"foo": "hello world", "bar": 42}))); -/// ``` -pub trait Keyed { - /// The (string) key for this entry - /// - /// These should be unique per `Keyed` type to avoid parser collisions. - /// Even if a duplicate key is used, the shape of the contained data can - /// also be used to disambiguate. - const KEY: &'static str; -} - -// FIXME keyed? -/// A parser trait for one-or-more unioned types that can be used as metadata fields. -/// -/// [`MultiKeyed`] may be composed of a single entry, or the union of sevveral e.g. via enum. -pub trait MultiKeyed: TryFrom + Into { - /// The (string) keys for an enum merging multiple [`Keyed`]s - const KEYS: &'static [&'static str]; -} - -impl + Into> MultiKeyed for T { - const KEYS: &'static [&'static str] = &[T::KEY]; -} diff --git a/src/new_wasm.rs b/src/new_wasm.rs deleted file mode 100644 index 760bf027..00000000 --- a/src/new_wasm.rs +++ /dev/null @@ -1,7 +0,0 @@ -// use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; -// use js_sys; -// use serde::{Deserialize, Serialize}; -// use wasm_bindgen::prelude::*; - -// #[wasm_bindgen] -// type JsDynamic = Dynamic; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 83662cc2..81311154 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,8 +1,5 @@ use super::responds::Responds; -use crate::{ - ability::arguments, capsule::Capsule, did::Did, metadata as meta, metadata::Metadata, - nonce::Nonce, time::Timestamp, -}; +use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; @@ -10,7 +7,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, @@ -18,21 +15,20 @@ pub struct Payload { pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, + Payload: Clone, T::Success: Serialize + DeserializeOwned, - Ipld: From, { fn serialize(&self, serializer: S) -> Result where @@ -43,13 +39,9 @@ where } } -impl< - 'de, - T: Responds + Deserialize<'de>, - E: DeserializeOwned + Serialize + meta::MultiKeyed + TryFrom, - > Deserialize<'de> for Payload +impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, + as TryFrom>>::Error: Debug, T::Success: DeserializeOwned + Serialize, { fn deserialize(d: D) -> Result @@ -63,8 +55,7 @@ where } } -impl + meta::MultiKeyed> TryFrom - for Payload +impl TryFrom for Payload where T::Success: Serialize + DeserializeOwned, { @@ -76,8 +67,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -105,7 +96,7 @@ where issued_at: Option, } -impl> From> for Payload +impl From> for Payload where T::Success: Serialize + DeserializeOwned, { @@ -116,19 +107,18 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata.into(), + metadata: s.metadata, nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where - Ipld: From, T::Success: Serialize + DeserializeOwned, { - fn from(s: Payload) -> Self { + fn from(s: Payload) -> Self { InternalSerializer { issuer: s.issuer, ran: s.ran, diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs index 594bf3a4..dfd1db01 100644 --- a/src/receipt/receipt.rs +++ b/src/receipt/receipt.rs @@ -1,4 +1,4 @@ use super::payload::Payload; use crate::signature; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 35c7d5ed..75cd0691 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,15 +1,15 @@ use super::{Receipt, Responds}; -use crate::{metadata, task}; +use crate::task; use libipld_core::ipld::Ipld; -pub trait Store { +pub trait Store { type Error; - fn get(id: task::Id) -> Result, Self::Error> + fn get(id: task::Id) -> Result, Self::Error> where ::Success: TryFrom; - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } From e8d7afe3c260bcf6697cfc34984c5bc99d680f1e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 21:07:32 -0800 Subject: [PATCH 118/234] Save ahead of reorg --- flake.nix | 18 +- src/ability/dynamic.rs | 5 +- src/ability/ucan.rs | 1 + src/ability/ucan/revoke.rs | 91 ++++ src/delegation/delegatable.rs | 2 + src/did.rs | 15 +- src/did_verifier.rs | 2 +- src/lib.rs | 2 +- src/nonce.rs | 115 ++++- src/proof/checkable.rs | 2 + src/reader.rs | 111 +++-- src/task.rs | 10 +- src/time.rs | 33 +- tests/conformance.rs | 781 +++++++++++++++++----------------- 14 files changed, 699 insertions(+), 489 deletions(-) create mode 100644 src/ability/ucan/revoke.rs diff --git a/flake.nix b/flake.nix index 3415cbca..3cd19cb8 100644 --- a/flake.nix +++ b/flake.nix @@ -254,7 +254,7 @@ name = "watch:test:docs:host"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec test"; + command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; } { name = "watch:test:wasm"; @@ -266,7 +266,7 @@ name = "watch:test:docs:wasm"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown --features=mermaid_docs'"; } # Test { @@ -310,26 +310,32 @@ name = "docs"; help = "[DEFAULT]: Open refreshed docs"; category = "dev"; - command = "docs:open"; + command = "docs:open:host"; } { - name = "docs:build"; + name = "docs:build:host"; help = "Refresh the docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs"; } { - name = "docs:wasm:build"; + name = "docs:build:wasm"; help = "Refresh the docs with the wasm32-unknown-unknown target"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; } { - name = "docs:open"; + name = "docs:open:host"; help = "Open refreshed docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --open"; } + { + name = "docs:open:wasm"; + help = "Open refreshed docs"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; + } { name = "docs:wasm:open"; help = "Open refreshed docs for wasm32-unknown-unknown"; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index f6998fa3..b08c51c8 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -13,16 +13,15 @@ use wasm_bindgen::prelude::*; /// A "dynamic" ability with the bare minimum of statics /// ///
-/// This should be a last resort for e.g. FFI. The Dynamic ability is +/// This should be a last resort, and only for e.g. FFI. The Dynamic ability is /// not recommended for typical Rust usage. /// -/// /// This is instead meant to be embedded inside of structs that have e.g. FFI bindings to /// a validation function, such as `js_sys::Function` for JS, `magnus::function!` for Ruby, /// and so on. ///
/// -/// Dynamic none of the typical ability traits directly. Rather, it must be wrapped +/// [`Dynamic`] uses none of the typical ability traits directly. Rather, it must be wrapped /// in [`Reader`][crate::reader::Reader], which wires up dynamic dispatch for the /// relevant traits using a configuration struct. #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 830f7c22..1987e8a1 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,2 +1,3 @@ pub mod proxy; +pub mod revoke; // FIXME pub mod revoke; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs new file mode 100644 index 00000000..aa13e358 --- /dev/null +++ b/src/ability/ucan/revoke.rs @@ -0,0 +1,91 @@ +//! UCAN [Revocations](https://github.com/ucan-wg/revocation) + +use crate::{ + ability::{arguments, command::Command}, + delegation::Delegatable, + invocation::{Promise, Resolvable}, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +/// An ability for revoking previously issued UCANs by [`Cid`] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + // FIXME check spec + /// The UCAN to revoke + pub ucan: Arg, +} + +impl Command for Generic { + const COMMAND: &'static str = "ucan/revoke"; +} + +/// The fully resolved variant: ready to execute. +pub type Ready = Generic; + +impl Delegatable for Ready { + type Builder = Builder; +} + +impl Resolvable for Ready { + type Promised = Promised; +} + +/// A variant with some fields waiting to be set. +pub type Builder = Generic>; + +impl From for Builder { + fn from(resolved: Ready) -> Builder { + Builder { + ucan: Some(resolved.ucan), + } + } +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(b: Builder) -> Result { + Ok(Ready { + ucan: b.ucan.ok_or(())?, + }) + } +} + +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { + let mut btree = BTreeMap::new(); + if let Some(cid) = b.ucan { + btree.insert("ucan".into(), cid.into()); + } + arguments::Named(btree) + } +} + +/// A variant where arguments may be [`Promise`]s +pub type Promised = Generic>; + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + ucan: Promise::Fulfilled(r.ucan), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) + } +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(p: Promised) -> Result { + Ok(Ready { + ucan: p.ucan.try_resolve().map_err(|_| ())?, + }) + } +} diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 757976c8..dcb733d9 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,5 +1,7 @@ use crate::ability::arguments; pub trait Delegatable: Sized { + /// A delegation with some arguments filled + /// FIXME add more type Builder: TryInto + From + Into; } diff --git a/src/did.rs b/src/did.rs index 66f8beaa..c5312722 100644 --- a/src/did.rs +++ b/src/did.rs @@ -1,3 +1,5 @@ +//! Decentralized Identifier (DID) utilities + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -63,21 +65,14 @@ impl TryFrom for Did { #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { /// Strutural errors in the [`Did`] - StructuralError(did_url::Error), + #[error(transparent)] + StructuralError(#[from] did_url::Error), /// The [`Ipld`] was not a string + #[error("Not an IPLD String")] NotAnIpldString(Ipld), } -impl fmt::Display for FromIpldError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FromIpldError::StructuralError(e) => write!(f, "DID Error: {}", e), - FromIpldError::NotAnIpldString(_ipld) => write!(f, "Not an IPLD String"), // FIXME include the bad ipld, but needs a Display instance - } - } -} - impl Serialize for FromIpldError { fn serialize(&self, serializer: S) -> Result where diff --git a/src/did_verifier.rs b/src/did_verifier.rs index 62c5ab59..14a2f47a 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -1,4 +1,4 @@ -//! DID verifier methods +// //! DID verifier methods use core::fmt; use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 794ebac0..c8928529 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ extern crate alloc; // pub mod builder; // pub mod capability; // pub mod crypto; -// pub mod did_verifier; // pub mod error; // pub mod plugins; // pub mod semantics; @@ -68,6 +67,7 @@ pub mod agent; pub mod capsule; pub mod delegation; pub mod did; +// pub mod did_verifier; pub mod invocation; pub mod ipld; pub mod nonce; diff --git a/src/nonce.rs b/src/nonce.rs index b9c775e5..9f3628d9 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -38,6 +38,16 @@ impl From<[u8; 16]> for Nonce { } } +impl From for Vec { + fn from(nonce: Nonce) -> Self { + match nonce { + Nonce::Nonce12(nonce) => nonce.to_vec(), + Nonce::Nonce16(nonce) => nonce.to_vec(), + Nonce::Custom(nonce) => nonce, + } + } +} + impl From> for Nonce { fn from(nonce: Vec) -> Self { match nonce.len() { @@ -57,31 +67,80 @@ impl From> for Nonce { } impl Nonce { - // NOTE seed = domain-separator - pub fn generate_12(seed: &mut Vec) -> Nonce { - seed.append(&mut [0].repeat(12)); - - let buf = seed.as_mut_slice(); + // NOTE salt = domain-separator + /// Generate a 96-bit, 12-byte nonce. + /// This is the minimum nonce size typically recommended. + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + /// + /// # Example + /// + /// ```rust + /// # use ucan::nonce::Nonce; + /// # use ucan::did::Did; + /// # + /// let mut salt = "did:example:123".as_bytes().to_vec(); + /// let nonce = Nonce::generate_12(&mut salt); + /// + /// assert_eq!(Vec::from(nonce).len(), 12); + /// ``` + pub fn generate_12(salt: &mut Vec) -> Nonce { + salt.append(&mut [0].repeat(12)); + + let buf = salt.as_mut_slice(); getrandom(buf).expect("irrecoverable getrandom failure"); let mut hasher = Sha2_256::default(); hasher.update(buf); - let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + let bytes = hasher + .finalize() + .chunks(12) + .next() + .expect("SHA2_256 is 32 bytes") + .try_into() + .expect("we set the length to 12 earlier"); + Nonce::Nonce12(bytes) } - pub fn generate_16(seed: &mut Vec) -> Nonce { - seed.append(&mut [0].repeat(16)); - - let buf = seed.as_mut_slice(); + /// Generate a 128-bit, 16-byte nonce + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + /// + /// # Example + /// + /// ```rust + /// # use ucan::nonce::Nonce; + /// # use ucan::did::Did; + /// # + /// let mut salt = "did:example:123".as_bytes().to_vec(); + /// let nonce = Nonce::generate_16(&mut salt); + /// + /// assert_eq!(Vec::from(nonce).len(), 16); + /// ``` + pub fn generate_16(salt: &mut Vec) -> Nonce { + salt.append(&mut [0].repeat(16)); + + let buf = salt.as_mut_slice(); getrandom(buf).expect("irrecoverable getrandom failure"); let mut hasher = Sha2_256::default(); hasher.update(buf); - let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); - Nonce::Nonce12(bytes) + let bytes = hasher + .finalize() + .chunks(16) + .next() + .expect("SHA2_256 is 32 bytes") + .try_into() + .expect("we set the length to 16 earlier"); + + Nonce::Nonce16(bytes) } } @@ -144,6 +203,7 @@ impl TryFrom<&Ipld> for Nonce { #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] +/// A JavaScript-compatible wrapper for [`Nonce`] pub struct JsNonce(#[wasm_bindgen(skip)] pub Nonce); #[cfg(target_arch = "wasm32")] @@ -163,18 +223,39 @@ impl From for JsNonce { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsNonce { - pub fn generate_12(mut seed: Vec) -> JsNonce { - Nonce::generate_12(&mut seed).into() + /// Generate a 96-bit, 12-byte nonce. + /// This is the minimum nonce size typically recommended. + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + pub fn generate_12(mut salt: Vec) -> JsNonce { + Nonce::generate_12(&mut salt).into() } - pub fn generate_16(mut seed: Vec) -> JsNonce { - Nonce::generate_16(&mut seed).into() + /// Generate a 128-bit, 16-byte nonce + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + pub fn generate_16(mut salt: Vec) -> JsNonce { + Nonce::generate_16(&mut salt).into() } - pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { + /// Directly lift a 12-byte `Uint8Array` into a [`JsNonce`] + /// + /// # Arguments + /// + /// * `nonce` - The exact nonce to convert to a [`JsNonce`] + pub fn from_uint8_array(nonce: Box<[u8]>) -> JsNonce { Nonce::from(arr.to_vec()).into() } + /// Expose the underlying bytes of a [`JsNonce`] as a 12-byte `Uint8Array` + /// + /// # Arguments + /// + /// * `self` - The [`JsNonce`] to convert to a `Uint8Array` pub fn to_uint8_array(&self) -> Box<[u8]> { match &self.0 { Nonce::Nonce12(nonce) => nonce.to_vec().into_boxed_slice(), diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 9b04cf1f..87b08f4f 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -2,6 +2,8 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; +// FIXME move to Delegatbel? + /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { /// The type of hierarchy this ability has diff --git a/src/reader.rs b/src/reader.rs index 60032406..ecb03a97 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -8,8 +8,6 @@ use crate::{ }; use serde::{Deserialize, Serialize}; -// NOTE to self: this is helpful as a common container to lift various FFI into - /// A struct that attaches an ambient environment to a value /// /// This is helpful for dependency injection and/or passing around values that @@ -72,31 +70,6 @@ pub struct Reader { pub val: T, } -impl> From> for arguments::Named { - fn from(reader: Reader) -> Self { - reader.val.into() - } -} - -// NOTE plug this into Reader like: Reader> -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Promised(pub T); - -impl> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl> From> for arguments::Named { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - impl Reader { /// Map a function over the `val` of the [`Reader`] pub fn map(self, func: F) -> Reader @@ -178,12 +151,79 @@ impl Reader { } } +impl> From> for arguments::Named { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +impl Checkable for Reader +where + Reader: CheckSame, +{ + type Hierarchy = Env::Hierarchy; +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +/// A helper newtype that marks a value as being a [`Delegatable::Builder`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Builder}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Builder(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +impl> From> for arguments::Named { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + impl From> for Reader> { fn from(reader: Reader) -> Self { reader.map(Builder) } } +impl> Delegatable for Reader { + type Builder = Reader>; +} + +/// A helper newtype that marks a value as being a [`Resolvable::Promised`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Promised}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Promised(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl> From> for arguments::Named { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + impl From>> for Reader { fn from(reader: Reader>) -> Self { reader.map(|b| b.0) @@ -202,23 +242,6 @@ impl From>> for Reader { } } -impl> Delegatable for Reader { - type Builder = Reader>; -} - impl> Resolvable for Reader { type Promised = Reader>; } - -impl ToCommand for Reader { - fn to_command(&self) -> String { - self.env.to_command() - } -} - -impl Checkable for Reader -where - Reader: CheckSame, -{ - type Hierarchy = Env::Hierarchy; -} diff --git a/src/task.rs b/src/task.rs index c3363f0a..f9c616db 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,19 +21,19 @@ const SHA2_256: u64 = 0x12; /// on the type. In particular, the `nonce` field should be constant for all of the same type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { - /// The `subject`: root issuer, and arbiter of the semantics/namespace + /// The `subject`: root issuer, and arbiter of the semantics/namespace. pub sub: Did, - /// A unique identifier for the particular task run + /// A unique identifier for the particular task run. /// /// This is an [`Option`] because not all task types require a nonce. #[serde(default, skip_serializing_if = "Option::is_none")] pub nonce: Option, - /// The command identifier + /// The command identifier. pub cmd: String, - /// The arguments to the command + /// The arguments to the command. pub args: arguments::Named, } @@ -67,7 +67,7 @@ impl From for Cid { } } -/// The unique identifier for a [`Task`] +/// The unique identifier for a [`Task`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { diff --git a/src/time.rs b/src/time.rs index 0e07098b..1440e1b0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -9,7 +9,7 @@ use web_time::{Duration, SystemTime, UNIX_EPOCH}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -/// Get the current time in seconds since UNIX_EPOCH +/// Get the current time in seconds since [`UNIX_EPOCH`]. pub fn now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -17,16 +17,16 @@ pub fn now() -> u64 { .as_secs() } -/// All timestamps that this library can handle +/// All timestamps that this library can handle. /// -/// Strictly speaking, UCAN only supports [`JsTime`] for JavaScript interoperability. +/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). #[derive(Debug, Clone, PartialEq)] pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numberic range + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. JsSafe(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`] + /// received timestamps may be parsed as regular [`SystemTime`]. Postel(SystemTime), } @@ -47,12 +47,14 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(js_time) = JsTime::deserialize(deserializer) { - return Ok(Timestamp::JsSafe(js_time)); + if let Ok(sys_time) = SystemTime::deserialize(deserializer) { + match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + } + } else { + Err(serde::de::Error::custom("not a Timestamp")) } - - todo!() - // FIXME just todo()ing this for now becuase the enum will likely go away very shortly } } @@ -79,6 +81,8 @@ impl TryFrom for Timestamp { } } +/// A JavaScript-wrapper for [`Timestamp`]. +/// /// Per the UCAN spec, timestamps MUST respect [IEEE-754] /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. /// @@ -93,6 +97,7 @@ pub struct JsTime { } #[cfg(target_arch = "wasm32")] +#[wasm_bindgen] impl JsTime { /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] pub fn from_date(date_time: js_sys::Date) -> Result { @@ -114,7 +119,11 @@ impl JsTime { } impl JsTime { - /// Create a [`JsTime`] from a [`SystemTime`] + /// Create a [`JsTime`] from a [`SystemTime`]. + /// + /// # Arguments + /// + /// * `time` — The time to convert /// /// # Errors /// @@ -166,7 +175,7 @@ impl<'de> Deserialize<'de> for JsTime { /// An error expressing when a time is larger than 2^53 seconds past the Unix epoch #[derive(Debug, Clone, PartialEq, Eq, Error)] pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53) + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53). pub tried: SystemTime, } diff --git a/tests/conformance.rs b/tests/conformance.rs index 44518df3..8e24da1f 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -1,390 +1,391 @@ -use libipld_core::{ipld::Ipld, raw::RawCodec}; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs::File, io::BufReader}; - -use ucan::{ - capability::DefaultCapabilityParser, - did_verifier::DidVerifierMap, - store::{self, Store}, - ucan::Ucan, - DefaultFact, -}; - -trait TestTask { - fn run(&self, name: &str, report: &mut TestReport); -} - -#[derive(Debug, Default)] -struct TestReport { - num_tests: usize, - successes: Vec, - failures: Vec, -} - -#[derive(Debug)] -struct TestFailure { - name: String, - error: String, -} - -impl TestReport { - fn register_success(&mut self, name: &str) { - self.num_tests += 1; - self.successes.push(name.to_string()); - } - - fn register_failure(&mut self, name: &str, error: String) { - self.num_tests += 1; - self.failures.push(TestFailure { - name: name.to_string(), - error, - }); - } - - fn finish(&self) { - for success in &self.successes { - println!("✅ {}", success); - } - - for failure in &self.failures { - println!("❌ {}: {}", failure.name, failure.error); - } - - println!( - "{} tests, {} successes, {} failures", - self.num_tests, - self.successes.len(), - self.failures.len() - ); - - if !self.failures.is_empty() { - panic!(); - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestFixture { - name: String, - #[serde(flatten)] - test_case: TestCase, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "task", rename_all = "camelCase")] -enum TestCase { - Verify(VerifyTest), - Refute(RefuteTest), - Build(BuildTest), - ToCID(ToCidTest), -} - -#[derive(Debug, Serialize, Deserialize)] -struct VerifyTest { - inputs: TestInputsTokenAndProofs, - assertions: TestAssertions, -} - -#[derive(Debug, Serialize, Deserialize)] -struct RefuteTest { - inputs: TestInputsTokenAndProofs, - assertions: TestAssertions, - errors: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTest { - inputs: BuildTestInputs, - outputs: BuildTestOutputs, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTest { - inputs: ToCidTestInputs, - outputs: ToCidTestOutputs, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestInputsTokenAndProofs { - token: String, - proofs: HashMap, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertions { - header: TestAssertionsHeader, - payload: TestAssertionsPayload, - signature: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertionsHeader { - alg: Option, - typ: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertionsPayload { - ucv: Option, - iss: Option, - aud: Option, - exp: Option, - // TODO: CAP - // TODO: FCT - prf: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTestInputs { - version: Option, - issuer_base64_key: String, - signature_scheme: String, - audience: Option, - not_before: Option, - expiration: Option, - // TODO CAPABILITIES - // TODO FACTS -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTestOutputs { - token: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTestInputs { - token: String, - hasher: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTestOutputs { - cid: String, -} - -impl TestTask for VerifyTest { - fn run(&self, name: &str, report: &mut TestReport) { - let mut store = store::InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - for (_cid, token) in self.inputs.proofs.iter() { - store - .write(Ipld::Bytes(token.as_bytes().to_vec()), None) - .unwrap(); - } - - let Ok(ucan) = Ucan::::from_str(&self.inputs.token) - else { - report.register_failure(name, "failed to parse token".to_string()); - - return; - }; - - if let Some(alg) = &self.assertions.header.alg { - if ucan.algorithm() != alg { - report.register_failure( - name, - format!( - "expected algorithm to be {}, but was {}", - alg, - ucan.algorithm() - ), - ); - - return; - } - } - - if let Some(typ) = &self.assertions.header.typ { - if ucan.typ() != typ { - report.register_failure( - name, - format!("expected type to be {}, but was {}", typ, ucan.typ()), - ); - - return; - } - } - - if let Some(ucv) = &self.assertions.payload.ucv { - if ucan.version() != ucv { - report.register_failure( - name, - format!("expected version to be {}, but was {}", ucv, ucan.version()), - ); - - return; - } - } - - if let Some(iss) = &self.assertions.payload.iss { - if ucan.issuer() != iss { - report.register_failure( - name, - format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), - ); - - return; - } - } - - if let Some(aud) = &self.assertions.payload.aud { - if ucan.audience() != aud { - report.register_failure( - name, - format!( - "expected audience to be {}, but was {}", - aud, - ucan.audience() - ), - ); - - return; - } - } - - if ucan.expires_at() != self.assertions.payload.exp { - report.register_failure( - name, - format!( - "expected expiration to be {:?}, but was {:?}", - self.assertions.payload.exp, - ucan.expires_at() - ), - ); - - return; - } - - if ucan - .proofs() - .map(|f| f.iter().map(|c| c.to_string()).collect()) - != self.assertions.payload.prf - { - report.register_failure( - name, - format!( - "expected proofs to be {:?}, but was {:?}", - self.assertions.payload.prf, - ucan.proofs() - ), - ); - - return; - } - - let Ok(signature) = serde_json::to_value(ucan.signature()) else { - report.register_failure(name, "failed to serialize signature".to_string()); - - return; - }; - - let Some(signature) = signature.as_str() else { - report.register_failure(name, "expected signature to be a string".to_string()); - - return; - }; - - if signature != self.assertions.signature { - report.register_failure( - name, - format!( - "expected signature to be {}, but was {}", - self.assertions.signature, signature - ), - ); - - return; - } - - if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { - report.register_failure(name, err.to_string()); - - return; - } - } -} - -impl TestTask for RefuteTest { - fn run(&self, name: &str, report: &mut TestReport) { - let mut store = store::InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - for (_cid, token) in self.inputs.proofs.iter() { - store - .write(Ipld::Bytes(token.as_bytes().to_vec()), None) - .unwrap(); - } - - if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) - { - if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { - report.register_failure( - &name, - "expected token to fail validation, but it passed".to_string(), - ); - - return; - } - } - } -} - -impl TestTask for BuildTest { - fn run(&self, _: &str, _: &mut TestReport) { - //TODO: can't assert on signature because of canonicalization issues - } -} - -impl TestTask for ToCidTest { - fn run(&self, name: &str, report: &mut TestReport) { - let ucan = - Ucan::::from_str(&self.inputs.token).unwrap(); - let hasher = match self.inputs.hasher.as_str() { - "SHA2-256" => multihash::Code::Sha2_256, - "BLAKE3-256" => multihash::Code::Blake3_256, - _ => panic!(), - }; - - let Ok(cid) = ucan.to_cid(Some(hasher)) else { - report.register_failure(&name, "failed to convert to CID".to_string()); - - return; - }; - - if cid.to_string() != self.outputs.cid { - report.register_failure( - &name, - format!( - "expected CID to be {}, but was {}", - self.outputs.cid, - cid.to_string() - ), - ); - - return; - } - } -} - -#[test] -fn ucan_0_10_0_conformance_tests() { - let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); - let reader = BufReader::new(fixtures_file); - let fixtures: Vec = serde_json::from_reader(reader).unwrap(); - - let mut report = TestReport::default(); - - for fixture in fixtures { - match fixture.test_case { - TestCase::Verify(test) => test.run(&fixture.name, &mut report), - TestCase::Refute(test) => test.run(&fixture.name, &mut report), - TestCase::Build(test) => test.run(&fixture.name, &mut report), - TestCase::ToCID(test) => test.run(&fixture.name, &mut report), - }; - - report.register_success(&fixture.name); - } - - report.finish(); -} +// use libipld_core::{ipld::Ipld, raw::RawCodec}; +// use serde::{Deserialize, Serialize}; +// use std::{collections::HashMap, fs::File, io::BufReader}; + +// use ucan::{ +// capability::DefaultCapabilityParser, +// did_verifier::DidVerifierMap, +// store::{self, Store}, +// ucan::Ucan, +// DefaultFact, +// }; +// +// trait TestTask { +// fn run(&self, name: &str, report: &mut TestReport); +// } +// +// #[derive(Debug, Default)] +// struct TestReport { +// num_tests: usize, +// successes: Vec, +// failures: Vec, +// } +// +// #[derive(Debug)] +// struct TestFailure { +// name: String, +// error: String, +// } +// +// impl TestReport { +// fn register_success(&mut self, name: &str) { +// self.num_tests += 1; +// self.successes.push(name.to_string()); +// } +// +// fn register_failure(&mut self, name: &str, error: String) { +// self.num_tests += 1; +// self.failures.push(TestFailure { +// name: name.to_string(), +// error, +// }); +// } +// +// fn finish(&self) { +// for success in &self.successes { +// println!("✅ {}", success); +// } +// +// for failure in &self.failures { +// println!("❌ {}: {}", failure.name, failure.error); +// } +// +// println!( +// "{} tests, {} successes, {} failures", +// self.num_tests, +// self.successes.len(), +// self.failures.len() +// ); +// +// if !self.failures.is_empty() { +// panic!(); +// } +// } +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestFixture { +// name: String, +// #[serde(flatten)] +// test_case: TestCase, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// #[serde(tag = "task", rename_all = "camelCase")] +// enum TestCase { +// Verify(VerifyTest), +// Refute(RefuteTest), +// Build(BuildTest), +// ToCID(ToCidTest), +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct VerifyTest { +// inputs: TestInputsTokenAndProofs, +// assertions: TestAssertions, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct RefuteTest { +// inputs: TestInputsTokenAndProofs, +// assertions: TestAssertions, +// errors: Vec, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTest { +// inputs: BuildTestInputs, +// outputs: BuildTestOutputs, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct ToCidTest { +// inputs: ToCidTestInputs, +// outputs: ToCidTestOutputs, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestInputsTokenAndProofs { +// token: String, +// proofs: HashMap, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertions { +// header: TestAssertionsHeader, +// payload: TestAssertionsPayload, +// signature: String, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertionsHeader { +// alg: Option, +// typ: Option, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertionsPayload { +// ucv: Option, +// iss: Option, +// aud: Option, +// exp: Option, +// // TODO: CAP +// // TODO: FCT +// prf: Option>, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTestInputs { +// version: Option, +// issuer_base64_key: String, +// signature_scheme: String, +// audience: Option, +// not_before: Option, +// expiration: Option, +// // TODO CAPABILITIES +// // TODO FACTS +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTestOutputs { +// token: String, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct ToCidTestInputs { +// token: String, +// hasher: String, +// } +// +// // #[derive(Debug, Serialize, Deserialize)] +// // +// // struct ToCidTestOutputs { +// // cid: String, +// // } +// // +// // impl TestTask for VerifyTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let mut store = store::InMemoryStore::::default(); +// // let did_verifier_map = DidVerifierMap::default(); +// // +// // for (_cid, token) in self.inputs.proofs.iter() { +// // store +// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) +// // .unwrap(); +// // } +// // +// // let Ok(ucan) = Ucan::::from_str(&self.inputs.token) +// // else { +// // report.register_failure(name, "failed to parse token".to_string()); +// // +// // return; +// // }; +// // +// // if let Some(alg) = &self.assertions.header.alg { +// // if ucan.algorithm() != alg { +// // report.register_failure( +// // name, +// // format!( +// // "expected algorithm to be {}, but was {}", +// // alg, +// // ucan.algorithm() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(typ) = &self.assertions.header.typ { +// // if ucan.typ() != typ { +// // report.register_failure( +// // name, +// // format!("expected type to be {}, but was {}", typ, ucan.typ()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(ucv) = &self.assertions.payload.ucv { +// // if ucan.version() != ucv { +// // report.register_failure( +// // name, +// // format!("expected version to be {}, but was {}", ucv, ucan.version()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(iss) = &self.assertions.payload.iss { +// // if ucan.issuer() != iss { +// // report.register_failure( +// // name, +// // format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(aud) = &self.assertions.payload.aud { +// // if ucan.audience() != aud { +// // report.register_failure( +// // name, +// // format!( +// // "expected audience to be {}, but was {}", +// // aud, +// // ucan.audience() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // +// // if ucan.expires_at() != self.assertions.payload.exp { +// // report.register_failure( +// // name, +// // format!( +// // "expected expiration to be {:?}, but was {:?}", +// // self.assertions.payload.exp, +// // ucan.expires_at() +// // ), +// // ); +// // +// // return; +// // } +// // +// // if ucan +// // .proofs() +// // .map(|f| f.iter().map(|c| c.to_string()).collect()) +// // != self.assertions.payload.prf +// // { +// // report.register_failure( +// // name, +// // format!( +// // "expected proofs to be {:?}, but was {:?}", +// // self.assertions.payload.prf, +// // ucan.proofs() +// // ), +// // ); +// // +// // return; +// // } +// // +// // let Ok(signature) = serde_json::to_value(ucan.signature()) else { +// // report.register_failure(name, "failed to serialize signature".to_string()); +// // +// // return; +// // }; +// // +// // let Some(signature) = signature.as_str() else { +// // report.register_failure(name, "expected signature to be a string".to_string()); +// // +// // return; +// // }; +// // +// // if signature != self.assertions.signature { +// // report.register_failure( +// // name, +// // format!( +// // "expected signature to be {}, but was {}", +// // self.assertions.signature, signature +// // ), +// // ); +// // +// // return; +// // } +// // +// // if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { +// // report.register_failure(name, err.to_string()); +// // +// // return; +// // } +// // } +// // } +// // +// // impl TestTask for RefuteTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let mut store = store::InMemoryStore::::default(); +// // let did_verifier_map = DidVerifierMap::default(); +// // +// // for (_cid, token) in self.inputs.proofs.iter() { +// // store +// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) +// // .unwrap(); +// // } +// // +// // if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) +// // { +// // if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { +// // report.register_failure( +// // &name, +// // "expected token to fail validation, but it passed".to_string(), +// // ); +// // +// // return; +// // } +// // } +// // } +// // } +// // +// // impl TestTask for BuildTest { +// // fn run(&self, _: &str, _: &mut TestReport) { +// // //TODO: can't assert on signature because of canonicalization issues +// // } +// // } +// // +// // impl TestTask for ToCidTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let ucan = +// // Ucan::::from_str(&self.inputs.token).unwrap(); +// // let hasher = match self.inputs.hasher.as_str() { +// // "SHA2-256" => multihash::Code::Sha2_256, +// // "BLAKE3-256" => multihash::Code::Blake3_256, +// // _ => panic!(), +// // }; +// // +// // let Ok(cid) = ucan.to_cid(Some(hasher)) else { +// // report.register_failure(&name, "failed to convert to CID".to_string()); +// // +// // return; +// // }; +// // +// // if cid.to_string() != self.outputs.cid { +// // report.register_failure( +// // &name, +// // format!( +// // "expected CID to be {}, but was {}", +// // self.outputs.cid, +// // cid.to_string() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // } +// // +// // #[test] +// // fn ucan_0_10_0_conformance_tests() { +// // let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); +// // let reader = BufReader::new(fixtures_file); +// // let fixtures: Vec = serde_json::from_reader(reader).unwrap(); +// // +// // let mut report = TestReport::default(); +// // +// // for fixture in fixtures { +// // match fixture.test_case { +// // TestCase::Verify(test) => test.run(&fixture.name, &mut report), +// // TestCase::Refute(test) => test.run(&fixture.name, &mut report), +// // TestCase::Build(test) => test.run(&fixture.name, &mut report), +// // TestCase::ToCID(test) => test.run(&fixture.name, &mut report), +// // }; +// // +// // report.register_success(&fixture.name); +// // } +// // +// // report.finish(); +// // } From 1ed78e121737cf3689fc4f4ad37fcb1a345789b3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 23:50:23 -0800 Subject: [PATCH 119/234] Save before reworking Promise --- Cargo.toml | 1 + src/ability/crud/any.rs | 19 +---- src/ability/ucan.rs | 5 +- src/ability/ucan/revoke.rs | 13 ++- src/ability/wasm.rs | 32 +------- src/ability/wasm/module.rs | 75 +++++++++++++++++ src/ability/wasm/run.rs | 147 ++++++++++++++++++++++++++++++++++ src/delegation/delegatable.rs | 2 + src/invocation/promise.rs | 10 ++- src/invocation/resolvable.rs | 2 + src/proof.rs | 1 + src/proof/error.rs | 45 +++++++++++ src/proof/parentful.rs | 27 +++++++ src/proof/parentless.rs | 22 ++++- src/proof/parents.rs | 14 ++++ src/proof/prove.rs | 23 +++++- src/proof/same.rs | 100 +++++++++++------------ src/time.rs | 6 +- 18 files changed, 437 insertions(+), 107 deletions(-) create mode 100644 src/ability/wasm/module.rs create mode 100644 src/ability/wasm/run.rs create mode 100644 src/proof/error.rs diff --git a/Cargo.toml b/Cargo.toml index 34115ea0..4f2a9d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ anyhow = "1.0.75" aquamarine = { version = "0.5", optional = true } async-signature = "0.4.0" async-trait = "0.1.73" +base64 = "0.21" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" # FIXME attempt to remove diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 6554c30f..e805705b 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,23 +1,15 @@ use crate::{ ability::command::Command, - proof::{ - parentless::NoParents, - same::{CheckSame, OptionalFieldErr}, - }, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -#[cfg(target_arch = "wasm32")] -use crate::{ipld, proof::same::OptionalFieldReason}; - #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -// NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Builder { @@ -32,15 +24,10 @@ impl Command for Builder { impl NoParents for Builder {} impl CheckSame for Builder { - type Error = OptionalFieldErr; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri - .check_same(&proof.uri) - .map_err(|err| OptionalFieldErr { - field: "uri".into(), - err, - }) + self.uri.check_same(&proof.uri) } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 1987e8a1..0e3daaad 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,3 +1,4 @@ -pub mod proxy; +//! Abilities for and about UCANs themselves + pub mod revoke; -// FIXME pub mod revoke; +// FIXME pub mod proxy; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index aa13e358..5316f80d 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, invocation::{Promise, Resolvable}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; @@ -24,6 +25,8 @@ impl Command for Generic { /// The fully resolved variant: ready to execute. pub type Ready = Generic; +impl NoParents for Ready {} + impl Delegatable for Ready { type Builder = Builder; } @@ -35,6 +38,14 @@ impl Resolvable for Ready { /// A variant with some fields waiting to be set. pub type Builder = Generic>; +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.ucan.check_same(&proof.ucan).map_err(|_| ()) + } +} + impl From for Builder { fn from(resolved: Ready) -> Builder { Builder { @@ -63,7 +74,7 @@ impl From for arguments::Named { } } -/// A variant where arguments may be [`Promise`]s +/// A variant where arguments may be [`Promise`]s. pub type Promised = Generic>; impl From for Promised { diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 07e8d5cd..3aa1e67b 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,30 +1,4 @@ -use super::command::Command; -use crate::proof::{parentless::NoParents, same::CheckSame}; -use libipld_core::{ipld::Ipld, link::Link}; +//! [WebAssembly](https://webassembly.org/) abilities -#[derive(Debug, Clone, PartialEq)] -pub struct Run { - pub module: Module, - pub function: String, - pub args: Vec, -} - -// FIXME -#[derive(Debug, Clone, PartialEq)] -pub enum Module { - Inline(Vec), - Cid(Link>), -} - -impl Command for Run { - const COMMAND: &'static str = "wasm/run"; -} - -impl NoParents for Run {} - -impl CheckSame for Run { - type Error = (); // FIXME - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) // FIXME - } -} +pub mod module; +pub mod run; diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs new file mode 100644 index 00000000..7b319b69 --- /dev/null +++ b/src/ability/wasm/module.rs @@ -0,0 +1,75 @@ +//! Wasm module representations + +use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; + +/// Ways to represent a Wasm module in a `wasm/run` payload. +#[derive(Debug, Clone, PartialEq)] +pub enum Module { + // FIXME serialize both as URLs + /// The raw bytes of the Wasm module + /// + /// Encodes as a `data:` URL + Inline(Vec), + + /// A link to the Wasm module + Remote(Link>), +} + +impl From for Ipld { + fn from(module: Module) -> Self { + match module { + Module::Inline(bytes) => Ipld::Bytes(bytes), + Module::Remote(cid) => Ipld::Link(*cid), + } + } +} + +impl TryFrom for Module { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Serialize for Module { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + Module::Remote(link) => link.cid().serialize(serializer), + Module::Inline(bytes) => format!( + "data:application/wasm;base64,{}", + Base64Display::new(bytes.as_ref(), &STANDARD) + ) + .serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Module { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s.starts_with("data:") { + let data = s + .split(',') + .nth(1) + .ok_or_else(|| serde::de::Error::custom("missing base64 data"))?; + + let bytes = STANDARD + .decode(data) + .map_err(|err| serde::de::Error::custom(err))?; + + Ok(Module::Inline(bytes)) + } else { + let cid = Cid::try_from(s).map_err(serde::de::Error::custom)?; + Ok(Module::Remote(Link::new(cid))) + } + } +} diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs new file mode 100644 index 00000000..e699b39c --- /dev/null +++ b/src/ability/wasm/run.rs @@ -0,0 +1,147 @@ +//! Ability to run a Wasm module + +use super::module::Module; +use crate::{ + ability::{arguments, command::Command}, + delegation::Delegatable, + invocation::{Promise, Resolvable}, + proof::{parentless::NoParents, same::CheckSame}, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// The ability to run a Wasm module on the subject's machine +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + /// The Wasm module to run + pub module: Mod, + + /// The function from the module to run + pub function: Fun, + + /// Arguments to pass to the function + pub args: Args, +} + +impl Command for Generic { + const COMMAND: &'static str = "wasm/run"; +} + +/// A variant with all of the required fields filled in +pub type Ready = Generic>; + +impl Delegatable for Ready { + type Builder = Builder; +} + +impl Resolvable for Ready { + type Promised = Promised; +} + +/// A variant meant for delegation, where fields may be omitted +pub type Builder = Generic, Option, Option>>; + +impl NoParents for Builder {} + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut btree = BTreeMap::new(); + if let Some(module) = builder.module { + btree.insert("module".into(), Ipld::from(module)); + } + + if let Some(function) = builder.function { + btree.insert("function".into(), Ipld::String(function)); + } + + if let Some(args) = builder.args { + btree.insert("args".into(), Ipld::List(args)); + } + + arguments::Named(btree) + } +} + +impl From for Builder { + fn from(ready: Ready) -> Builder { + Builder { + module: Some(ready.module), + function: Some(ready.function), + args: Some(ready.args), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Ready { + module: b.module.ok_or(())?, + function: b.function.ok_or(())?, + args: b.args.ok_or(())?, + }) + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(module) = &self.module { + if module != proof.module.as_ref().unwrap() { + return Err(()); + } + } + + if let Some(function) = &self.function { + if function != proof.function.as_ref().unwrap() { + return Err(()); + } + } + + if let Some(args) = &self.args { + if args != proof.args.as_ref().unwrap() { + return Err(()); + } + } + + Ok(()) + } +} + +/// A variant meant for linking together invocations with promises +pub type Promised = Generic, Promise, Promise>>; + +impl From for Promised { + fn from(ready: Ready) -> Self { + Promised { + module: Promise::from(ready.module), + function: Promise::from(ready.function), + args: Promise::from(ready.args), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(promised: Promised) -> Result { + Ok(Ready { + module: promised.module.try_resolve().map_err(|_| ())?, + function: promised.function.try_resolve().map_err(|_| ())?, + args: promised.args.try_resolve().map_err(|_| ())?, + }) + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + arguments::Named::from_iter([ + ("module".into(), promised.module.into()), + ("function".into(), promised.function.into()), + ("args".into(), promised.args.into()), + ]) + } +} diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index dcb733d9..f6368a84 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,7 +1,9 @@ use crate::ability::arguments; +// FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more + /// FIXME require CheckSame? type Builder: TryInto + From + Into; } diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index f2dcfa0a..520cddda 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -11,8 +11,16 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { + /// The fulfilled (resolved) value. Fulfilled(T), - Pending(Selector), + + // Rejected(E) + // + /// A deferred value and its associated [`Selector`]. + /// + /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] + /// and substitute into the value. + Pending(Selector), // FIXME shodu there be FulfilledOk and FulfilledErr? If so, why cover al branches here? } #[cfg(target_arch = "wasm32")] diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index d9570d98..1ce955a3 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -3,3 +3,5 @@ use crate::ability::arguments; pub trait Resolvable: Sized { type Promised: TryInto + From + Into; } + +// NOTE Promised into args should cover all of the values diff --git a/src/proof.rs b/src/proof.rs index 122ca024..dbe2b43a 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,6 +1,7 @@ //! Proof chains, checking, and utilities pub mod checkable; +pub mod error; pub mod parentful; pub mod parentless; pub mod parents; diff --git a/src/proof/error.rs b/src/proof/error.rs new file mode 100644 index 00000000..6b615c8c --- /dev/null +++ b/src/proof/error.rs @@ -0,0 +1,45 @@ +//! Standatd error types for delegation checking. + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +/// An error for when values are unequal. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[error("unequal")] +pub struct Unequal {} + +/// A generic error for when two fields are unequal. +#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum OptionalFieldError { + /// A required field is missing. + /// + /// For example, when its proof has a vaue, but the target does not. + #[error("missing")] + Missing, + + /// A field is present but has a different value in its proof + #[error("unequal")] + Unequal, +} + +impl From for OptionalFieldError { + fn from(_: Unequal) -> Self { + OptionalFieldError::Unequal + } +} + +impl TryFrom for Unequal { + type Error = OptionalFieldError; + + fn try_from(e: OptionalFieldError) -> Result { + match e { + OptionalFieldError::Unequal => Ok(Unequal {}), + _ => Err(e), + } + } +} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 5bf611ce..340b45f4 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -1,3 +1,5 @@ +//! Utilities for working with abilties that *do* have a delegation hirarchy. + use super::{ internal::Checker, parents::CheckParents, @@ -6,18 +8,43 @@ use super::{ }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use thiserror::Error; +/// The possible cases for an [ability][crate::ability]'s +/// [Delegation][crate::delegation::Delegation] chain when +/// it has parent abilities (a hierarchy). +/// +/// This type is generally not used directly, but rather is +/// called in the plumbing of the library. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentful { + /// The "top" ability (`*`) Any, + + /// All possible parents for the ability. Parents(T::Parents), + + /// The (invokable) ability itself. This(T), } +/// Error cases when checking proofs (including parents) +#[derive(Debug, Error, PartialEq)] pub enum ParentfulError { + /// The `cmd` field was more powerful than the proof. + /// + /// i.e. it behaves like moving "down" the delegation chain not "up" CommandEscelation, + + /// The `args` field was more powerful than the proof. ArgumentEscelation(ArgErr), + + /// The parents do not prove the ability. InvalidProofChain(PrfErr), + + /// Comparing parents in a delegation chain failed. + /// + /// The specific comparison error is captured in the `ParErr`. InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 08c0e14c..390c99ac 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -1,3 +1,5 @@ +//! Utilities for working with abilties that *don't* have a delegation hirarchy +//! use super::{ checkable::Checkable, internal::Checker, @@ -8,20 +10,38 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::convert::Infallible; +/// The possible cases for an [ability][crate::ability]'s +/// [Delegation][crate::delegation::Delegation] chain when +/// it has no parent abilities (no hierarchy). +/// +/// This type is generally not used directly, but rather is +/// called in the plumbing of the library. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentless { + /// The "top" ability (`*`) Any, + + /// The (invokable) ability itself. This(T), } -// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename +// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? +/// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] pub enum ParentlessError { + /// The `cmd` field was more powerful than the proof. + /// + /// i.e. it behaves like moving "down" the delegation chain not "up" CommandEscelation, + + /// The `args` field was more powerful than the proof ArgumentEscelation(T::Error), } // FIXME better name +/// A helper trait to indicate that a type has no parents. +/// +/// This behaves as an alias for `Checkable::>`. pub trait NoParents {} impl Checkable for T { diff --git a/src/proof/parents.rs b/src/proof/parents.rs index e947c201..5b68889d 100644 --- a/src/proof/parents.rs +++ b/src/proof/parents.rs @@ -1,8 +1,22 @@ +//! Check the parents in an ability hierarchy. + use super::same::CheckSame; +/// Check if the parents of a proof are valid. +/// +/// Note that the top ability (`*`) does not need to be handled separately, +/// as the code from [`CheckParents`] will be lifted into +/// [`Parentful`][super::parentful::Parentful], which knows +/// how to check `*`. pub trait CheckParents: CheckSame { + /// The parents of the hierarchy. + /// + /// Note that `Self` *need not* be included in [`CheckParents::Parents`]. type Parents; + + /// Error checking against [`CheckParents::Parents`]. type ParentError; + // FIXME fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index d9876dcb..34e32de0 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -1,9 +1,16 @@ +//! High-level proof chain checking. + use super::internal::Checker; -// FIXME is it worth locking consumers out with that Checker bound? +/// An internal trait that checks based on the other traits for an ability type. pub trait Prove { + /// The error if the argument is invalid. type ArgumentError; + + /// The error if the proof chain is invalid. type ProofChainError; + + /// The error if the parents are invalid. type ParentsError; fn check( @@ -13,11 +20,23 @@ pub trait Prove { } // FIXME that's a lot of error type params +/// The outcome of a proof check. pub enum Outcome { + /// Success Proven, + + /// Special case for success by checking against `*`. ProvenByAny, + + /// An error in the command chain. + CommandEscelation, + + /// An error in the argument chain. ArgumentEscelation(ArgErr), + + /// An error in the proof chain. InvalidProofChain(ChainErr), + + /// An error in the parents. InvalidParents(ParentErr), - CommandEscelation, } diff --git a/src/proof/same.rs b/src/proof/same.rs index 22f2e060..2e295b68 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,56 +1,52 @@ -use crate::did::Did; -use core::fmt; -use serde::{Deserialize, Serialize}; -use thiserror::Error; +//! Check the delegation proof against another instance of the same type -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use super::error::{OptionalFieldError, Unequal}; +use crate::did::Did; +/// Trait for checking if a proof of the same type is equally or less restrictive. +/// +/// # Example +/// +/// ```rust +/// # use ucan::proof::same::CheckSame; +/// # use ucan::did::Did; +/// # +/// struct HelloBuilder { +/// wave_at: Option, +/// } +/// +/// enum HelloError { +/// MissingWaveAt, +/// WeDontTalkTo(Did) +/// } +/// +/// impl CheckSame for HelloBuilder { +/// type Error = HelloError; +/// +/// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +/// if self.wave_at == Some(Did::try_from("did:example:mallory".to_string()).unwrap()) { +/// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); +/// } +/// +/// if let Some(_) = &proof.wave_at { +/// if self.wave_at != proof.wave_at { +/// return Err(HelloError::MissingWaveAt); +/// } +/// } +/// +/// Ok(()) +/// } +/// } pub trait CheckSame { - type Error; + /// Error type describing why a proof was insufficient. + type Error; // FIXME Rename CheckSameError? + /// Check if the proof is equally or less restrictive than the instance. + /// + /// Delegation must always attenuate. If the proof is more restrictive than the instance, + /// it has violated the delegation chain rules. fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } - -// Genereic -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Unequal; - -// FIXME move under error.rs -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))] -pub struct OptionalFieldErr { - pub field: String, - pub err: OptionalFieldReason, -} - -impl fmt::Display for OptionalFieldErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Field {} is {}", self.field, self.err) - } -} - -// FIXME at minimum the name is confusing -#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum OptionalFieldReason { - MissingField, - UnequalValue, -} - -impl fmt::Display for OptionalFieldReason { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - OptionalFieldReason::MissingField => "missing", - OptionalFieldReason::UnequalValue => "unequal", - } - ) - } -} - impl CheckSame for Did { type Error = Unequal; @@ -58,24 +54,24 @@ impl CheckSame for Did { if self.eq(proof) { Ok(()) } else { - Err(Unequal) + Err(Unequal {}) } } } -impl CheckSame for Option { - type Error = OptionalFieldReason; +impl CheckSame for Option { + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match proof { None => Ok(()), Some(proof_) => match self { - None => Err(OptionalFieldReason::MissingField), + None => Err(OptionalFieldError::Missing), Some(self_) => { if self_.eq(proof_) { Ok(()) } else { - Err(OptionalFieldReason::UnequalValue) + Err(OptionalFieldError::Unequal) } } }, diff --git a/src/time.rs b/src/time.rs index 1440e1b0..cd82682f 100644 --- a/src/time.rs +++ b/src/time.rs @@ -172,15 +172,15 @@ impl<'de> Deserialize<'de> for JsTime { } } -/// An error expressing when a time is larger than 2^53 seconds past the Unix epoch +/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch #[derive(Debug, Clone, PartialEq, Eq, Error)] pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53). + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). pub tried: SystemTime, } impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "time out of JsTime (2^53) range: {:?}", self.tried) + write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) } } From c60234f61527df620c502c12d250d340de7a764e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 8 Feb 2024 01:03:57 -0800 Subject: [PATCH 120/234] Fixin' warnings! --- src/ability/arguments.rs | 30 ++- src/ability/crud.rs | 20 +- src/ability/crud/any.rs | 42 ++-- src/ability/crud/mutate.rs | 30 ++- src/ability/crud/parents.rs | 5 +- src/ability/crud/read.rs | 7 +- src/ability/dynamic.rs | 2 +- src/ability/msg.rs | 8 +- src/ability/msg/receive.rs | 8 +- src/ability/msg/send.rs | 99 ++++---- src/ability/ucan/proxy.rs | 40 +--- src/ability/ucan/revoke.rs | 8 +- src/ability/wasm/run.rs | 11 +- src/delegation/condition.rs | 23 +- src/delegation/condition/contains_all.rs | 21 +- src/delegation/condition/contains_any.rs | 20 +- src/delegation/condition/contains_key.rs | 57 +++-- src/delegation/condition/excludes_all.rs | 66 +++++- src/delegation/condition/excludes_key.rs | 48 +++- src/delegation/condition/matches_regex.rs | 24 +- src/delegation/condition/max_length.rs | 23 +- src/delegation/condition/max_number.rs | 22 +- src/delegation/condition/min_length.rs | 23 +- src/delegation/condition/min_number.rs | 22 +- src/delegation/condition/traits.rs | 3 +- src/delegation/payload.rs | 31 ++- src/invocation.rs | 4 +- src/invocation/promise.rs | 200 ++-------------- src/invocation/promise/any.rs | 207 +++++++++++++++++ src/invocation/promise/err.rs | 86 +++++++ src/invocation/promise/js.rs | 123 ++++++++++ src/invocation/promise/ok.rs | 87 +++++++ src/invocation/promise/resolves.rs | 266 ++++++++++++++++++++++ src/ipld.rs | 6 +- src/ipld/cid.rs | 72 +++++- src/lib.rs | 1 + src/nonce.rs | 10 +- src/proof/error.rs | 2 +- src/time.rs | 11 +- src/url.rs | 47 ++++ 40 files changed, 1348 insertions(+), 467 deletions(-) create mode 100644 src/invocation/promise/any.rs create mode 100644 src/invocation/promise/err.rs create mode 100644 src/invocation/promise/js.rs create mode 100644 src/invocation/promise/ok.rs create mode 100644 src/invocation/promise/resolves.rs create mode 100644 src/url.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index a8f81fe0..b8314428 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -21,37 +21,42 @@ use crate::ipld; /// # Examples /// /// ```rust -/// # use ucan::ability::arguments::Named; +/// # use ucan::ability::arguments; /// # use url::Url; /// # use libipld::ipld; /// # /// struct Execute { /// program: Url, -/// args: Named, +/// instructions: arguments::Named, /// } /// /// let ability = Execute { /// program: Url::parse("file://host.name/path/to/exe").unwrap(), -/// args: Named::try_from(ipld!({ -/// "bold": true, -/// "message": "hello world", -/// })).unwrap() +/// instructions: arguments::Named::from_iter([ +/// ("bold".into(), ipld!(true)), +/// ("message".into(), ipld!("hello world")), +/// ]) /// }; /// -/// assert_eq!(ability.args.get("bold"), Some(&ipld!(true))); +/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Named(pub BTreeMap); impl Named { - /// Get the value associated with a key + /// Create a new, empty `Named` instance. + pub fn new() -> Self { + Default::default() + } + + /// Get the value associated with a key. /// /// An alias for [`BTreeMap::insert`]. pub fn get(&self, key: &str) -> Option<&Ipld> { self.0.get(key) } - /// Inserts a key-value pair + /// Inserts a key-value pair. /// /// An alias for [`BTreeMap::insert`]. pub fn insert(&mut self, key: String, value: Ipld) -> Option { @@ -64,6 +69,13 @@ impl Named { pub fn iter(&self) -> impl Iterator { self.0.iter() } + + /// The number of entries in. + /// + /// A wrapper around [`BTreeMap::len`]. + pub fn len(&self) -> usize { + self.0.len() + } } impl Default for Named { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index dda6aa59..58f8845f 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,7 +1,15 @@ -pub mod any; -pub mod create; -pub mod destroy; -pub mod mutate; +mod any; +mod create; +mod destroy; +mod mutate; +mod read; +mod update; + pub mod parents; -pub mod read; -pub mod update; + +pub use any::Any; +pub use create::Create; +pub use destroy::Destroy; +pub use mutate::Mutate; +pub use read::Read; +pub use update::Update; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index e805705b..8fda3432 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,5 +1,6 @@ use crate::{ ability::command::Command, + ipld, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -12,18 +13,18 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Builder { +pub struct Any { #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for Builder { +impl Command for Any { const COMMAND: &'static str = "crud/*"; } -impl NoParents for Builder {} +impl NoParents for Any {} -impl CheckSame for Builder { +impl CheckSame for Any { type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -31,7 +32,7 @@ impl CheckSame for Builder { } } -impl TryFrom for Builder { +impl TryFrom for Any { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -39,22 +40,15 @@ impl TryFrom for Builder { } } -impl From for Ipld { - fn from(builder: Builder) -> Self { +impl From for Ipld { + fn from(builder: Any) -> Self { builder.into() } } -// FIXME -#[derive(Debug, Error)] -pub enum E { - #[error("Some error")] - SomeErrMsg(String), -} - #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); +pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); // FIXME macro this away #[cfg(target_arch = "wasm32")] @@ -69,16 +63,20 @@ impl CrudAny { } pub fn command(&self) -> String { - Builder::COMMAND.to_string() + Any::COMMAND.to_string() } pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - self.0.check_same(&proof.0).map_err(|_| { - OptionalFieldErr { - field: "uri".into(), - err: OptionalFieldReason::MissingField, + if self.uri.is_some() { + if self.uri != proof.uri { + return Err(OptionalFieldError { + field: "uri".into(), + err: OptionalFieldReason::NotEqual, + } + .into()); } - .into() - }) + } + + Ok(()) } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 0e5590ec..8bc3a869 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,52 +1,50 @@ -use super::any; use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct MutateBuilder { +pub struct Mutate { #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for MutateBuilder { +impl Command for Mutate { const COMMAND: &'static str = "crud/mutate"; } -impl From for Ipld { - fn from(mutate: MutateBuilder) -> Self { +impl From for Ipld { + fn from(mutate: Mutate) -> Self { mutate.into() } } -impl TryFrom for MutateBuilder { - type Error = (); // FIXME +impl TryFrom for Mutate { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Checkable for MutateBuilder { - type Hierarchy = Parentful; +impl Checkable for Mutate { + type Hierarchy = Parentful; } -impl CheckSame for MutateBuilder { +impl CheckSame for Mutate { type Error = (); fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -// TODO note to self, this is effectively a partial order -impl CheckParents for MutateBuilder { - type Parents = any::Builder; - type ParentError = (); +impl CheckParents for Mutate { + type Parents = super::Any; + type ParentError = (); // FIXME fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index bd03b9e8..902e28e7 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,12 +1,11 @@ -use super::{any, mutate::MutateBuilder}; use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum Mutable { - Mutate(MutateBuilder), - Any(any::Builder), + Mutate(super::Mutate), + Any(super::Any), } impl CheckSame for Mutable { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 3d8c409c..1a42083d 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,5 @@ -use super::any; use crate::{ - ability::{arguments, command::Command}, + ability::{arguments, command::Command, crud::any::CrudAny}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -78,7 +77,7 @@ impl CheckSame for Read { } impl CheckParents for Read { - type Parents = any::Builder; + type Parents = super::Any; type ParentError = E; fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { @@ -109,7 +108,7 @@ impl CrudRead { self.0.check_same(&proof.0).map_err(Into::into) } - pub fn check_parent(&self, proof: &any::CrudAny) -> Result<(), JsError> { + pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { self.0.check_parent(&proof.0).map_err(Into::into) } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b08c51c8..1db05871 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -103,7 +103,7 @@ impl CheckSame for Dynamic { } self.args.0.iter().try_for_each(|(k, v)| { - if let Some(proof_v) = proof.args.0.get(k) { + if let Some(proof_v) = proof.args.get(k) { if v != proof_v { return Err("arguments::Named mismatch".into()); } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 0f5bdd0f..708d2bf6 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,5 +1,9 @@ //! Message abilities -pub mod any; -pub mod receive; +mod any; +mod receive; + pub mod send; + +pub use any::Any; +pub use receive::Receive; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 07c86aea..9282d6fd 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -8,8 +8,6 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; -use super::any as msg; - #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages /// @@ -62,16 +60,14 @@ impl CheckSame for Receive { } impl CheckParents for Receive { - type Parents = msg::Any; - type ParentError = ::Error; + type Parents = super::Any; + type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&proof.from).map_err(|_| ()) } } -//////////// - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 63c31ca6..c15341f4 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -3,16 +3,19 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + url as url_newtype, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -use super::any as msg; - +/// Helper for creating instances of `msg/send` with the correct shape. +/// +/// This is not generally used directly, unless you want to abstract +/// over all of the `msg/send` variants. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { @@ -49,14 +52,14 @@ pub struct Generic { /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Resolved") +/// sendrun("msg::send::Ready") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} /// /// style sendrun stroke:orange; /// ``` -pub type Resolved = Generic; +pub type Ready = Generic; #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable variant of the `msg/send` ability. @@ -108,20 +111,21 @@ pub type Builder = Generic, Option, Option>; /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Resolved") +/// sendrun("msg::send::Ready") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} /// /// style sendpromise stroke:orange; /// ``` -pub type Promised = Generic, Promise, Promise>; +pub type Promised = + Generic, promise::Resolves, promise::Resolves>; -impl Delegatable for Resolved { +impl Delegatable for Ready { type Builder = Builder; } -impl Resolvable for Resolved { +impl Resolvable for Ready { type Promised = Promised; } @@ -139,21 +143,21 @@ impl From for arguments::Named { } impl From for arguments::Named { - fn from(promised: Promised) -> Self { + fn from(p: Promised) -> Self { arguments::Named(BTreeMap::from_iter([ - ("to".into(), promised.to.map(String::from).into()), - ("from".into(), promised.from.map(String::from).into()), - ("message".into(), promised.message.into()), + ("to".into(), p.to.map(url_newtype::Newtype).into()), + ("from".into(), p.from.map(String::from).into()), + ("message".into(), p.message.into()), ])) } } impl From for Builder { - fn from(awaiting: Promised) -> Self { + fn from(p: Promised) -> Self { Builder { - to: awaiting.to.try_resolve().ok(), - from: awaiting.from.try_resolve().ok(), - message: awaiting.message.try_resolve().ok(), + to: p.to.into(), + from: p.from.into(), + message: p.message.into(), } } } @@ -168,6 +172,7 @@ impl Checkable for Builder { impl CheckSame for Builder { type Error = (); // FIXME better error + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { self.to.check_same(&proof.to).map_err(|_| ())?; self.from.check_same(&proof.from).map_err(|_| ())?; @@ -176,17 +181,16 @@ impl CheckSame for Builder { } impl CheckParents for Builder { - type Parents = msg::Any; - type ParentError = ::Error; + type Parents = super::Any; + type ParentError = ::Error; - // FIXME rename other to proof - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&other.from).map_err(|_| ()) + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&proof.from).map_err(|_| ()) } } -impl From for Builder { - fn from(resolved: Resolved) -> Self { +impl From for Builder { + fn from(resolved: Ready) -> Self { Generic { to: resolved.to.into(), from: resolved.from.into(), @@ -195,36 +199,41 @@ impl From for Builder { } } -impl From for Promised { - fn from(resolved: Resolved) -> Self { - Generic { - to: resolved.to.into(), - from: resolved.from.into(), - message: resolved.message.into(), +impl From for Promised { + fn from(r: Ready) -> Self { + Promised { + to: promise::Resolves::from(Ok(r.to)), + from: promise::Resolves::from(Ok(r.from)), + message: promise::Resolves::from(Ok(r.message)), } } } -impl TryFrom for Resolved { - type Error = (); +impl TryFrom for Ready { + type Error = Promised; - fn try_from(awaiting: Promised) -> Result { - Ok(Generic { - to: awaiting.to.try_resolve().map_err(|_| ())?, - from: awaiting.from.try_resolve().map_err(|_| ())?, - message: awaiting.message.try_resolve().map_err(|_| ())?, - }) + fn try_from(p: Promised) -> Result { + match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { + Ok((to, from, message)) => Ok(Ready { to, from, message }), + Err((to, from, message)) => Err(Promised { to, from, message }), + } } } -impl TryFrom for Resolved { - type Error = (); +impl TryFrom for Ready { + type Error = Builder; + + fn try_from(b: Builder) -> Result { + // Entirely by refernce + if b.to.is_none() || b.from.is_none() || b.message.is_none() { + return Err(b); + } - fn try_from(builder: Builder) -> Result { - Ok(Generic { - to: builder.to.ok_or(())?, - from: builder.from.ok_or(())?, - message: builder.message.ok_or(())?, + // Moves, and unwrap because we checked above instead of 2 clones per line + Ok(Ready { + to: b.to.unwrap(), + from: b.from.unwrap(), + message: b.message.unwrap(), }) } } diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index da916a9c..04eb5c33 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -18,7 +18,7 @@ pub struct Generic { // FIXME should args just be a CID } -pub type Resolved = Generic; +pub type Ready = Generic; pub type Builder = Generic>; pub type Promised = Generic>; @@ -26,12 +26,12 @@ impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; } -impl Delegatable for Resolved { +impl Delegatable for Ready { type Builder = Builder; } -impl From for Builder { - fn from(resolved: Resolved) -> Builder { +impl From for Builder { + fn from(resolved: Ready) -> Builder { Builder { cmd: resolved.cmd, args: Some(resolved.args), @@ -39,11 +39,11 @@ impl From for Builder { } } -impl TryFrom for Resolved { +impl TryFrom for Ready { type Error = (); // FIXME fn try_from(b: Builder) -> Result { - Ok(Resolved { + Ok(Ready { cmd: b.cmd, args: b.args.ok_or(())?, }) @@ -57,31 +57,3 @@ impl From for arguments::Named { args } } - -// // FIXME hmmm need to decide on the exact shape of this -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 5316f80d..07b1df31 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -3,10 +3,10 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -75,12 +75,12 @@ impl From for arguments::Named { } /// A variant where arguments may be [`Promise`]s. -pub type Promised = Generic>; +pub type Promised = Generic>; impl From for Promised { fn from(r: Ready) -> Promised { Promised { - ucan: Promise::Fulfilled(r.ucan), + ucan: Ok(r.ucan).into(), } } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index e699b39c..a6a8cdcc 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -4,7 +4,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -112,14 +112,15 @@ impl CheckSame for Builder { } /// A variant meant for linking together invocations with promises -pub type Promised = Generic, Promise, Promise>>; +pub type Promised = + Generic, promise::Resolves, promise::Resolves>>; impl From for Promised { fn from(ready: Ready) -> Self { Promised { - module: Promise::from(ready.module), - function: Promise::from(ready.function), - args: Promise::from(ready.args), + module: promise::Resolves::from(Ok(ready.module)), + function: promise::Resolves::from(Ok(ready.function)), + args: promise::Resolves::from(Ok(ready.args)), } } } diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 3b3b0c7f..42048dc2 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -10,6 +10,7 @@ pub mod min_length; pub mod min_number; pub mod traits; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use traits::Condition; @@ -44,18 +45,18 @@ impl TryFrom for Common { } impl Condition for Common { - fn validate(&self, ipld: &Ipld) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match self { - Common::ContainsAll(c) => c.validate(ipld), - Common::ContainsAny(c) => c.validate(ipld), - Common::ContainsKey(c) => c.validate(ipld), - Common::ExcludesKey(c) => c.validate(ipld), - Common::ExcludesAll(c) => c.validate(ipld), - Common::MinLength(c) => c.validate(ipld), - Common::MaxLength(c) => c.validate(ipld), - Common::MinNumber(c) => c.validate(ipld), - Common::MaxNumber(c) => c.validate(ipld), - Common::MatchesRegex(c) => c.validate(ipld), + Common::ContainsAll(c) => c.validate(args), + Common::ContainsAny(c) => c.validate(args), + Common::ContainsKey(c) => c.validate(args), + Common::ExcludesKey(c) => c.validate(args), + Common::ExcludesAll(c) => c.validate(args), + Common::MinLength(c) => c.validate(args), + Common::MaxLength(c) => c.validate(args), + Common::MinNumber(c) => c.validate(args), + Common::MaxNumber(c) => c.validate(args), + Common::MatchesRegex(c) => c.validate(args), } } } diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs index 57d71297..b7a8aca8 100644 --- a/src/delegation/condition/contains_all.rs +++ b/src/delegation/condition/contains_all.rs @@ -1,12 +1,21 @@ +//! A [`Condition`] for ensuring a field contains all of a set of values. + use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A condition for ensuring a field contains all of a set of values. +/// +/// This works on lists and maps. Maps will check the values, not keys. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAll { - field: String, - contains_all: Vec, + /// Name of the field to check + pub field: String, + + /// The elements that must be present + pub contains_all: Vec, } impl From for Ipld { @@ -24,10 +33,10 @@ impl TryFrom for ContainsAll { } impl Condition for ContainsAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.contains_all.iter().all(|ipld| array.contains(ipld)), - Ipld::Map(btree) => { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), + Some(Ipld::Map(btree)) => { let vals: Vec<&Ipld> = btree.values().collect(); self.contains_all.iter().all(|ipld| vals.contains(&ipld)) } diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs index dde1212b..5f730a03 100644 --- a/src/delegation/condition/contains_any.rs +++ b/src/delegation/condition/contains_any.rs @@ -1,12 +1,20 @@ +//! A [`Condition`] for ensuring a field contains some of a set of values. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a field contains one or more of a set of values. +/// +/// This works on lists and maps. Maps will check the values, not keys. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAny { - field: String, - contains_any: Vec, + /// Name of the field to check. + pub field: String, + + /// The elements that must be present. + pub contains_any: Vec, } impl From for Ipld { @@ -24,10 +32,10 @@ impl TryFrom for ContainsAny { } impl Condition for ContainsAny { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => array.iter().any(|ipld| self.contains_any.contains(ipld)), - Ipld::Map(btree) => { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), + Some(Ipld::Map(btree)) => { let vals: Vec<&Ipld> = btree.values().collect(); self.contains_any.iter().any(|ipld| vals.contains(&ipld)) } diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 098237db..0a4ff86d 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -1,15 +1,48 @@ +//! A [`Condition`] for ensuring a map contains a key. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a map contains a key. +/// +/// Note that this operates on a key inside the args, not the args themselves. +/// The shape of an [`ability`][crate::ability] is pretermined, so further +/// constraining the top-level argument keys is not necessary. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{contains_key::ContainsKey, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// let cond = ContainsKey{ +/// field: "a".into(), +/// key: "b".into() +/// }; +/// +/// assert!(cond.validate(&args)); +/// +/// // Fails when the key is not present +/// assert!(!ContainsKey { +/// field: "nope".into(), +/// key: "b".into() +/// }.validate(&args)); +/// +/// // Also fails when the input is not a map +/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); +/// assert!(!cond.validate(&list)); +/// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsKey { - field: String, - contains_key: String, + /// Name of the field to check. + pub field: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - with_value: Option, + /// The elements that must be present. + pub key: String, } impl From for Ipld { @@ -27,19 +60,9 @@ impl TryFrom for ContainsKey { } impl Condition for ContainsKey { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Map(map) => { - if let Some(value) = map.get(&self.field) { - if let Some(with_value) = &self.with_value { - value == with_value - } else { - true - } - } else { - false - } - } + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Map(map)) => map.contains_key(&self.key), _ => false, } } diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index 2e94ca3e..ece60444 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -1,12 +1,49 @@ +//! A [`Condition`] for ensuring a field contains none of a set of values. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a field contains none of a set of values. +/// +/// This works on all [`Ipld`] types. For lists and maps, it checks the values, not keys. +/// For the rest, it checks the value itself. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{excludes_all::ExcludesAll, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); +/// let cond = ExcludesAll { +/// field: "a".into(), +/// excludes_all: vec![ipld!(2), ipld!("a")] +/// }; +/// +/// assert!(cond.validate(&args)); +/// +/// // Fails when the values of a map match +/// assert!(!cond.validate(&ipld!({"a": {"b": 2}}).try_into().unwrap())); +/// +/// // Succeeds when the key is not present +/// assert!(ExcludesAll { +/// field: "nope".into(), +/// excludes_all: vec![ipld!(1), ipld!("b")] +/// }.validate(&args)); +/// +/// // Also checks non-maps/non-lists +/// assert!(!cond.validate(&ipld!({"a": 2}).try_into().unwrap())); +/// assert!(cond.validate(&ipld!({"a": "hello world"}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesAll { - field: String, - excludes_all: Vec, + /// Name of the field to check. + pub field: String, + + /// The elements that must not be present. + pub excludes_all: Vec, } impl From for Ipld { @@ -24,14 +61,25 @@ impl TryFrom for ExcludesAll { } impl Condition for ExcludesAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.excludes_all.iter().all(|ipld| !array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.excludes_all.iter().all(|ipld| !vals.contains(&ipld)) + fn validate(&self, args: &arguments::Named) -> bool { + if let Some(ipld) = args.get(&self.field) { + let mut it = self.excludes_all.iter(); + match ipld { + Ipld::Null => it.all(|x| x != ipld), + Ipld::Bool(_) => it.all(|x| x != ipld), + Ipld::Float(_) => it.all(|x| x != ipld), + Ipld::Integer(_) => it.all(|x| x != ipld), + Ipld::Bytes(_) => it.all(|x| x != ipld), + Ipld::String(_) => it.all(|x| x != ipld), + Ipld::Link(_) => it.all(|x| x != ipld), + Ipld::List(array) => it.all(|x| !array.contains(x)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + it.all(|x| !vals.contains(&x)) + } } - _ => false, + } else { + true } } } diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index b12ef89e..c2beba10 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -1,12 +1,48 @@ +//! A [`Condition`] for ensuring a field contains none of a set of keys. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a map excludes a key. +/// +/// Note that this operates on a key inside the args, not the args themselves. +/// The shape of an [`ability`][crate::ability] is pretermined, so further +/// constraining the top-level argument keys is not necessary. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{excludes_key::ExcludesKey, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// let cond = ExcludesKey{ +/// field: "a".into(), +/// key: "b".into() +/// }; +/// +/// assert!(!cond.validate(&args)); +/// +/// // Succeeds when the key is not present +/// assert!(ExcludesKey { +/// field: "yep".into(), +/// key: "b".into() +/// }.validate(&args)); +/// +/// // Also succeeds when the input is not a map +/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); +/// assert!(cond.validate(&list)); +/// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesKey { - field: String, - excludes_key: String, + /// Name of the field to check. + pub field: String, + + /// The key that must not be present. + pub key: String, } impl From for Ipld { @@ -24,10 +60,10 @@ impl TryFrom for ExcludesKey { } impl Condition for ExcludesKey { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Map(map) => map.get(&self.field).is_none(), - _ => false, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Map(map)) => map.contains_key(&self.field), + _ => true, } } } diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs index 02c0f2e4..f6f9f4d6 100644 --- a/src/delegation/condition/matches_regex.rs +++ b/src/delegation/condition/matches_regex.rs @@ -1,13 +1,21 @@ +//! A regular expression [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use regex::Regex; use serde_derive::{Deserialize, Serialize}; +/// A regular expression [`Condition`] +/// +/// This checks a string against a regular expression. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MatchesRegex { - field: String, - matches_regex: Matcher, + /// Name of the field to check + pub field: String, + + /// The minimum length + pub matches_regex: Matcher, } impl From for Ipld { @@ -25,14 +33,15 @@ impl TryFrom for MatchesRegex { } impl Condition for MatchesRegex { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => self.matches_regex.0.is_match(string), + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), _ => false, } } } +/// A newtype wrapper around [`Regex`] #[derive(Debug, Clone)] pub struct Matcher(Regex); @@ -61,10 +70,7 @@ impl<'de> serde::Deserialize<'de> for Matcher { let s: &str = serde::Deserialize::deserialize(deserializer)?; match Regex::new(s) { Ok(regex) => Ok(Matcher(regex)), - Err(_) => { - // FIXME - todo!() - } + Err(_) => Err(serde::de::Error::custom(format!("Invalid regex: {}", s))), } } } diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs index 13fe157c..7bb4ad97 100644 --- a/src/delegation/condition/max_length.rs +++ b/src/delegation/condition/max_length.rs @@ -1,12 +1,21 @@ +//! A max length [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A maximum length [`Condition`] +/// +/// A condition that checks if the length of a string, list, +/// or map is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxLength { - field: String, - max_length: usize, + /// Name of the field to check + pub field: String, + + /// The maximum length + pub max_length: usize, } impl From for Ipld { @@ -24,11 +33,11 @@ impl TryFrom for MaxLength { } impl Condition for MaxLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() <= self.max_length, - Ipld::List(list) => list.len() <= self.max_length, - Ipld::Map(map) => map.len() <= self.max_length, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => string.len() <= self.max_length, + Some(Ipld::List(list)) => list.len() <= self.max_length, + Some(Ipld::Map(map)) => map.len() <= self.max_length, _ => false, } } diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 46327ee0..81d90374 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -1,13 +1,21 @@ +//! A max number [`Condition`]. use super::traits::Condition; -use crate::number::Number; +use crate::{ability::arguments, number::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A maximum number [`Condition`] +/// +/// A condition that checks if the length of an integer +/// or float is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxNumber { - field: String, - max_number: Number, + /// Name of the field to check + pub field: String, + + /// The maximum number + pub max_number: Number, } impl From for Ipld { @@ -25,13 +33,13 @@ impl TryFrom for MaxNumber { } impl Condition for MaxNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.max_number { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Integer(integer)) => match self.max_number { Number::Float(float) => *integer as f64 <= float, Number::Integer(integer) => integer <= integer, }, - Ipld::Float(float) => match self.max_number { + Some(Ipld::Float(float)) => match self.max_number { Number::Float(float) => float <= float, Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests }, diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs index 85dbe1c9..60f4c4ed 100644 --- a/src/delegation/condition/min_length.rs +++ b/src/delegation/condition/min_length.rs @@ -1,12 +1,21 @@ +//! A min length [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A mimimum length [`Condition`] +/// +/// This checks if the length of a string, list, +/// or map is greater than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MinLength { - field: String, - min_length: usize, + /// Name of the field to check + pub field: String, + + /// The minimum length + pub min_length: usize, } impl From for Ipld { @@ -24,11 +33,11 @@ impl TryFrom for MinLength { } impl Condition for MinLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() >= self.min_length, - Ipld::List(list) => list.len() >= self.min_length, - Ipld::Map(map) => map.len() >= self.min_length, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => string.len() >= self.min_length, + Some(Ipld::List(list)) => list.len() >= self.min_length, + Some(Ipld::Map(map)) => map.len() >= self.min_length, _ => false, } } diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index a19ef7c3..7b22a968 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -1,13 +1,21 @@ +//! A min number [`Condition`]. use super::traits::Condition; -use crate::number::Number; +use crate::{ability::arguments, number::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A minimum number [`Condition`] +/// +/// A condition that checks if the length of an integer +/// or float is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MinNumber { - field: String, - min_number: Number, + /// Name of the field to check + pub field: String, + + /// The minimum number + pub min_number: Number, } impl From for Ipld { @@ -25,13 +33,13 @@ impl TryFrom for MinNumber { } impl Condition for MinNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.min_number { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Integer(integer)) => match self.min_number { Number::Float(float) => *integer as f64 >= float, Number::Integer(integer) => integer >= integer, }, - Ipld::Float(float) => match self.min_number { + Some(Ipld::Float(float)) => match self.min_number { Number::Float(float) => float >= float, Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests }, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index f56edc60..1d9b977c 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,5 +1,6 @@ +use crate::ability::arguments; use libipld_core::ipld::Ipld; pub trait Condition: TryFrom + Into { - fn validate(&self, ipld: &Ipld) -> bool; + fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c23371c7..a2c89546 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -89,7 +89,12 @@ impl From> for Ipld { } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { +impl< + 'a, + T: Delegatable + Resolvable + Checkable + Clone + Into, + C: Condition, + > Payload +{ pub fn check( invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, @@ -106,11 +111,11 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let ipld: Ipld = invoked.clone().into(); + let args: arguments::Named = invoked.ability.clone().into(); let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { - match step(&prev, proof, &ipld, now) { + match step(&prev, proof, &args, now) { Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), Outcome::InvalidParents(_) => Err(()), @@ -151,7 +156,7 @@ struct Acc<'a, T: Checkable> { fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, proof: &Payload, - invoked_ipld: &Ipld, + args: &arguments::Named, now: SystemTime, ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types @@ -182,13 +187,17 @@ where // return Err(()); // } - let cond_result = proof.conditions.iter().try_fold((), |_acc, c| { - if c.validate(&invoked_ipld) { - Ok(()) - } else { - Err(()) - } - }); + let cond_result = + proof.conditions.iter().try_fold( + (), + |_acc, c| { + if c.validate(&args) { + Ok(()) + } else { + Err(()) + } + }, + ); if let Err(_) = cond_result { return Outcome::InvalidProofChain(()); diff --git a/src/invocation.rs b/src/invocation.rs index 993fd5d3..d217ca07 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,10 +1,10 @@ mod payload; -mod promise; mod resolvable; mod serializer; +pub mod promise; + pub use payload::{Payload, Unresolved}; -pub use promise::Promise; pub use resolvable::Resolvable; use crate::signature; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 520cddda..c72bc76c 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,187 +1,31 @@ -use crate::ability::arguments; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +//! [UCAN Promise](https://github.com/ucan-wg/promise) -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +// FIXME put entire module behind feature flag -// FIXME move under invocation? +mod any; +mod err; +mod ok; +mod resolves; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Promise { - /// The fulfilled (resolved) value. - Fulfilled(T), - - // Rejected(E) - // - /// A deferred value and its associated [`Selector`]. - /// - /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] - /// and substitute into the value. - Pending(Selector), // FIXME shodu there be FulfilledOk and FulfilledErr? If so, why cover al branches here? -} - -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, PartialEq, Eq)] -#[wasm_bindgen] -pub enum UcanPromiseStatus { - Fulfilled, - Pending, -} - -// FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? -// FIXME consider wrapping in a trait -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, PartialEq)] -#[wasm_bindgen] -pub struct UcanPromise { - status: UcanPromiseStatus, - selector: Option, - value: Option, -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(getter_with_clone)] -pub struct IncoherentPromise(pub UcanPromise); - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Promise { - type Error = IncoherentPromise; - - fn try_from(js: UcanPromise) -> Result { - match js.status { - UcanPromiseStatus::Fulfilled => { - if let Some(val) = &js.value { - return Ok(Promise::Fulfilled(val.clone())); - } - } - UcanPromiseStatus::Pending => { - if let Some(selector) = &js.selector { - return Ok(Promise::Pending(selector.clone())); - } - } - } - - Err(IncoherentPromise(js)) - } -} - -#[cfg(target_arch = "wasm32")] -impl> From> for UcanPromise { - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(val) => UcanPromise { - status: UcanPromiseStatus::Fulfilled, - selector: None, - value: Some(val.into()), - }, - Promise::Pending(cid) => UcanPromise { - status: UcanPromiseStatus::Pending, - selector: Some(cid), - value: None, - }, - } - } -} - -impl Promise { - pub fn map(self, f: F) -> Promise - where - F: FnOnce(T) -> U, - { - match self { - Promise::Fulfilled(t) => Promise::Fulfilled(f(t)), - Promise::Pending(selector) => Promise::Pending(selector), - } - } -} - -impl> From> for arguments::Named { - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(t) => t.into(), - Promise::Pending(selector) => selector.into(), - } - } -} - -impl From for Promise { - fn from(value: T) -> Self { - Promise::Fulfilled(value) - } -} +pub mod js; -impl Promise { - pub fn try_resolve(self) -> Result { - match self { - Promise::Fulfilled(t) => Ok(t), - Promise::Pending(selector) => Err(selector), - } - } -} +pub use any::PromiseAny; +pub use err::PromiseErr; +pub use ok::PromiseOk; +pub use resolves::Resolves; -impl From> for Ipld -where - T: Into, -{ - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(t) => t.into(), - Promise::Pending(selector) => selector.into(), - } - } -} +use serde::{Deserialize, Serialize}; -/// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also -pub enum Selector { - Any { - #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? - any: Cid, - }, - Ok { - #[serde(rename = "ucan/ok")] - ok: Cid, - }, - Err { - #[serde(rename = "ucan/err")] - err: Cid, - }, -} - -impl From for Ipld { - fn from(selector: Selector) -> Self { - selector.into() - } -} - -impl TryFrom for Selector { - type Error = (); - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl From for arguments::Named { - fn from(selector: Selector) -> Self { - let mut btree = BTreeMap::new(); +/// Top-level union of all UCAN Promise options +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Promise { + /// The `await/ok` promise + Ok(PromiseOk), - match selector { - Selector::Any { any } => { - btree.insert("ucan/*".into(), any.into()); - } - Selector::Ok { ok } => { - btree.insert("ucan/ok".into(), ok.into()); - } - Selector::Err { err } => { - btree.insert("ucan/err".into(), err.into()); - } - } + /// The `await/err` promise + Err(PromiseErr), - arguments::Named(btree) - } + /// The `await/*` promise + Any(PromiseAny), } diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs new file mode 100644 index 00000000..3ec42b1b --- /dev/null +++ b/src/invocation/promise/any.rs @@ -0,0 +1,207 @@ +use super::{err::PromiseErr, ok::PromiseOk}; +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{ + de::{DeserializeSeed, Deserializer, Error, Expected, IgnoredAny, MapAccess, Visitor}, + Deserialize, Serialize, Serializer, +}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(untagged)] +pub enum PromiseAny { + /// The fulfilled (resolved) value. + Fulfilled(#[serde(rename = "ucan/ok")] T), + + /// The failure state of a promise. + Rejected(#[serde(rename = "ucan/err")] E), + + /// A deferred value and its associated [`Selector`]. + /// + /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] + /// and substitute into the value. + Pending(#[serde(rename = "await/*")] Cid), +} + +impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Deserialize<'de> for PromiseAny { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct PromiseAnyVisitor(std::marker::PhantomData<(T, E)>); + + impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Visitor<'de> for PromiseAnyVisitor { + type Value = PromiseAny; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a promise") + } + + fn visit_map(self, mut map: M) -> Result, M::Error> + where + M: MapAccess<'de>, + { + if map.size_hint() != Some(1) { + return Err(serde::de::Error::custom("expected a single key")); + } + + let key = map + .next_key::()? + .ok_or(Error::invalid_length(0, &"expected exactly 1 key"))?; + + match key.as_str() { + "ucan/ok" => { + let val = map.next_value()?; + return Ok(PromiseAny::Fulfilled(val)); + } + + "ucan/err" => { + let err = map.next_value()?; + return Ok(PromiseAny::Rejected(err)); + } + + "await/*" => { + let cid = map.next_value()?; + return Ok(PromiseAny::Pending(cid)); + } + + _ => return Err(serde::de::Error::custom("expected a valid PromiseAny")), + } + } + } + + deserializer.deserialize_map(PromiseAnyVisitor(std::marker::PhantomData)) + } +} + +impl PromiseAny { + pub fn map(self, f: F) -> PromiseAny + where + F: FnOnce(T) -> U, + { + match self { + PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(f(val)), + PromiseAny::Rejected(err) => PromiseAny::Rejected(err), + PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + } + } + + pub fn map_err(self, f: F) -> PromiseAny + where + F: FnOnce(E) -> X, + { + match self { + PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(val), + PromiseAny::Rejected(err) => PromiseAny::Rejected(f(err)), + PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl From> for Ipld { + fn from(p: PromiseAny) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseAny +where + T: for<'de> Deserialize<'de>, + E: for<'de> Deserialize<'de>, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl From> for arguments::Named +where + Ipld: From + From, +{ + fn from(p: PromiseAny) -> arguments::Named { + match p { + PromiseAny::Fulfilled(val) => { + arguments::Named::from_iter([("ucan/ok".into(), Ipld::from(val))]) + } + PromiseAny::Rejected(err) => { + arguments::Named::from_iter([("ucan/err".into(), Ipld::from(err))]) + } + PromiseAny::Pending(cid) => { + arguments::Named::from_iter([("await/*".into(), Ipld::from(cid))]) + } + } + } +} + +impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom + for PromiseAny +{ + type Error = (); // FIXME + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if args.len() != 1 { + return Err(()); + } + + if let Some(ipld) = args.get("ucan/ok") { + return Ok(PromiseAny::Fulfilled(ipld.try_into().map_err(|_| ())?)); + } + + if let Some(ipld) = args.get("ucan/err") { + return Ok(PromiseAny::Rejected(ipld.try_into().map_err(|_| ())?)); + } + + if let Some(ipld) = args.get("await/*") { + return match cid::Newtype::try_from(ipld) { + Ok(nt) => Ok(PromiseAny::Pending(nt.cid)), + Err(_) => Err(()), + }; + } + + Err(()) + } +} + +impl From> for PromiseAny { + fn from(p_ok: PromiseOk) -> PromiseAny { + match p_ok { + PromiseOk::Fulfilled(val) => PromiseAny::Fulfilled(val), + PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl From> for PromiseAny { + fn from(p_err: PromiseErr) -> PromiseAny { + match p_err { + PromiseErr::Rejected(err) => PromiseAny::Rejected(err), + PromiseErr::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl TryFrom> for PromiseOk { + type Error = (); // FIXME + + fn try_from(p_any: PromiseAny) -> Result, Self::Error> { + match p_any { + PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), + PromiseAny::Rejected(err) => Err(()), + PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), + } + } +} + +impl TryFrom> for PromiseErr { + type Error = (); // FIXME + + fn try_from(p_any: PromiseAny) -> Result, Self::Error> { + match p_any { + PromiseAny::Fulfilled(val) => Err(()), + PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), + PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), + } + } +} diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs new file mode 100644 index 00000000..f29acfa2 --- /dev/null +++ b/src/invocation/promise/err.rs @@ -0,0 +1,86 @@ +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PromiseErr { + /// The failure state of a promise. + Rejected(E), + + /// The [`Cid`] that is being waited on to return an `{"err": value}` + Pending(#[serde(rename = "await/err")] Cid), +} + +impl PromiseErr { + pub fn try_resolve(self) -> Result> { + match self { + PromiseErr::Rejected(err) => Ok(err), + PromiseErr::Pending(_cid) => Err(self), + } + } + + pub fn map(self, f: F) -> PromiseErr + where + F: FnOnce(E) -> X, + { + match self { + PromiseErr::Rejected(err) => PromiseErr::Rejected(f(err)), + PromiseErr::Pending(cid) => PromiseErr::Pending(cid), + } + } +} + +impl From> for Option { + fn from(p: PromiseErr) -> Option { + match p { + PromiseErr::Rejected(err) => Some(err), + PromiseErr::Pending(_) => None, + } + } +} + +impl From> for Ipld { + fn from(p: PromiseErr) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseErr { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for arguments::Named +where + Ipld: From, +{ + fn from(p: PromiseErr) -> arguments::Named { + match p { + PromiseErr::Rejected(err) => err.into(), + PromiseErr::Pending(cid) => { + arguments::Named::from_iter([("await/err".into(), Ipld::Link(cid))]) + } + } + } +} + +impl> TryFrom for PromiseErr { + type Error = >::Error; + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if let Some(ipld) = args.get("ucan/err") { + if args.len() == 1 { + if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { + return Ok(PromiseErr::Pending(cid)); + } + } + } + + E::try_from(Ipld::from(args)).map(PromiseErr::Rejected) + } +} diff --git a/src/invocation/promise/js.rs b/src/invocation/promise/js.rs new file mode 100644 index 00000000..9f8b9069 --- /dev/null +++ b/src/invocation/promise/js.rs @@ -0,0 +1,123 @@ +use crate::ability::arguments; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +// FIXME +// #[cfg(target_arch = "wasm32")] +// use wasm_bindgen::prelude::*; +// +// #[cfg(target_arch = "wasm32")] +// #[derive(Clone, Debug, PartialEq, Eq)] +// #[wasm_bindgen] +// pub enum UcanPromiseStatus { +// Fulfilled, +// Pending, +// } +// +// // FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? +// // FIXME consider wrapping in a trait +// #[cfg(target_arch = "wasm32")] +// #[derive(Clone, Debug, PartialEq)] +// #[wasm_bindgen] +// pub struct UcanPromise { +// status: UcanPromiseStatus, +// selector: Option, +// value: Option, +// } +// +// #[cfg(target_arch = "wasm32")] +// #[wasm_bindgen(getter_with_clone)] +// pub struct IncoherentPromise(pub UcanPromise); +// +// #[cfg(target_arch = "wasm32")] +// impl TryFrom for Promise { +// type Error = IncoherentPromise; +// +// fn try_from(js: UcanPromise) -> Result { +// match js.status { +// UcanPromiseStatus::Fulfilled => { +// if let Some(val) = &js.value { +// return Ok(Promise::Fulfilled(val.clone())); +// } +// } +// UcanPromiseStatus::Pending => { +// if let Some(selector) = &js.selector { +// return Ok(Promise::Pending(selector.clone())); +// } +// } +// } +// +// Err(IncoherentPromise(js)) +// } +// } +// +// #[cfg(target_arch = "wasm32")] +// impl> From> for UcanPromise { +// fn from(promise: Promise) -> Self { +// match promise { +// Promise::Fulfilled(val) => UcanPromise { +// status: UcanPromiseStatus::Fulfilled, +// selector: None, +// value: Some(val.into()), +// }, +// Promise::Pending(cid) => UcanPromise { +// status: UcanPromiseStatus::Pending, +// selector: Some(cid), +// value: None, +// }, +// } +// } +// } +// +// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. +// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +// #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also +// pub enum Selector { +// Any { +// #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? +// any: Cid, +// }, +// Ok { +// #[serde(rename = "await/ok")] +// ok: Cid, +// }, +// Err { +// #[serde(rename = "await/err")] +// err: Cid, +// }, +// } +// +// impl From for Ipld { +// fn from(selector: Selector) -> Self { +// selector.into() +// } +// } +// +// impl TryFrom for Selector { +// type Error = (); +// +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld).map_err(|_| ()) +// } +// } +// +// impl From for arguments::Named { +// fn from(selector: Selector) -> Self { +// let mut btree = BTreeMap::new(); +// +// match selector { +// Selector::Any { any } => { +// btree.insert("ucan/*".into(), any.into()); +// } +// Selector::Ok { ok } => { +// btree.insert("await/ok".into(), ok.into()); +// } +// Selector::Err { err } => { +// btree.insert("await/err".into(), err.into()); +// } +// } +// +// arguments::Named(btree) +// } +// } diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs new file mode 100644 index 00000000..f9dfb2ca --- /dev/null +++ b/src/invocation/promise/ok.rs @@ -0,0 +1,87 @@ +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt::Debug; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum PromiseOk { + /// The fulfilled (resolved) value. + Fulfilled(T), + + /// The [`Cid`] that is being waited on to return an `{"ok": value}` + Pending(#[serde(rename = "await/ok")] Cid), +} + +impl PromiseOk { + pub fn try_resolve(self) -> Result> { + match self { + PromiseOk::Fulfilled(value) => Ok(value), + PromiseOk::Pending(_cid) => Err(self), + } + } + + pub fn map(self, f: F) -> PromiseOk + where + F: FnOnce(T) -> U, + { + match self { + PromiseOk::Fulfilled(val) => PromiseOk::Fulfilled(f(val)), + PromiseOk::Pending(cid) => PromiseOk::Pending(cid), + } + } +} + +impl From> for Option { + fn from(p: PromiseOk) -> Option { + match p { + PromiseOk::Fulfilled(value) => Some(value), + PromiseOk::Pending(_) => None, + } + } +} + +impl From> for Ipld { + fn from(p: PromiseOk) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseOk { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for arguments::Named +where + Ipld: From, +{ + fn from(p: PromiseOk) -> arguments::Named { + match p { + PromiseOk::Fulfilled(val) => val.into(), + PromiseOk::Pending(cid) => { + arguments::Named::from_iter([("await/ok".into(), Ipld::Link(cid))]) + } + } + } +} + +impl> TryFrom for PromiseOk { + type Error = >::Error; + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if let Some(ipld) = args.get("ucan/ok") { + if args.len() == 1 { + if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { + return Ok(PromiseOk::Pending(cid)); + } + } + } + + T::try_from(Ipld::from(args)).map(PromiseOk::Fulfilled) + } +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs new file mode 100644 index 00000000..8343e524 --- /dev/null +++ b/src/invocation/promise/resolves.rs @@ -0,0 +1,266 @@ +use super::{PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Resolves { + Ok(PromiseOk), + Err(PromiseErr), +} + +impl Resolves { + pub fn try_resolve(self) -> Result> { + match self { + Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), + Resolves::Err(p_err) => p_err.try_resolve().map_err(Resolves::Err), + } + } + + // FIXME replace with variadic macro? + // FIXME docs + pub fn try_resolve_2( + t: Resolves, + u: Resolves, + ) -> Result<(T, U), (Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + { + if t.is_ready() && u.is_ready() { + Ok((t.try_resolve().unwrap(), u.try_resolve().unwrap())) + } else { + Err((t, u)) + } + } + + pub fn try_resolve_3( + t: Resolves, + u: Resolves, + v: Resolves, + ) -> Result<(T, U, V), (Resolves, Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + )) + } else { + Err((t, u, v)) + } + } + + pub fn try_resolve_4( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + ) -> Result<(T, U, V, W), (Resolves, Resolves, Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w)) + } + } + + pub fn try_resolve_5( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + x: Resolves, + ) -> Result< + (T, U, V, W, X), + ( + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + ), + > + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + X: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() && x.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + x.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w, x)) + } + } + + pub fn try_resolve_6( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + x: Resolves, + y: Resolves, + ) -> Result< + (T, U, V, W, X, Y), + ( + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + ), + > + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + X: fmt::Debug, + Y: fmt::Debug, + { + if [ + t.is_ready(), + u.is_ready(), + v.is_ready(), + w.is_ready(), + x.is_ready(), + y.is_ready(), + ] + .iter() + .all(|x| *x) + { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + x.try_resolve().unwrap(), + y.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w, x, y)) + } + } + + pub fn is_ready(&self) -> bool { + match self { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(_) => true, + _ => false, + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(_) => true, + _ => false, + }, + } + } + + pub fn map(self, f: F) -> Resolves + where + F: FnOnce(T) -> U, + { + match self { + Resolves::Ok(p_ok) => Resolves::Ok(p_ok.map(f)), + Resolves::Err(p_err) => Resolves::Err(p_err.map(f)), + } + } +} + +impl From> for Option { + fn from(r: Resolves) -> Option { + match r { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for Resolves { + fn from(result: Result) -> Self { + match result { + Ok(value) => Resolves::Ok(PromiseOk::Fulfilled(value)), + Err(value) => Resolves::Err(PromiseErr::Rejected(value)), + } + } +} + +impl> From> for Ipld { + fn from(resolves: Resolves) -> Ipld { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for Resolves { + fn from(ok: PromiseOk) -> Self { + Resolves::Ok(ok) + } +} + +impl From> for Resolves { + fn from(err: PromiseErr) -> Self { + Resolves::Err(err) + } +} + +impl TryFrom> for PromiseOk { + type Error = PromiseErr; + + fn try_from(resolved: Resolves) -> Result { + match resolved { + Resolves::Ok(ok) => Ok(ok), + Resolves::Err(err) => Err(err), + } + } +} + +impl TryFrom> for PromiseErr { + type Error = PromiseOk; + + fn try_from(resolved: Resolves) -> Result { + match resolved { + Resolves::Ok(ok) => Err(ok), + Resolves::Err(err) => Ok(err), + } + } +} + +impl From> for PromiseAny { + fn from(resolve: Resolves) -> PromiseAny { + match resolve { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(value) => PromiseAny::Fulfilled(value), + PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(err) => PromiseAny::Rejected(err), + PromiseErr::Pending(cid) => PromiseAny::Pending(cid), + }, + } + } +} diff --git a/src/ipld.rs b/src/ipld.rs index 34a3e1e1..11e55325 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,6 +3,7 @@ pub mod cid; use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -34,7 +35,8 @@ use js_sys::{Array, Map, Object, Uint8Array}; /// # /// assert_eq!(wrapped.0, ipld); /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Newtype(pub Ipld); impl From for Newtype { @@ -164,7 +166,7 @@ impl TryFrom for Newtype { } // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(nt) = cid::Newtype::try_from(&js_val) { + if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { return Ok(Newtype(Ipld::Link(nt.into()))); } diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index b8765f86..791cc16b 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,6 +1,9 @@ //! Utilities for [`Cid`]s -use libipld_core::cid::Cid; +use crate::ipld; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -11,11 +14,21 @@ use wasm_bindgen_derive::TryFromJsValue; /// A newtype wrapper around a [`Cid`] /// /// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. -#[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Newtype { - cid: Cid, + pub cid: Cid, +} + +/// A newtype wrapper around a [`Cid`] +/// +/// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. +#[cfg(target_arch = "wasm32")] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct Newtype { + #[wasm_bindgen(skip)] + pub cid: Cid, } #[cfg(target_arch = "wasm32")] @@ -33,17 +46,25 @@ extern "C" { #[wasm_bindgen] impl Newtype { /// Parse a [`Newtype`] from a string - pub fn from_string(cid_string: String) -> Result { - Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + pub fn from_string(cid_string: String) -> Result { + Newtype::try_from(cid_string).map_err(|e| JsError::new(&format!("{}", e))) + } + + pub fn try_from_js_value(js: &JsValue) -> Result { + match &js.as_string() { + Some(s) => Newtype::from_string(s.clone()), + None => Err(JsError::new("Expected a string")), + } } +} +impl Newtype { /// Convert the [`Cid`] to a string pub fn to_string(&self) -> String { self.cid.to_string() } } -#[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = >::Error; @@ -52,16 +73,47 @@ impl TryFrom for Newtype { } } -#[cfg(target_arch = "wasm32")] impl From for Cid { fn from(wrapper: Newtype) -> Self { wrapper.cid } } -#[cfg(target_arch = "wasm32")] impl From for Newtype { fn from(cid: Cid) -> Self { Self { cid } } } + +impl TryFrom for Newtype { + type Error = NotACid; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Link(cid) => Ok(Newtype { cid }), + other => Err(NotACid(other.into())), + } + } +} + +impl TryFrom<&Ipld> for Newtype { + type Error = NotACid; + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Link(cid) => Ok(Newtype { cid: *cid }), + other => Err(NotACid(other.clone().into())), + } + } +} + +// #[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] +#[error("Not a CID: {0:?}")] +pub struct NotACid(pub ipld::Newtype); + +// #[cfg(target_arch = "wasm32")] +// #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] +// #[error("Not a CID: {0:?}")] +// #[wasm_bindgen] +// pub struct NotACid(#[wasm_bindgen(skip)] pub ipld::Newtype); diff --git a/src/lib.rs b/src/lib.rs index c8928529..ab83b718 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub mod receipt; pub mod signature; pub mod task; pub mod time; +pub mod url; // FIXME consider a fact-system // /// The empty fact diff --git a/src/nonce.rs b/src/nonce.rs index 9f3628d9..c2837684 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -192,14 +192,6 @@ impl TryFrom for Nonce { } } -impl TryFrom<&Ipld> for Nonce { - type Error = (); // FIXME - - fn try_from(ipld: &Ipld) -> Result { - TryFrom::try_from(ipld.to_owned()) - } -} - #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] @@ -247,7 +239,7 @@ impl JsNonce { /// # Arguments /// /// * `nonce` - The exact nonce to convert to a [`JsNonce`] - pub fn from_uint8_array(nonce: Box<[u8]>) -> JsNonce { + pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { Nonce::from(arr.to_vec()).into() } diff --git a/src/proof/error.rs b/src/proof/error.rs index 6b615c8c..a36b5208 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -10,7 +10,7 @@ use wasm_bindgen::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[error("unequal")] -pub struct Unequal {} +pub struct Unequal(); /// A generic error for when two fields are unequal. #[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/time.rs b/src/time.rs index cd82682f..37a4b838 100644 --- a/src/time.rs +++ b/src/time.rs @@ -47,10 +47,13 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(sys_time) = SystemTime::deserialize(deserializer) { - match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), + if let Ok(secs) = u64::deserialize(deserializer) { + match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { + None => return Err(serde::de::Error::custom("time out of range for SystemTime")), + Some(sys_time) => match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + }, } } else { Err(serde::de::Error::custom("not a Timestamp")) diff --git a/src/url.rs b/src/url.rs new file mode 100644 index 00000000..a69b78bc --- /dev/null +++ b/src/url.rs @@ -0,0 +1,47 @@ +//! URL utilities + +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +/// A wrapper around [`Url`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use ::url::Url; +/// # use ucan::url; +/// # +/// let url = Url::parse("https://example.com").unwrap(); +/// let wrapped = url::Newtype(url.clone()); +/// // wrapped.some_trait_method(); +/// ``` +/// +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use ::url::Url; +/// # use ucan::url; +/// # +/// # let url = Url::parse("https://example.com").unwrap(); +/// # let wrapped = url::Newtype(url.clone()); +/// # +/// assert_eq!(wrapped.0, url); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Newtype(pub Url); + +impl From for Ipld { + fn from(newtype: Newtype) -> Self { + newtype.into() + } +} + +impl TryFrom for Newtype { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} From 4c64c7ab942cbf33ed593e0914c9edd2b762572c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 8 Feb 2024 22:59:58 -0800 Subject: [PATCH 121/234] What a saga! --- src/ability.rs | 2 + src/ability/arguments.rs | 116 ++++++++--- src/ability/crud.rs | 55 +++++- src/ability/crud/any.rs | 105 +++++----- src/ability/crud/create.rs | 32 +-- src/ability/crud/destroy.rs | 18 +- src/ability/crud/error.rs | 28 +++ src/ability/crud/js.rs | 67 +++++++ src/ability/crud/mutate.rs | 73 ++++++- src/ability/crud/parents.rs | 63 +++++- src/ability/crud/read.rs | 228 ++++++++++++++++------ src/ability/crud/update.rs | 72 ++++--- src/ability/msg/any.rs | 6 +- src/ability/msg/receive.rs | 2 + src/ability/msg/send.rs | 13 +- src/ability/ucan/revoke.rs | 17 +- src/ability/wasm/run.rs | 19 +- src/delegation/condition.rs | 2 +- src/delegation/condition/contains_all.rs | 2 +- src/delegation/condition/contains_any.rs | 2 +- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_all.rs | 2 +- src/delegation/condition/excludes_key.rs | 2 +- src/delegation/condition/matches_regex.rs | 2 +- src/delegation/condition/max_length.rs | 2 +- src/delegation/condition/max_number.rs | 2 +- src/delegation/condition/min_length.rs | 2 +- src/delegation/condition/min_number.rs | 2 +- src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 3 +- src/delegation/payload.rs | 8 +- src/invocation/payload.rs | 4 +- src/invocation/promise/any.rs | 19 +- src/invocation/promise/err.rs | 8 +- src/invocation/promise/ok.rs | 8 +- src/invocation/promise/resolves.rs | 16 ++ src/invocation/resolvable.rs | 8 +- src/ipld.rs | 3 +- src/ipld/promised.rs | 177 +++++++++++++++++ src/reader.rs | 31 ++- src/receipt/payload.rs | 4 +- src/task.rs | 2 +- 42 files changed, 956 insertions(+), 275 deletions(-) create mode 100644 src/ability/crud/error.rs create mode 100644 src/ability/crud/js.rs create mode 100644 src/ipld/promised.rs diff --git a/src/ability.rs b/src/ability.rs index fe0363ba..f9547f56 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,4 +1,6 @@ // FIXME feature flag each? +// FIXME ability implementers guide (e.g. serde deny fields) +// pub mod crud; pub mod msg; pub mod ucan; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index b8314428..95a4085b 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -3,6 +3,7 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -23,11 +24,11 @@ use crate::ipld; /// ```rust /// # use ucan::ability::arguments; /// # use url::Url; -/// # use libipld::ipld; +/// # use libipld::{ipld, ipld::Ipld}; /// # /// struct Execute { /// program: Url, -/// instructions: arguments::Named, +/// instructions: arguments::Named, /// } /// /// let ability = Execute { @@ -41,9 +42,9 @@ use crate::ipld; /// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Named(pub BTreeMap); +pub struct Named(pub BTreeMap); -impl Named { +impl Named { /// Create a new, empty `Named` instance. pub fn new() -> Self { Default::default() @@ -52,21 +53,21 @@ impl Named { /// Get the value associated with a key. /// /// An alias for [`BTreeMap::insert`]. - pub fn get(&self, key: &str) -> Option<&Ipld> { + pub fn get(&self, key: &str) -> Option<&T> { self.0.get(key) } /// Inserts a key-value pair. /// /// An alias for [`BTreeMap::insert`]. - pub fn insert(&mut self, key: String, value: Ipld) -> Option { + pub fn insert(&mut self, key: String, value: T) -> Option { self.0.insert(key, value) } /// Gets an iterator over the entries, sorted by key. /// /// A wrapper around [`BTreeMap::iter`]. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -78,28 +79,28 @@ impl Named { } } -impl Default for Named { +impl Default for Named { fn default() -> Self { Named(BTreeMap::new()) } } -impl IntoIterator for Named { - type Item = (String, Ipld); - type IntoIter = std::collections::btree_map::IntoIter; +impl IntoIterator for Named { + type Item = (String, T); + type IntoIter = std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl FromIterator<(String, Ipld)> for Named { - fn from_iter>(iter: T) -> Self { +impl FromIterator<(String, T)> for Named { + fn from_iter>(iter: I) -> Self { Named(iter.into_iter().collect()) } } -impl TryFrom for Named { +impl Deserialize<'de>> TryFrom for Named { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -107,15 +108,26 @@ impl TryFrom for Named { } } -impl From for Ipld { - fn from(arguments: Named) -> Self { +impl From> for Ipld { + fn from(arguments: Named) -> Self { ipld_serde::to_ipld(arguments).unwrap() } } #[cfg(target_arch = "wasm32")] -impl From for Object { - fn from(arguments: Named) -> Self { +impl> From> for Object { + fn from(arguments: Named) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), v.into()).unwrap(); + } + obj + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for Object { + fn from(arguments: Named) -> Self { let obj = Object::new(); for (k, v) in arguments.0 { Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); @@ -127,7 +139,28 @@ impl From for Object { // NOTE saves a few cycles while calling by not cloning // the extra Object fields that we're not going to use #[cfg(target_arch = "wasm32")] -impl From<&Object> for Named { +impl> From<&Object> for Named { + // FIXME probbaly needs to be a try_from + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = T::try_from(entry.get(1)).unwrap().0; // FIXME + (key, value) + }) + .collect::>(); + + Named(btree) + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl From<&Object> for Named { + // FIXME probbaly needs to be a try_from fn from(obj: &Object) -> Self { let btree = Object::entries(obj) .iter() @@ -144,8 +177,22 @@ impl From<&Object> for Named { } #[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(arguments: Named) -> Self { +impl From> for JsValue { + fn from(arguments: Named) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for JsValue { + fn from(arguments: Named) -> Self { arguments .0 .iter() @@ -161,7 +208,20 @@ impl From for JsValue { } #[cfg(target_arch = "wasm32")] -impl TryFrom for Named { +impl TryFrom for Named { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match T::try_from(js) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Named(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Named { type Error = (); // FIXME fn try_from(js: JsValue) -> Result { @@ -172,3 +232,15 @@ impl TryFrom for Named { } } } + +/// Errors for [`arguments::Named`][Named]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum NamedError { + /// A required field was missing. + #[error("Missing arguments::Named field {0}")] + FieldMissing(String), + + /// The value at the named field didn't match the expected value. + #[error("arguments::Named field {0}: value doesn't match")] + FieldValueMismatch(String), +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 58f8845f..b06fb0f7 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,15 +1,54 @@ +//! Abilties for [CRUD] (create, read, update, and destroy) interfaces +//! +//! An overview of the hierarchy can be found on [`crud::Any`][`Any`]. +//! +//! # Wrapping External Resources +//! +//! In most cases, the Subject _is_ the resource being acted +//! on with a CRUD interface. To model external resources directly +//! (i.e. without a URL), generate a unique [`Did`] that represents the +//! specific resource (i.e. the `sub`) directly. This makes the +//! UCAN self-certifying, and can give multiple names to a single +//! resource (which can be important if operating over an open network +//! such as a DHT or gossip). It also provides an abstraction if, +//! for example, the the domain name of a service changes. +//! +//! # `path` Field +//! +//! All variants of CRUD abilities include an *optional* `path` field. +//! +//! There are cases where a Subject acts as a gateway for *external* +//! resources, such as web services or hierarchical file systems. +//! Both of these contain sub-resources expressed via path. +//! If you are issued access to the root, and can attenuate that access to +//! any sub-path, or a single leaf resource. +//! +//! ```js +//! { +//! "sub: "did:example:1234", // <-- e.g. Wraps a web API +//! "cmd": "crud/update", +//! "args": { +//! "path": "/some/path/to/a/resource", +//! }, +//! // ... +//! } +//! ``` +//! +//! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete +//! [`Did`]: crate::did::Did + mod any; -mod create; -mod destroy; mod mutate; -mod read; -mod update; +pub mod create; +pub mod destroy; +pub mod error; pub mod parents; +pub mod read; +pub mod update; pub use any::Any; -pub use create::Create; -pub use destroy::Destroy; pub use mutate::Mutate; -pub use read::Read; -pub use update::Update; + +#[cfg(target_arch = "wasm32")] +pub mod js; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 8fda3432..897527d5 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,21 +1,64 @@ +//! "Any" CRUD ability (superclass of all CRUD abilities) + +use super::error::PathError; use crate::{ ability::command::Command, - ipld, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::Url; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use std::path::PathBuf; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The superclass of all other CRUD abilities. +/// +/// For example, the [`crud::Create`][super::create::Create] ability may +/// be proven by the [`crud::Any`][Any] ability in a delegation chain. +/// +/// It may not be invoked directly, but rather is used as a delegaton proof +/// for other CRUD abilities (see the diagram below). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// read("crud/read") +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style any stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { + /// A an optional path relative to the actor's root. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Any { @@ -25,10 +68,17 @@ impl Command for Any { impl NoParents for Any {} impl CheckSame for Any { - type Error = OptionalFieldError; + type Error = PathError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri) + if let Some(path) = &self.path { + let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } + + Ok(()) } } @@ -45,38 +95,3 @@ impl From for Ipld { builder.into() } } - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); - -// FIXME macro this away -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl CrudAny { - pub fn to_js(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() - } - - pub fn from_js(js_val: JsValue) -> Result { - ipld::Newtype::try_into_jsvalue(js_val).map(CrudAny) - } - - pub fn command(&self) -> String { - Any::COMMAND.to_string() - } - - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - if self.uri.is_some() { - if self.uri != proof.uri { - return Err(OptionalFieldError { - field: "uri".into(), - err: OptionalFieldReason::NotEqual, - } - .into()); - } - } - - Ok(()) - } -} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 5270fb2c..71044525 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,18 +2,17 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use url::Url; +use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::Mutable; +use super::parents::MutableParents; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Create { #[serde(skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, #[serde(skip_serializing_if = "Option::is_none")] pub args: Option>, @@ -30,10 +29,10 @@ impl From for Ipld { } impl TryFrom for Create { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } @@ -43,8 +42,9 @@ impl Checkable for Create { impl CheckSame for Create { type Error = (); // FIXME better error + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.uri == proof.uri { + if self.path == proof.path { Ok(()) } else { Err(()) @@ -53,22 +53,22 @@ impl CheckSame for Create { } impl CheckParents for Create { - type Parents = Mutable; + type Parents = MutableParents; type ParentError = (); fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_uri) = &self.uri { + if let Some(self_path) = &self.path { match other { - Mutable::Any(any) => { - if let Some(proof_uri) = &any.uri { - if self_uri != proof_uri { + MutableParents::Any(any) => { + if let Some(proof_path) = &any.path { + if self_path != proof_path { return Err(()); } } } - Mutable::Mutate(mutate) => { - if let Some(proof_uri) = &mutate.uri { - if self_uri != proof_uri { + MutableParents::Mutate(mutate) => { + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { return Err(()); } } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 6d9bd003..d448a34f 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -2,18 +2,18 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +use std::path::PathBuf; -use super::parents::Mutable; +use super::parents::MutableParents; // Destroy is its own builder #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Destroy { #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Destroy { @@ -27,10 +27,10 @@ impl From for Ipld { } impl TryFrom for Destroy { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } @@ -46,13 +46,13 @@ impl CheckSame for Destroy { } impl CheckParents for Destroy { - type Parents = Mutable; + type Parents = MutableParents; type ParentError = (); fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(_mutate) => Ok(()), // FIXME - Mutable::Any(_any) => Ok(()), // FIXME + MutableParents::Mutate(_mutate) => Ok(()), // FIXME + MutableParents::Any(_any) => Ok(()), // FIXME } } } diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs new file mode 100644 index 00000000..adaf33cc --- /dev/null +++ b/src/ability/crud/error.rs @@ -0,0 +1,28 @@ +use crate::ability::arguments; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +pub enum ProofError { + #[error("An issue with the path field")] + Path(#[from] PathError), + + #[error("An issue with the (inner) arguments field")] + Args(#[from] arguments::NamedError), + + #[error("Proof `args` were expected, but none were present")] + MissingProofArgs, +} + +#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum PathError { + #[error("Path required in proof, but was not present")] + Missing, + + #[error("Proof path did not match")] + Mismatch, +} diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs new file mode 100644 index 00000000..c66f87e4 --- /dev/null +++ b/src/ability/crud/js.rs @@ -0,0 +1,67 @@ +use super::{read, Any}; +use wasm_bindgen::prelude::*; + +/// DOCS? +#[wasm_bindgen] +pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); + +// FIXME macro this away +#[wasm_bindgen] +impl CrudAny { + pub fn into_js(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn try_from_js(js: JsValue) -> Result { + ipld::Newtype::try_from_js(js).map(CrudAny) + } + + pub fn command(&self) -> String { + Any::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { + if self.path.is_some() { + if self.path != proof.path { + return Err(OptionalFieldError { + field: "path".into(), + err: OptionalFieldReason::NotEqual, + } + .into()); + } + } + + Ok(()) + } +} + +#[wasm_bindgen] +pub struct CrudRead(#[wasm_bindgen(skip)] pub read::Ready); + +#[wasm_bindgen] +impl CrudRead { + pub fn to_jsvalue(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_jsvalue(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + } + + pub fn command(&self) -> String { + Read::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(Into::into) + } + + // FIXME more than any + pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { + self.0.check_parent(&proof.0).map_err(Into::into) + } +} + +// FIXME needs bindings +#[wasm_bindgen] +pub struct CrudReadPromise(#[wasm_bindgen(skip)] pub read::Promised); diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 8bc3a869..32563b6f 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,16 +1,62 @@ +//! The delegation superclass for all mutable CRUD actions. + +use super::error::PathError; use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +use std::path::PathBuf; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegation superclass for all mutable CRUD actions. +/// +/// For example, the [`crud::Create`][super::create::Create] ability may +/// be proven by the [`crud::Mutate`][Mutate] ability in a delegation chain. +/// [`crud::Any`][super::Any] is a suitable proof for [`crud::Mutate`][Mutate], but not vice-versa. +/// +/// It may not be invoked directly, but rather is used as a delegaton proof +/// for other CRUD abilities (see the diagram below). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of mutable CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style mutate stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Mutate { + /// A an optional path relative to the actor's root. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Mutate { @@ -36,17 +82,32 @@ impl Checkable for Mutate { } impl CheckSame for Mutate { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { + type Error = PathError; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(path) = &self.path { + let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } + Ok(()) } } impl CheckParents for Mutate { type Parents = super::Any; - type ParentError = (); // FIXME + type ParentError = PathError; + + fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(path) = &self.path { + let proof_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } - fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 902e28e7..93f223b3 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,22 +1,67 @@ -use crate::proof::same::CheckSame; +//! Flat types for parent checking. +//! +//! Types here turn recursive checking into a since union to check. +//! This only needs to handle "inner" delegation types, not the topmost `*` +//! ability, or the invocable leaves of a delegation hierarchy. + +use crate::proof::{parents::CheckParents, same::CheckSame}; use serde::{Deserialize, Serialize}; +use thiserror::Error; +/// The union of mutable parents. +/// +/// This is helpful as a flat type to put in [`CheckParents::Parents`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub enum Mutable { - Mutate(super::Mutate), +pub enum MutableParents { + /// The `crud/*` ability. Any(super::Any), + + /// The `crud/mutate` ability. + Mutate(super::Mutate), } -impl CheckSame for Mutable { - type Error = (); +impl CheckSame for MutableParents { + type Error = Error; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { - Mutable::Mutate(mutate) => match proof { - Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), - Mutable::Any(_any) => Ok(()), + MutableParents::Mutate(mutate) => match proof { + MutableParents::Mutate(proof_mutate) => mutate + .check_same(proof_mutate) + .map_err(Error::InvalidMutateProof), + + MutableParents::Any(proof_any) => mutate + .check_parent(proof_any) + .map_err(Error::InvalidMutateParent), + }, + + MutableParents::Any(any) => match proof { + MutableParents::Mutate(_) => Err(Error::CommandEscelation), + MutableParents::Any(proof_any) => { + any.check_same(proof_any).map_err(Error::InvalidAnyProof) + } }, - _ => Err(()), } } } + +/// Error cases when checking [`MutableParents`] proofs +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum Error { + /// Error when comparing `crud/*` to another `crud/*`. + #[error(transparent)] + InvalidAnyProof(::Error), + + /// Error when comparing `crud/mutate` to another `crud/mutate`. + #[error(transparent)] + InvalidMutateProof(::Error), + + /// Error when comparing `crud/*` as a proof for `crud/mutate`. + #[error(transparent)] + InvalidMutateParent(::ParentError), + + /// "Expected `crud/*`, but got `crud/mutate`". + #[error("Expected `crud/*`, but got `crud/mutate`")] + CommandEscelation, +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 1a42083d..e29f6b02 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,73 +1,126 @@ +//! The ability to read (fetch) from a resource. +//! +//! * This ability may be invoked when [`Ready`]. +//! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). +//! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. + +use super::error::{PathError, ProofError}; use crate::{ - ability::{arguments, command::Command, crud::any::CrudAny}, + ability::{arguments, command::Command}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::Url; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - -// Read is its own builder +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The CRUD ability to retrieve data from a resource. +/// +/// Note that the delegation [`Builder`] has the exact same +/// fields in this case. +/// +/// # Invocation +/// +/// The executable/dispatchable variant of the `msg/send` ability. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readrun("crud::read::Ready") +/// +/// top --> any +/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// +/// style readrun stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Read { +pub struct Ready { + /// Optional path within the resource. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, + /// Optional additional arugments to pass in the request. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, + pub args: Option>, } -impl Command for Read { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The CRUD ability to retrieve data from a resource. +/// +/// Note that the delegation [`Builder`] has the exact same +/// fields as [`read::Ready`][Ready] in this case. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// +/// style read stroke:orange; +/// ``` +pub type Builder = Ready; + +impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl From for Ipld { - fn from(read: Read) -> Self { - read.into() - } -} - -impl TryFrom for Read { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// FIXME -#[derive(Debug, Error)] -pub enum E { - #[error("Some error")] - SomeErrMsg(String), +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl Checkable for Read { - type Hierarchy = Parentful; -} +impl CheckSame for Ready { + type Error = ProofError; -impl CheckSame for Read { - type Error = E; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(uri) = &self.uri { - if uri != proof.uri.as_ref().unwrap() { - return Err(E::SomeErrMsg("".into())); + if let Some(path) = &self.path { + if path != proof.path.as_ref().unwrap() { + return Err(PathError::Mismatch.into()); } } if let Some(args) = &self.args { - if let Some(proof_args) = &proof.args { - for (k, v) in args.iter() { - if proof_args.get(k) != Some(v) { - return Err(E::SomeErrMsg("".into())); - } + let proof_args = proof.args.as_ref().ok_or(ProofError::MissingProofArgs)?; + for (k, v) in args.iter() { + if proof_args + .get(k) + .ok_or(arguments::NamedError::FieldMissing(k.clone()))? + .ne(v) + { + return Err(arguments::NamedError::FieldValueMismatch(k.clone()).into()); } } } @@ -76,39 +129,84 @@ impl CheckSame for Read { } } -impl CheckParents for Read { +impl CheckParents for Ready { type Parents = super::Any; - type ParentError = E; + type ParentError = PathError; - fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) // FIXME + fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { + if let Some(path) = &self.path { + let crud_any_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + if path != crud_any_path { + return Err(PathError::Mismatch); + } + } + + Ok(()) } } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl CrudRead { - pub fn to_jsvalue(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() +impl From for Ipld { + fn from(ready: Ready) -> Self { + ready.into() } +} +impl TryFrom for Ready { + type Error = SerdeError; - pub fn from_jsvalue(js_val: JsValue) -> Result { - ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } +} - pub fn command(&self) -> String { - Read::COMMAND.to_string() - } +#[cfg_attr(doc, aquamarine::aquamarine)] +/// This ability is used to fetch messages from other actors. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readrun("crud::read::Ready") +/// +/// top --> any +/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// +/// style readpromise stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// Optional path within the resource + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub path: promise::Resolves>, + + /// Optional additional arugments to pass in the request + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub args: promise::Resolves>>>, +} - pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { - self.0.check_same(&proof.0).map_err(Into::into) +impl From for Ipld { + fn from(promised: Promised) -> Self { + promised.into() } +} - pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { - self.0.check_parent(&proof.0).map_err(Into::into) +impl TryFrom for Promised { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 62378050..be8ba511 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,87 +1,97 @@ +use super::parents::MutableParents; use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, + invocation::promise, + ipld::promised::PromisedIpld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use url::Url; - -use super::parents::Mutable; +use std::{collections::BTreeMap, path::PathBuf}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Update { +pub struct Ready { + /// Optional path within the resource. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub args: BTreeMap, + /// Additional arugments to pass in the request. + pub args: arguments::Named, } -impl From for Ipld { - fn from(udpdate: Update) -> Self { +impl From for Ipld { + fn from(udpdate: Ready) -> Self { udpdate.into() } } -impl TryFrom for Update { - type Error = (); // FIXME +impl TryFrom for Ready { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Command for Update { +impl Command for Ready { const COMMAND: &'static str = "crud/update"; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct UpdateBuilder { +pub struct Builder { #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub args: Option>, // FIXME use a type param? } -impl From for Ipld { - fn from(udpdate: UpdateBuilder) -> Self { +impl From for Ipld { + fn from(udpdate: Builder) -> Self { udpdate.into() } } -impl TryFrom for UpdateBuilder { - type Error = (); // FIXME +impl TryFrom for Builder { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Checkable for UpdateBuilder { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for UpdateBuilder { +impl CheckSame for Builder { type Error = (); // FIXME fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri).map_err(|_| ())?; + self.path.check_same(&proof.path).map_err(|_| ())?; self.args.check_same(&proof.args).map_err(|_| ()) } } -impl CheckParents for UpdateBuilder { - type Parents = Mutable; +impl CheckParents for Builder { + type Parents = MutableParents; type ParentError = (); // FIXME fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { - Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), - Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), + MutableParents::Any(any) => self.path.check_same(&any.path).map_err(|_| ()), + MutableParents::Mutate(mutate) => self.path.check_same(&mutate.path).map_err(|_| ()), } } } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub path: promise::Resolves>, + + pub args: promise::Resolves>, +} diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 4d7ece1b..48f8eeab 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize}; use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] -/// The [`Any`] message ability may not be invoked, but it is the superclass of +/// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of /// all other message abilities. /// -/// For example, the [`message::Receive`][super::receive::Receive] ability may -/// be proven by the [`Any`] ability in a delegation chain. +/// For example, the [`msg::Receive`][super::receive::Receive] ability may +/// be proven by the [`msg::Any`][Any] ability in a delegation chain. /// /// # Delegation Hierarchy /// diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 9282d6fd..c8a34ce4 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -44,6 +44,8 @@ pub struct Receive { pub from: Option, } +// FIXME needs promisory version + impl Command for Receive { const COMMAND: &'static str = "msg/send"; } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c15341f4..b7a63b94 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -22,6 +22,8 @@ pub struct Generic { /// The recipient of the message pub to: To, + /// FIXME Builder needs to omit option fields from Serde + /// The sender address of the message /// /// This *may* be a URL (such as an email address). @@ -127,9 +129,16 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { + Ok((to, from, message)) => Ok(Ready { to, from, message }), + Err((to, from, message)) => Err(Promised { to, from, message }), + } + } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); b.to.map(|to| btree.insert("to".into(), to.to_string().into())); @@ -142,7 +151,7 @@ impl From for arguments::Named { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named(BTreeMap::from_iter([ ("to".into(), p.to.map(url_newtype::Newtype).into()), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 07b1df31..bf45a63d 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -6,7 +6,7 @@ use crate::{ invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -33,6 +33,13 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised.ucan.try_resolve() { + Ok(ucan) => Ok(Ready { ucan }), + Err(ucan) => Err(Promised { ucan }), + } + } } /// A variant with some fields waiting to be set. @@ -64,8 +71,8 @@ impl TryFrom for Ready { } } -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { let mut btree = BTreeMap::new(); if let Some(cid) = b.ucan { btree.insert("ucan".into(), cid.into()); @@ -85,8 +92,8 @@ impl From for Promised { } } -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index a6a8cdcc..b32f7880 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -37,6 +37,21 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promise::Resolves::try_resolve_3(promised.module, promised.function, promised.args) { + Ok((module, function, args)) => Ok(Ready { + module, + function, + args, + }), + Err((module, function, args)) => Err(Promised { + module, + function, + args, + }), + } + } } /// A variant meant for delegation, where fields may be omitted @@ -44,7 +59,7 @@ pub type Builder = Generic, Option, Option>>; impl NoParents for Builder {} -impl From for arguments::Named { +impl From for arguments::Named { fn from(builder: Builder) -> Self { let mut btree = BTreeMap::new(); if let Some(module) = builder.module { @@ -137,7 +152,7 @@ impl TryFrom for Ready { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(promised: Promised) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 42048dc2..d6ca60be 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -45,7 +45,7 @@ impl TryFrom for Common { } impl Condition for Common { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match self { Common::ContainsAll(c) => c.validate(args), Common::ContainsAny(c) => c.validate(args), diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs index b7a8aca8..d0882e64 100644 --- a/src/delegation/condition/contains_all.rs +++ b/src/delegation/condition/contains_all.rs @@ -33,7 +33,7 @@ impl TryFrom for ContainsAll { } impl Condition for ContainsAll { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), Some(Ipld::Map(btree)) => { diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs index 5f730a03..46af682f 100644 --- a/src/delegation/condition/contains_any.rs +++ b/src/delegation/condition/contains_any.rs @@ -32,7 +32,7 @@ impl TryFrom for ContainsAny { } impl Condition for ContainsAny { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), Some(Ipld::Map(btree)) => { diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 0a4ff86d..cec31ffa 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -60,7 +60,7 @@ impl TryFrom for ContainsKey { } impl Condition for ContainsKey { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Map(map)) => map.contains_key(&self.key), _ => false, diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index ece60444..99048823 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -61,7 +61,7 @@ impl TryFrom for ExcludesAll { } impl Condition for ExcludesAll { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { if let Some(ipld) = args.get(&self.field) { let mut it = self.excludes_all.iter(); match ipld { diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index c2beba10..550014c6 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -60,7 +60,7 @@ impl TryFrom for ExcludesKey { } impl Condition for ExcludesKey { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Map(map)) => map.contains_key(&self.field), _ => true, diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs index f6f9f4d6..4b7c9426 100644 --- a/src/delegation/condition/matches_regex.rs +++ b/src/delegation/condition/matches_regex.rs @@ -33,7 +33,7 @@ impl TryFrom for MatchesRegex { } impl Condition for MatchesRegex { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), _ => false, diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs index 7bb4ad97..94b0a8f5 100644 --- a/src/delegation/condition/max_length.rs +++ b/src/delegation/condition/max_length.rs @@ -33,7 +33,7 @@ impl TryFrom for MaxLength { } impl Condition for MaxLength { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => string.len() <= self.max_length, Some(Ipld::List(list)) => list.len() <= self.max_length, diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 81d90374..981e63ea 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -33,7 +33,7 @@ impl TryFrom for MaxNumber { } impl Condition for MaxNumber { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Integer(integer)) => match self.max_number { Number::Float(float) => *integer as f64 <= float, diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs index 60f4c4ed..5d137df7 100644 --- a/src/delegation/condition/min_length.rs +++ b/src/delegation/condition/min_length.rs @@ -33,7 +33,7 @@ impl TryFrom for MinLength { } impl Condition for MinLength { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => string.len() >= self.min_length, Some(Ipld::List(list)) => list.len() >= self.min_length, diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index 7b22a968..f6e1e1d6 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -33,7 +33,7 @@ impl TryFrom for MinNumber { } impl Condition for MinNumber { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Integer(integer)) => match self.min_number { Number::Float(float) => *integer as f64 >= float, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 1d9b977c..55c2738d 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -2,5 +2,5 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; pub trait Condition: TryFrom + Into { - fn validate(&self, args: &arguments::Named) -> bool; + fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index f6368a84..7dbbd2bd 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,9 +1,10 @@ use crate::ability::arguments; +use libipld_core::ipld::Ipld; // FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more /// FIXME require CheckSame? - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into>; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index a2c89546..18f07363 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -91,7 +91,7 @@ impl From> for Ipld { // FIXME this likely should move to invocation impl< 'a, - T: Delegatable + Resolvable + Checkable + Clone + Into, + T: Delegatable + Resolvable + Checkable + Clone + Into>, C: Condition, > Payload { @@ -111,7 +111,7 @@ impl< to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let args: arguments::Named = invoked.ability.clone().into(); + let args: arguments::Named = invoked.ability.clone().into(); let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { @@ -156,7 +156,7 @@ struct Acc<'a, T: Checkable> { fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, proof: &Payload, - args: &arguments::Named, + args: &arguments::Named, now: SystemTime, ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types @@ -229,7 +229,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: arguments::Named, + arguments: arguments::Named, #[serde(rename = "cond")] conditions: Vec, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index caf2fffc..adf6dc9b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -116,7 +116,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: arguments::Named, + arguments: arguments::Named, #[serde(rename = "prf")] proofs: Vec, @@ -196,7 +196,7 @@ impl TryFrom for InternalSerializer { // } // } -impl> From> for InternalSerializer { +impl>> From> for InternalSerializer { fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 3ec42b1b..8dbd4920 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -2,7 +2,7 @@ use super::{err::PromiseErr, ok::PromiseOk}; use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{DeserializeSeed, Deserializer, Error, Expected, IgnoredAny, MapAccess, Visitor}, + de::{DeserializeSeed, Deserializer, Error, Expected, MapAccess, Visitor}, Deserialize, Serialize, Serializer, }; use std::fmt; @@ -116,31 +116,28 @@ where } } -impl From> for arguments::Named -where - Ipld: From + From, -{ - fn from(p: PromiseAny) -> arguments::Named { +impl, E: Into> From> for arguments::Named { + fn from(p: PromiseAny) -> arguments::Named { match p { PromiseAny::Fulfilled(val) => { - arguments::Named::from_iter([("ucan/ok".into(), Ipld::from(val))]) + arguments::Named::from_iter([("ucan/ok".into(), val.into())]) } PromiseAny::Rejected(err) => { - arguments::Named::from_iter([("ucan/err".into(), Ipld::from(err))]) + arguments::Named::from_iter([("ucan/err".into(), err.into())]) } PromiseAny::Pending(cid) => { - arguments::Named::from_iter([("await/*".into(), Ipld::from(cid))]) + arguments::Named::from_iter([("await/*".into(), cid.into())]) } } } } -impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom +impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom> for PromiseAny { type Error = (); // FIXME - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if args.len() != 1 { return Err(()); } diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index f29acfa2..623808ec 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -55,11 +55,11 @@ impl TryFrom for PromiseErr { } } -impl> From> for arguments::Named +impl>> From> for arguments::Named where Ipld: From, { - fn from(p: PromiseErr) -> arguments::Named { + fn from(p: PromiseErr) -> arguments::Named { match p { PromiseErr::Rejected(err) => err.into(), PromiseErr::Pending(cid) => { @@ -69,10 +69,10 @@ where } } -impl> TryFrom for PromiseErr { +impl> TryFrom> for PromiseErr { type Error = >::Error; - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if let Some(ipld) = args.get("ucan/err") { if args.len() == 1 { if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index f9dfb2ca..70623609 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -56,11 +56,11 @@ impl TryFrom for PromiseOk { } } -impl> From> for arguments::Named +impl>> From> for arguments::Named where Ipld: From, { - fn from(p: PromiseOk) -> arguments::Named { + fn from(p: PromiseOk) -> arguments::Named { match p { PromiseOk::Fulfilled(val) => val.into(), PromiseOk::Pending(cid) => { @@ -70,10 +70,10 @@ where } } -impl> TryFrom for PromiseOk { +impl> TryFrom> for PromiseOk { type Error = >::Error; - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if let Some(ipld) = args.get("ucan/ok") { if args.len() == 1 { if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 8343e524..c06b458b 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -9,6 +9,22 @@ pub enum Resolves { Err(PromiseErr), } +impl Resolves> { + // FIXME Helpful for serde, maybe extract to a trait? + pub fn resolved_none(&self) -> bool { + match self { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(None) => true, + _ => false, + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(None) => true, + _ => false, + }, + } + } +} + impl Resolves { pub fn try_resolve(self) -> Result> { match self { diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 1ce955a3..bc9103ea 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,7 +1,9 @@ use crate::ability::arguments; +use libipld_core::ipld::Ipld; pub trait Resolvable: Sized { - type Promised: TryInto + From + Into; -} + type Promised: Into>; -// NOTE Promised into args should cover all of the values + // FIXME indeed needed to get teh right err type + fn try_resolve(promised: Self::Promised) -> Result; +} diff --git a/src/ipld.rs b/src/ipld.rs index 11e55325..7571fb44 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,6 +1,7 @@ //! Helpers for working with [`Ipld`] pub mod cid; +pub mod promised; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -53,7 +54,7 @@ impl From for Ipld { #[cfg(target_arch = "wasm32")] impl Newtype { - pub fn try_into_jsvalue>(js_val: JsValue) -> Result + pub fn try_from_js>(js_val: JsValue) -> Result where JsError: From<>::Error>, { diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs new file mode 100644 index 00000000..5f78741e --- /dev/null +++ b/src/ipld/promised.rs @@ -0,0 +1,177 @@ +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// A promise to recursively resolve to an [`Ipld`] value. +/// +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedIpld { + /// Lifted [`Ipld::Null`] + Null, + + /// Lifted [`Ipld::Bool`] + Bool(bool), + + /// Lifted [`Ipld::Integer`] + Integer(i128), + + /// Lifted [`Ipld::Float`] + Float(f64), + + /// Lifted [`Ipld::String`] + String(String), + + /// Lifted [`Ipld::Bytes`] (byte array) + Bytes(Vec), + + /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + List(Vec), + + /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + Map(BTreeMap), + + /// Lifted [`Ipld::Link`] + Link(Cid), + + /// The `await/ok` promise + PromiseOk(Cid), + + /// The `await/err` promise + PromiseErr(Cid), + + /// The `await/*` promise + PromiseAny(Cid), +} + +impl PromisedIpld { + pub fn is_resolved(&self) -> bool { + match self { + PromisedIpld::Null => true, + PromisedIpld::Bool(_) => true, + PromisedIpld::Integer(_) => true, + PromisedIpld::Float(_) => true, + PromisedIpld::String(_) => true, + PromisedIpld::Bytes(_) => true, + PromisedIpld::List(list) => list.iter().all(PromisedIpld::is_resolved), + PromisedIpld::Map(map) => map.values().all(PromisedIpld::is_resolved), + PromisedIpld::Link(_) => true, + PromisedIpld::PromiseOk(_) => false, + PromisedIpld::PromiseErr(_) => false, + PromisedIpld::PromiseAny(_) => false, + } + } + + pub fn is_pending(&self) -> bool { + !self.is_resolved() + } +} + +impl From for PromisedIpld { + fn from(ipld: Ipld) -> Self { + match ipld { + Ipld::Null => PromisedIpld::Null, + Ipld::Bool(b) => PromisedIpld::Bool(b), + Ipld::Integer(i) => PromisedIpld::Integer(i), + Ipld::Float(f) => PromisedIpld::Float(f), + Ipld::String(s) => PromisedIpld::String(s), + Ipld::Bytes(b) => PromisedIpld::Bytes(b), + Ipld::List(list) => { + PromisedIpld::List(list.into_iter().map(PromisedIpld::from).collect()) + } + Ipld::Map(map) => PromisedIpld::Map( + map.into_iter() + .map(|(k, v)| (k, PromisedIpld::from(v))) + .collect(), + ), + Ipld::Link(cid) => PromisedIpld::Link(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseOk) -> Self { + match promise { + PromiseOk::Fulfilled(ipld) => ipld.into(), + PromiseOk::Pending(cid) => PromisedIpld::PromiseOk(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseErr) -> Self { + match promise { + PromiseErr::Rejected(ipld) => ipld.into(), + PromiseErr::Pending(cid) => PromisedIpld::PromiseErr(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseAny) -> Self { + match promise { + PromiseAny::Fulfilled(ipld) => ipld.into(), + PromiseAny::Rejected(ipld) => ipld.into(), + PromiseAny::Pending(cid) => PromisedIpld::PromiseAny(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(resolves: Resolves) -> Self { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: Promise) -> Self { + match promise { + Promise::Ok(p_ok) => p_ok.into(), + Promise::Err(p_err) => p_err.into(), + Promise::Any(p_any) => p_any.into(), + } + } +} + +impl TryFrom for Ipld { + type Error = PromisedIpld; + + fn try_from(p: PromisedIpld) -> Result { + match p { + PromisedIpld::Null => Ok(Ipld::Null), + PromisedIpld::Bool(b) => Ok(Ipld::Bool(b)), + PromisedIpld::Integer(i) => Ok(Ipld::Integer(i)), + PromisedIpld::Float(f) => Ok(Ipld::Float(f)), + PromisedIpld::String(s) => Ok(Ipld::String(s)), + PromisedIpld::Bytes(b) => Ok(Ipld::Bytes(b)), + PromisedIpld::List(ref list) => { + let result: Result, ()> = list.iter().try_fold(vec![], |mut acc, x| { + let ipld = Ipld::try_from(x.clone()).map_err(|_| ())?; + acc.push(ipld); + Ok(acc) + }); + + Ok(Ipld::List(result.map_err(|_| p.clone())?)) + } + PromisedIpld::Map(ref map) => { + let map: Result, ()> = + map.into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + // FIXME non-tail recursion, and maybe even repeated clones + let ipld = Ipld::try_from(v.clone()).map_err(|_| ())?; + acc.insert(k.clone(), ipld); + Ok(acc) + }); + + Ok(Ipld::Map(map.map_err(|_| p)?)) + } + PromisedIpld::Link(cid) => Ok(Ipld::Link(cid)), + PromisedIpld::PromiseOk(_cid) => Err(p), + PromisedIpld::PromiseErr(_cid) => Err(p), + PromisedIpld::PromiseAny(_cid) => Err(p), + } + } +} diff --git a/src/reader.rs b/src/reader.rs index ecb03a97..7ba78b78 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3,9 +3,10 @@ use crate::{ ability::{arguments, command::ToCommand}, delegation::Delegatable, - invocation::Resolvable, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, same::CheckSame}, }; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; /// A struct that attaches an ambient environment to a value @@ -151,7 +152,7 @@ impl Reader { } } -impl> From> for arguments::Named { +impl>> From> for arguments::Named
{ fn from(reader: Reader) -> Self { reader.val.into() } @@ -186,7 +187,7 @@ impl ToCommand for Reader { #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Builder(pub T); -impl> From> for arguments::Named { +impl>> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() } @@ -198,7 +199,7 @@ impl From> for Reader> { } } -impl> Delegatable for Reader { +impl>> Delegatable for Reader { type Builder = Reader>; } @@ -218,7 +219,7 @@ impl> Delegatable for Reader #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Promised(pub T); -impl> From> for arguments::Named { +impl>> From> for arguments::Named { fn from(promised: Promised) -> Self { promised.0.into() } @@ -242,6 +243,22 @@ impl From>> for Reader { } } -impl> Resolvable for Reader { - type Promised = Reader>; +impl Resolvable for Reader +where + Reader: Into>, +{ + type Promised = Reader; + + fn try_resolve(promised: Self::Promised) -> Result { + match T::try_resolve(promised.val) { + Ok(val) => Ok(Reader { + env: promised.env, + val, + }), + Err(val) => Err(Reader { + env: promised.env, + val, + }), + } + } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 81311154..a76939c5 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -11,7 +11,7 @@ pub struct Payload { pub issuer: Did, pub ran: Cid, - pub out: Result, + pub out: Result>, pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, @@ -83,7 +83,7 @@ where issuer: Did, ran: Cid, - out: Result, + out: Result>, next: Vec, // FIXME rename here or in spec? #[serde(rename = "prf")] diff --git a/src/task.rs b/src/task.rs index f9c616db..454b6876 100644 --- a/src/task.rs +++ b/src/task.rs @@ -34,7 +34,7 @@ pub struct Task { pub cmd: String, /// The arguments to the command. - pub args: arguments::Named, + pub args: arguments::Named, } impl TryFrom for Task { From a1d661c8fc19082624fd22b5cbd273192d8f0898 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 14:56:43 -0800 Subject: [PATCH 122/234] Lots of cleanup --- src/ability/arguments.rs | 246 +----------------- src/ability/arguments/named.rs | 312 +++++++++++++++++++++++ src/ability/crud/any.rs | 9 +- src/ability/crud/create.rs | 2 +- src/ability/crud/error.rs | 25 +- src/ability/crud/mutate.rs | 18 +- src/ability/crud/read.rs | 110 +++++--- src/ability/crud/update.rs | 63 ++++- src/ability/dynamic.rs | 4 +- src/ability/js/parentful.rs | 3 +- src/ability/js/parentless.rs | 3 +- src/agent.rs | 2 +- src/delegation.rs | 7 +- src/delegation/condition.rs | 37 ++- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_all.rs | 2 +- src/delegation/condition/excludes_key.rs | 2 +- src/delegation/condition/traits.rs | 4 + src/delegation/payload.rs | 2 +- src/delegation/store.rs | 2 +- src/invocation/promise.rs | 18 ++ src/invocation/promise/any.rs | 4 +- src/invocation/promise/ok.rs | 2 +- src/invocation/promise/resolves.rs | 14 + src/invocation/resolvable.rs | 5 + src/ipld.rs | 208 +-------------- src/ipld/cid.rs | 8 +- src/ipld/enriched.rs | 90 +++++++ src/ipld/newtype.rs | 201 +++++++++++++++ src/ipld/promised.rs | 205 ++++----------- src/proof.rs | 1 + src/proof/error.rs | 4 +- src/proof/util.rs | 15 ++ src/task.rs | 3 + 34 files changed, 919 insertions(+), 714 deletions(-) create mode 100644 src/ability/arguments/named.rs create mode 100644 src/ipld/enriched.rs create mode 100644 src/ipld/newtype.rs create mode 100644 src/proof/util.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 95a4085b..305feadb 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -1,246 +1,10 @@ //! Utilities for ability arguments -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use thiserror::Error; +mod named; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +pub use named::{Named, NamedError}; -#[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Reflect}; +use crate::{invocation::promise::Resolves, ipld}; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - -/// Named arguments -/// -/// Being such a common pattern, but with so few trait implementations, -/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::ability::arguments; -/// # use url::Url; -/// # use libipld::{ipld, ipld::Ipld}; -/// # -/// struct Execute { -/// program: Url, -/// instructions: arguments::Named, -/// } -/// -/// let ability = Execute { -/// program: Url::parse("file://host.name/path/to/exe").unwrap(), -/// instructions: arguments::Named::from_iter([ -/// ("bold".into(), ipld!(true)), -/// ("message".into(), ipld!("hello world")), -/// ]) -/// }; -/// -/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Named(pub BTreeMap); - -impl Named { - /// Create a new, empty `Named` instance. - pub fn new() -> Self { - Default::default() - } - - /// Get the value associated with a key. - /// - /// An alias for [`BTreeMap::insert`]. - pub fn get(&self, key: &str) -> Option<&T> { - self.0.get(key) - } - - /// Inserts a key-value pair. - /// - /// An alias for [`BTreeMap::insert`]. - pub fn insert(&mut self, key: String, value: T) -> Option { - self.0.insert(key, value) - } - - /// Gets an iterator over the entries, sorted by key. - /// - /// A wrapper around [`BTreeMap::iter`]. - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - /// The number of entries in. - /// - /// A wrapper around [`BTreeMap::len`]. - pub fn len(&self) -> usize { - self.0.len() - } -} - -impl Default for Named { - fn default() -> Self { - Named(BTreeMap::new()) - } -} - -impl IntoIterator for Named { - type Item = (String, T); - type IntoIter = std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl FromIterator<(String, T)> for Named { - fn from_iter>(iter: I) -> Self { - Named(iter.into_iter().collect()) - } -} - -impl Deserialize<'de>> TryFrom for Named { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From> for Ipld { - fn from(arguments: Named) -> Self { - ipld_serde::to_ipld(arguments).unwrap() - } -} - -#[cfg(target_arch = "wasm32")] -impl> From> for Object { - fn from(arguments: Named) -> Self { - let obj = Object::new(); - for (k, v) in arguments.0 { - Reflect::set(&obj, &k.into(), v.into()).unwrap(); - } - obj - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for Object { - fn from(arguments: Named) -> Self { - let obj = Object::new(); - for (k, v) in arguments.0 { - Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); - } - obj - } -} - -// NOTE saves a few cycles while calling by not cloning -// the extra Object fields that we're not going to use -#[cfg(target_arch = "wasm32")] -impl> From<&Object> for Named { - // FIXME probbaly needs to be a try_from - fn from(obj: &Object) -> Self { - let btree = Object::entries(obj) - .iter() - .map(|entry| { - let entry = Array::from(&entry); - let key = entry.get(0).as_string().unwrap(); // FIXME - let value = T::try_from(entry.get(1)).unwrap().0; // FIXME - (key, value) - }) - .collect::>(); - - Named(btree) - } -} - -// NOTE saves a few cycles while calling by not cloning -// the extra Object fields that we're not going to use -#[cfg(target_arch = "wasm32")] -impl From<&Object> for Named { - // FIXME probbaly needs to be a try_from - fn from(obj: &Object) -> Self { - let btree = Object::entries(obj) - .iter() - .map(|entry| { - let entry = Array::from(&entry); - let key = entry.get(0).as_string().unwrap(); // FIXME - let value = ipld::Newtype::try_from(entry.get(1)).unwrap().0; - (key, value) - }) - .collect::>(); - - Named(btree) - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for JsValue { - fn from(arguments: Named) -> Self { - arguments - .0 - .iter() - .fold(Map::new(), |map, (ref k, v)| { - map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); - map - }) - .into() - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for JsValue { - fn from(arguments: Named) -> Self { - arguments - .0 - .iter() - .fold(Map::new(), |map, (ref k, v)| { - map.set( - &JsValue::from_str(k), - &JsValue::from(ipld::Newtype(v.clone())), - ); - map - }) - .into() - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Named { - type Error = (); // FIXME - - fn try_from(js: JsValue) -> Result { - match T::try_from(js) { - Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type - } - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Named { - type Error = (); // FIXME - - fn try_from(js: JsValue) -> Result { - match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { - Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type - } - } -} - -/// Errors for [`arguments::Named`][Named]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum NamedError { - /// A required field was missing. - #[error("Missing arguments::Named field {0}")] - FieldMissing(String), - - /// The value at the named field didn't match the expected value. - #[error("arguments::Named field {0}: value doesn't match")] - FieldValueMismatch(String), -} +// FIXME move under invoc::promise? +pub type Promised = Resolves>; diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs new file mode 100644 index 00000000..a63e6ee0 --- /dev/null +++ b/src/ability/arguments/named.rs @@ -0,0 +1,312 @@ +use crate::{invocation::promise, ipld}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Object, Reflect}; + +#[cfg(target_arch = "wasm32")] +use crate::ipld; + +/// Named arguments +/// +/// Being such a common pattern, but with so few trait implementations, +/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::arguments; +/// # use url::Url; +/// # use libipld::{ipld, ipld::Ipld}; +/// # +/// struct Execute { +/// program: Url, +/// instructions: arguments::Named, +/// } +/// +/// let ability = Execute { +/// program: Url::parse("file://host.name/path/to/exe").unwrap(), +/// instructions: arguments::Named::from_iter([ +/// ("bold".into(), ipld!(true)), +/// ("message".into(), ipld!("hello world")), +/// ]) +/// }; +/// +/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Named(pub BTreeMap); + +impl Named { + /// Create a new, empty `Named` instance. + pub fn new() -> Self { + Default::default() + } + + /// Get the value associated with a key. + /// + /// An alias for [`BTreeMap::insert`]. + pub fn get(&self, key: &str) -> Option<&T> { + self.0.get(key) + } + + /// Inserts a key-value pair. + /// + /// An alias for [`BTreeMap::insert`]. + pub fn insert(&mut self, key: String, value: T) -> Option { + self.0.insert(key, value) + } + + /// Gets an iterator over the entries, sorted by key. + /// + /// A wrapper around [`BTreeMap::iter`]. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// The number of entries in. + /// + /// A wrapper around [`BTreeMap::len`]. + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn contains(&self, other: &Named) -> Result<(), NamedError> + where + T: PartialEq, + { + // `other` should usually be smaller than `self` + for (k, other_v) in other.iter() { + if let Some(self_v) = self.get(k) { + if *self_v != *other_v { + return Err(NamedError::FieldValueMismatch(k.clone())); + } + } else { + return Err(NamedError::FieldMissing(k.clone())); + } + } + + Ok(()) + } +} + +impl Default for Named { + fn default() -> Self { + Named(BTreeMap::new()) + } +} + +impl IntoIterator for Named { + type Item = (String, T); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(String, T)> for Named { + fn from_iter>(iter: I) -> Self { + Named(iter.into_iter().collect()) + } +} + +impl Deserialize<'de>> TryFrom for Named { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for Ipld { + fn from(arguments: Named) -> Self { + Ipld::Map( + arguments + .0 + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} + +#[cfg(target_arch = "wasm32")] +impl> From> for Object { + fn from(arguments: Named) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), v.into()).unwrap(); + } + obj + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl> From<&Object> for Named { + // FIXME probbaly needs to be a try_from + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = T::try_from(entry.get(1)).unwrap().0; // FIXME + (key, value) + }) + .collect::>(); + + Named(btree) + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for JsValue { + fn from(arguments: Named) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Named { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match T::try_from(js) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Named(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} + +use crate::invocation::promise::Resolves; + +// impl> TryFrom> for Named { +// type Error = (); +// +// fn try_from(named: Named) -> Result { +// let btree = named +// .0 +// .into_iter() +// .map(|(k, v)| { +// let ipld = v.try_into().map_err(|_| ())?; +// Ok((k, ipld)) +// }) +// .collect::>()?; +// +// Ok(Named(btree)) +// } +// } +// the trait `From>` is not implemented for `Named` + +// impl From> for Named> { +// fn from(named: Named) -> Named> { +// named +// .into_iter() +// .map(|(k, v)| (k, promise::PromiseOk::Fulfilled(v).into())) +// .collect() +// } +// } +// impl> TryFrom> for Named> { +// type Error = Named; +// +// fn try_from(named: Named) -> Result>, Self::Error> { +// named +// .into_iter() +// .try_fold(Named::new(), |mut btree, (k, v)| { +// let ipld = v.try_into().map_err(|_| named.clone())?; +// btree.insert(k, promise::PromiseOk::Fulfilled(ipld).into()); +// Ok(btree) +// }) +// } +// } + +// FIXME abstract over both of these? +impl From> for Named> { + fn from(named: Named) -> Named> { + let btree: BTreeMap> = named + .into_iter() + .map(|(k, v)| { + let promised: ipld::Promised = v.into(); + (k, Resolves::new(promised)) + }) + .collect(); + + Named(btree) + } +} + +impl From> for Named { + fn from(named: Named) -> Named { + let btree: BTreeMap = + named.into_iter().map(|(k, v)| (k, v.into())).collect(); + + Named(btree) + } +} + +impl TryFrom> for Named { + type Error = Named; + + fn try_from(named: Named) -> Result { + // FIXME lots of clone + // FIXME idea: what if they implemet a is_resoled, and then the try_from? + // This lets us check by ref, and then do the conversion and unwrap + named + .iter() + .try_fold(Named::new(), |mut acc, (ref k, v)| { + let ipld = v.clone().try_into().map_err(|_| ())?; + acc.insert(k.to_string(), ipld); + Ok(acc) + }) + .map_err(|()| named.clone()) + } +} + +impl TryFrom>> for Named +where + Ipld: TryFrom, +{ + type Error = Resolves>; + + fn try_from(resolves: Resolves>) -> Result { + resolves + .clone() // FIXME could be a pretty heavy clone + .try_resolve()? + .into_iter() + .try_fold(Named::new(), |mut btree, (k, v)| { + let ipld = v.try_into().map_err(|_| ())?; + btree.insert(k, ipld); + Ok(btree) + }) + .map_err(|_: ()| resolves) + } +} + +/// Errors for [`arguments::Named`][Named]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum NamedError { + /// A required field was missing. + #[error("Missing arguments::Named field {0}")] + FieldMissing(String), + + /// The value at the named field didn't match the expected value. + #[error("arguments::Named field {0}: value doesn't match")] + FieldValueMismatch(String), +} diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 897527d5..077d1d70 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,9 +1,8 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) -use super::error::PathError; use crate::{ ability::command::Command, - proof::{parentless::NoParents, same::CheckSame}, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -68,13 +67,13 @@ impl Command for Any { impl NoParents for Any {} impl CheckSame for Any { - type Error = PathError; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 71044525..4e3e4b22 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -54,7 +54,7 @@ impl CheckSame for Create { impl CheckParents for Create { type Parents = MutableParents; - type ParentError = (); + type ParentError = (); // FIXME fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_path) = &self.path { diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs index adaf33cc..544ca58c 100644 --- a/src/ability/crud/error.rs +++ b/src/ability/crud/error.rs @@ -1,4 +1,4 @@ -use crate::ability::arguments; +use crate::{ability::arguments, proof::error::OptionalFieldError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -8,21 +8,22 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] pub enum ProofError { #[error("An issue with the path field")] - Path(#[from] PathError), + Path(#[from] OptionalFieldError), #[error("An issue with the (inner) arguments field")] Args(#[from] arguments::NamedError), - #[error("Proof `args` were expected, but none were present")] + #[error("Proof has `args`, but none were present on delegate")] MissingProofArgs, } -#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum PathError { - #[error("Path required in proof, but was not present")] - Missing, - - #[error("Proof path did not match")] - Mismatch, -} +// FIXME Is this just OptionalFieldError? +// #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +// pub enum PathError { +// #[error("Path required in proof, but was not present")] +// Missing, +// +// #[error("Proof path did not match")] +// Mismatch, +// } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 32563b6f..ab8c85a3 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,9 +1,11 @@ //! The delegation superclass for all mutable CRUD actions. -use super::error::PathError; use crate::{ ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -82,13 +84,13 @@ impl Checkable for Mutate { } impl CheckSame for Mutate { - type Error = PathError; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } @@ -98,13 +100,13 @@ impl CheckSame for Mutate { impl CheckParents for Mutate { type Parents = super::Any; - type ParentError = PathError; + type ParentError = OptionalFieldError; fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { - let proof_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index e29f6b02..bdc0b764 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -4,11 +4,14 @@ //! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). //! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. -use super::error::{PathError, ProofError}; +use super::error::ProofError; use crate::{ ability::{arguments, command::Command}, - invocation::promise, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + invocation::{promise, promise::Resolves, Resolvable}, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -20,9 +23,6 @@ use wasm_bindgen::prelude::*; #[cfg_attr(doc, aquamarine::aquamarine)] /// The CRUD ability to retrieve data from a resource. /// -/// Note that the delegation [`Builder`] has the exact same -/// fields in this case. -/// /// # Invocation /// /// The executable/dispatchable variant of the `msg/send` ability. @@ -58,17 +58,13 @@ pub struct Ready { #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, - /// Optional additional arugments to pass in the request. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, + /// Additional arugments to pass in the request. + pub args: arguments::Named, } #[cfg_attr(doc, aquamarine::aquamarine)] /// The CRUD ability to retrieve data from a resource. /// -/// Note that the delegation [`Builder`] has the exact same -/// fields as [`read::Ready`][Ready] in this case. -/// /// # Delegation Hierarchy /// /// The hierarchy of CRUD abilities is as follows: @@ -92,52 +88,52 @@ pub struct Ready { /// /// style read stroke:orange; /// ``` -pub type Builder = Ready; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + // FIXME ^^^^^^ rename delegation as a pattern + /// Optional path within the resource. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + /// Additional arugments to pass in the request. + pub args: Option>, +} impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl Checkable for Ready { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Ready { +impl CheckSame for Builder { type Error = ProofError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - if path != proof.path.as_ref().unwrap() { - return Err(PathError::Mismatch.into()); - } - } - - if let Some(args) = &self.args { - let proof_args = proof.args.as_ref().ok_or(ProofError::MissingProofArgs)?; - for (k, v) in args.iter() { - if proof_args - .get(k) - .ok_or(arguments::NamedError::FieldMissing(k.clone()))? - .ne(v) - { - return Err(arguments::NamedError::FieldValueMismatch(k.clone()).into()); - } - } + check_optional(self.path.as_ref(), proof.path.as_ref()) + .map_err(Into::::into)?; + + let args = self.args.as_ref().ok_or(ProofError::MissingProofArgs)?; + if let Some(proof_args) = &proof.args { + args.contains(proof_args).map_err(Into::into) + } else { + Ok(()) } - - Ok(()) } } -impl CheckParents for Ready { +impl CheckParents for Builder { type Parents = super::Any; - type ParentError = PathError; + type ParentError = OptionalFieldError; fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { - let crud_any_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + let crud_any_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != crud_any_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } @@ -192,9 +188,7 @@ pub struct Promised { #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] pub path: promise::Resolves>, - /// Optional additional arugments to pass in the request - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub args: promise::Resolves>>>, + pub args: arguments::Promised, } impl From for Ipld { @@ -210,3 +204,35 @@ impl TryFrom for Promised { ipld_serde::from_ipld(ipld) } } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: promise::PromiseOk::Fulfilled(r.path).into(), + args: promise::PromiseOk::Fulfilled(r.args.into()).into(), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + p.into() + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + match Resolves::try_resolve_2(p.path, p.args) { + Ok((path, promise_args)) => match promise_args.try_into() { + Ok(args) => Ok(Ready { path, args }), + Err(args) => Err(Promised { + args: promise::PromiseOk::Fulfilled(args).into(), + path: promise::PromiseOk::Fulfilled(path).into(), + }), + }, + Err((path, args)) => Err(Promised { path, args }), + } + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index be8ba511..3c8ef326 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,13 +1,12 @@ -use super::parents::MutableParents; +use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, - invocation::promise, - ipld::promised::PromisedIpld, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -45,7 +44,7 @@ pub struct Builder { pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, // FIXME use a type param? + pub args: Option>, } impl From for Ipld { @@ -67,22 +66,26 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = (); // FIXME + type Error = ProofError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.path.check_same(&proof.path).map_err(|_| ())?; - self.args.check_same(&proof.args).map_err(|_| ()) + self.path + .check_same(&proof.path) + .map_err(Into::::into)?; + self.args.check_same(&proof.args).map_err(Into::into) } } impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = (); // FIXME + type ParentError = ProofError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { - MutableParents::Any(any) => self.path.check_same(&any.path).map_err(|_| ()), - MutableParents::Mutate(mutate) => self.path.check_same(&mutate.path).map_err(|_| ()), + MutableParents::Any(any) => self.path.check_same(&any.path).map_err(Into::into), + MutableParents::Mutate(mutate) => { + self.path.check_same(&mutate.path).map_err(Into::into) + } } } } @@ -93,5 +96,41 @@ pub struct Promised { #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] pub path: promise::Resolves>, - pub args: promise::Resolves>, + pub args: arguments::Promised, +} + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: promise::PromiseOk::Fulfilled(r.path).into(), + args: promise::PromiseOk::Fulfilled(r.args.into()).into(), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + p.into() + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME resolve2? + // FIXME lots of clone + Ok(Ready { + path: p.path.clone().try_resolve().map_err(|path| Promised { + path, + args: p.args.clone(), + })?, + + args: p + .args + .clone() + .try_into() + .map_err(|args| Promised { path: p.path, args })?, + }) + } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 1db05871..e4584d13 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -32,7 +32,7 @@ pub struct Dynamic { /// Unstructured, named arguments /// /// The only requirement is that the keys are strings and the values are [`Ipld`] - pub args: arguments::Named, + pub args: arguments::Named, } impl ToCommand for Dynamic { @@ -41,7 +41,7 @@ impl ToCommand for Dynamic { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(dynamic: Dynamic) -> Self { dynamic.args } diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index f7523382..1af4654b 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -6,11 +6,12 @@ use crate::{ reader::Reader, }; use js_sys::{Function, JsString, Map}; +use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename -type WithParents = Reader; +type WithParents = Reader>; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 53dac266..27118b85 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -6,10 +6,11 @@ use crate::{ reader::Reader, }; use js_sys::Function; +use libipld_core::ipld::Ipld; use wasm_bindgen::prelude::*; // FIXME rename -type WithoutParents = Reader; +type WithoutParents = Reader>; /// The configuration object that expresses an ability (without parents) from JS #[derive(Debug, Clone, PartialEq)] diff --git a/src/agent.rs b/src/agent.rs index a0023741..62968faa 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,6 +1,6 @@ use crate::{ ability::command::ToCommand, - delegation::{traits::Condition, Delegatable, Delegation}, + delegation::{condition::Condition, Delegatable, Delegation}, did::Did, invocation::Invocation, proof::parents::CheckParents, diff --git a/src/delegation.rs b/src/delegation.rs index f457be00..3fe71ed2 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,15 +1,14 @@ -mod condition; mod delegatable; mod payload; +pub mod condition; pub mod store; -pub use condition::*; pub use delegatable::Delegatable; pub use payload::Payload; -use condition::traits::Condition; -use store::IndexedStore; +use condition::Condition; +// use store::IndexedStore; use crate::signature; diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index d6ca60be..6b853128 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,22 +1,35 @@ -pub mod contains_all; -pub mod contains_any; -pub mod contains_key; -pub mod excludes_all; -pub mod excludes_key; -pub mod matches_regex; -pub mod max_length; -pub mod max_number; -pub mod min_length; -pub mod min_number; -pub mod traits; +mod contains_all; +mod contains_any; +mod contains_key; +mod excludes_all; +mod excludes_key; +mod matches_regex; +mod max_length; +mod max_number; +mod min_length; +mod min_number; +mod traits; + +pub use contains_all::ContainsAll; +pub use contains_any::ContainsAny; +pub use contains_key::ContainsKey; +pub use excludes_all::ExcludesAll; +pub use excludes_key::ExcludesKey; +pub use matches_regex::MatchesRegex; +pub use max_length::MaxLength; +pub use max_number::MaxNumber; +pub use min_length::MinLength; +pub use min_number::MinNumber; +pub use traits::Condition; use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; -use traits::Condition; +/// The union of the common [`Condition`]s that ship directly with this library. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] +#[allow(missing_docs)] pub enum Common { ContainsAll(contains_all::ContainsAll), ContainsAny(contains_any::ContainsAny), diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index cec31ffa..87b53498 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -13,7 +13,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{contains_key::ContainsKey, traits::Condition}; +/// # use ucan::delegation::{condition::{ContainsKey, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index 99048823..fff743e4 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -12,7 +12,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{excludes_all::ExcludesAll, traits::Condition}; +/// # use ucan::delegation::{condition::{ExcludesAll, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index 550014c6..58fbfc64 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -13,7 +13,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{excludes_key::ExcludesKey, traits::Condition}; +/// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 55c2738d..7ff605e8 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,6 +1,10 @@ +//! Traits for abstracting over conditions. + use crate::ability::arguments; use libipld_core::ipld::Ipld; +/// A trait for conditions that can be run on named IPLD arguments. pub trait Condition: TryFrom + Into { + /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 18f07363..8b12153a 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::{condition::traits::Condition, delegatable::Delegatable}; +use super::{condition::Condition, delegatable::Delegatable}; use crate::{ ability::{arguments, command::Command}, capsule::Capsule, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 87e06108..1680cdb5 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,4 +1,4 @@ -use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; +use super::{condition::Condition, delegatable::Delegatable, Delegation}; use crate::did::Did; use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index c72bc76c..d74bf61d 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -29,3 +29,21 @@ pub enum Promise { /// The `await/*` promise Any(PromiseAny), } + +impl From> for Promise { + fn from(p_ok: PromiseOk) -> Self { + Promise::Ok(p_ok) + } +} + +impl From> for Promise { + fn from(p_err: PromiseErr) -> Self { + Promise::Err(p_err) + } +} + +impl From> for Promise { + fn from(p_any: PromiseAny) -> Self { + Promise::Any(p_any) + } +} diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 8dbd4920..81e56c48 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -2,8 +2,8 @@ use super::{err::PromiseErr, ok::PromiseOk}; use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{DeserializeSeed, Deserializer, Error, Expected, MapAccess, Visitor}, - Deserialize, Serialize, Serializer, + de::{Deserializer, Error, MapAccess, Visitor}, + Deserialize, Serialize, }; use std::fmt; diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 70623609..2662344e 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -2,7 +2,6 @@ use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; -use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] @@ -14,6 +13,7 @@ pub enum PromiseOk { Pending(#[serde(rename = "await/ok")] Cid), } +// FIXME move try_resolve to a trait, give a blanket impl for prims, and tag them impl PromiseOk { pub fn try_resolve(self) -> Result> { match self { diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index c06b458b..a8df1488 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -26,6 +26,10 @@ impl Resolves> { } impl Resolves { + pub fn new(val: T) -> Self { + Resolves::Ok(PromiseOk::Fulfilled(val)) + } + pub fn try_resolve(self) -> Result> { match self { Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), @@ -205,6 +209,16 @@ impl Resolves { } } +impl> TryFrom for Resolves { + type Error = Ipld; + + fn try_from(ipld: Ipld) -> Result { + // FIXME so much cloning + let t = ipld.clone().try_into().map_err(|_| ipld.clone())?; + Ok(PromiseOk::Fulfilled(t).into()) + } +} + impl From> for Option { fn from(r: Resolves) -> Option { match r { diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index bc9103ea..3a1f23da 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -6,4 +6,9 @@ pub trait Resolvable: Sized { // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; + + // FIXME remove + fn try_resolve0(promised: Self::Promised) -> Result { + Self::try_resolve(promised) + } } diff --git a/src/ipld.rs b/src/ipld.rs index 7571fb44..d57a311f 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,205 +1,11 @@ //! Helpers for working with [`Ipld`] -pub mod cid; -pub mod promised; - -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -#[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Uint8Array}; - -/// A wrapper around [`Ipld`] that has additional trait implementations -/// -/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. -/// -/// ```rust -/// # use libipld_core::ipld::Ipld; -/// # use ucan::ipld; -/// # -/// let ipld = Ipld::String("hello".into()); -/// let wrapped = ipld::Newtype(ipld.clone()); -/// // wrapped.some_trait_method(); -/// ``` -/// -/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. -/// -/// ``` -/// # use libipld_core::ipld::Ipld; -/// # use ucan::ipld; -/// # -/// # let ipld = Ipld::String("hello".into()); -/// # let wrapped = ipld::Newtype(ipld.clone()); -/// # -/// assert_eq!(wrapped.0, ipld); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Newtype(pub Ipld); - -impl From for Newtype { - fn from(ipld: Ipld) -> Self { - Self(ipld) - } -} - -impl From for Ipld { - fn from(wrapped: Newtype) -> Self { - wrapped.0 - } -} - -#[cfg(target_arch = "wasm32")] -impl Newtype { - pub fn try_from_js>(js_val: JsValue) -> Result - where - JsError: From<>::Error>, - { - match Newtype::try_from(js_val) { - Err(_err) => Err(JsError::new("can't convert")), // FIXME - Ok(nt) => nt.0.try_into().map_err(JsError::from), - } - } -} - -// TODO testme -#[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(wrapped: Newtype) -> Self { - match wrapped.0 { - Ipld::Null => JsValue::NULL, - Ipld::Bool(b) => JsValue::from(b), - Ipld::Integer(i) => JsValue::from(i), - Ipld::Float(f) => JsValue::from_f64(f), - Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => { - let u8arr = Uint8Array::new(&bs.len().into()); - for (i, b) in bs.iter().enumerate() { - u8arr.set_index(i as u32, *b); - } - JsValue::from(u8arr) - } - Ipld::List(ls) => { - let arr = Array::new(); - for ipld in ls { - arr.push(&JsValue::from(Newtype(ipld))); - } - JsValue::from(arr) - } - Ipld::Map(m) => { - let map = Map::new(); - for (k, v) in m { - map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); - } - JsValue::from(map) - } - Ipld::Link(cid) => cid::Newtype::from(cid).into(), - } - } -} - -// TODO testme -#[cfg(target_arch = "wasm32")] -impl TryFrom for Newtype { - type Error = (); // FIXME - - fn try_from(js_val: JsValue) -> Result { - if js_val.is_null() { - return Ok(Newtype(Ipld::Null)); - } - - if let Some(b) = js_val.as_bool() { - return Ok(Newtype(Ipld::Bool(b))); - } - - if let Some(f) = js_val.as_f64() { - return Ok(Newtype(Ipld::Float(f))); - } - - if let Some(s) = js_val.as_string() { - return Ok(Newtype(Ipld::String(s))); - } - - if let Some(arr) = js_val.dyn_ref::() { - let mut list = vec![]; - for x in arr.to_vec().iter() { - let ipld = Newtype::try_from(x.clone())?.into(); - list.push(ipld); - } +mod enriched; +mod newtype; +mod promised; - return Ok(Newtype(Ipld::List(list))); - } - - if let Some(arr) = js_val.dyn_ref::() { - let mut v = vec![]; - for item in arr.to_vec().iter() { - v.push(item.clone()); - } - - return Ok(Newtype(Ipld::Bytes(v))); - } - - if let Some(map) = js_val.dyn_ref::() { - let mut m = std::collections::BTreeMap::new(); - let mut acc = Ok(()); - - // Weird order, but correct per the docs - // vvvvvvvvvv - map.for_each(&mut |value, key| { - if acc.is_err() { - return; - } - - match (key.as_string(), Newtype::try_from(value.clone())) { - (Some(k), Ok(v)) => { - m.insert(k, v.0); - } - _ => { - acc = Err(()); - } - } - }); - - return acc.map(|_| Newtype(Ipld::Map(m))); - } - - // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { - return Ok(Newtype(Ipld::Link(nt.into()))); - } - - if js_val.is_object() { - let obj = Object::from(js_val); - let mut m = std::collections::BTreeMap::new(); - let mut acc = Ok(()); - - Object::entries(&obj).for_each(&mut |js_val, _, _| { - if acc.is_err() { - return; - } - - // By definition this must be the array [value, key], in that order - let arr = Array::from(&js_val); - - match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { - (Some(k), Ok(v)) => { - m.insert(k, v.0); - } - // FIXME more specific errors - _ => { - acc = Err(()); - } - } - }); - - return acc.map(|_| Newtype(Ipld::Map(m))); - } - - // NOTE fails on `undefined` and `function` +pub mod cid; - Err(()) - } -} +pub use enriched::Enriched; +pub use newtype::Newtype; +pub use promised::Promised; diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index 791cc16b..a5cab51d 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,7 +1,7 @@ //! Utilities for [`Cid`]s use crate::ipld; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -111,9 +111,3 @@ impl TryFrom<&Ipld> for Newtype { #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] #[error("Not a CID: {0:?}")] pub struct NotACid(pub ipld::Newtype); - -// #[cfg(target_arch = "wasm32")] -// #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] -// #[error("Not a CID: {0:?}")] -// #[wasm_bindgen] -// pub struct NotACid(#[wasm_bindgen(skip)] pub ipld::Newtype); diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs new file mode 100644 index 00000000..120feaa9 --- /dev/null +++ b/src/ipld/enriched.rs @@ -0,0 +1,90 @@ +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Enriched { + /// Lifted [`Ipld::Null`] + Null, + + /// Lifted [`Ipld::Bool`] + Bool(bool), + + /// Lifted [`Ipld::Integer`] + Integer(i128), + + /// Lifted [`Ipld::Float`] + Float(f64), + + /// Lifted [`Ipld::String`] + String(String), + + /// Lifted [`Ipld::Bytes`] (byte array) + Bytes(Vec), + + /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + List(Vec), + + /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + Map(BTreeMap), + + /// Lifted [`Ipld::Link`] + Link(Cid), +} + +impl> From for Enriched { + fn from(ipld: Ipld) -> Self { + match ipld { + Ipld::Null => Enriched::Null, + Ipld::Bool(b) => Enriched::Bool(b), + Ipld::Integer(i) => Enriched::Integer(i), + Ipld::Float(f) => Enriched::Float(f), + Ipld::String(s) => Enriched::String(s), + Ipld::Bytes(b) => Enriched::Bytes(b), + Ipld::List(l) => Enriched::List(l.into_iter().map(From::from).collect()), + Ipld::Map(m) => Enriched::Map(m.into_iter().map(|(k, v)| (k, From::from(v))).collect()), + Ipld::Link(c) => Enriched::Link(c), + } + } +} + +impl> TryFrom> for Ipld { + type Error = Enriched; + + fn try_from(enriched: Enriched) -> Result { + match enriched { + Enriched::List(ref vec) => { + let result: Result, ()> = vec.iter().try_fold(vec![], |mut acc, x| { + let resolved = x.clone().try_into().map_err(|_| ())?; + acc.push(resolved); + Ok(acc) + }); + + match result { + Ok(vec) => Ok(vec.into()), + Err(()) => Err(enriched), + } + } + Enriched::Map(ref btree) => { + let result: Result, ()> = + btree.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { + let resolved = v.clone().try_into().map_err(|_| ())?; + acc.insert(k.clone(), resolved); + Ok(acc) + }); + + match result { + Ok(vec) => Ok(vec.into()), + Err(()) => Err(enriched), + } + } + Enriched::Null => Ok(Ipld::Null), + Enriched::Bool(b) => Ok(b.into()), + Enriched::Integer(i) => Ok(i.into()), + Enriched::Float(f) => Ok(f.into()), + Enriched::String(s) => Ok(s.into()), + Enriched::Bytes(b) => Ok(b.into()), + Enriched::Link(l) => Ok(l.into()), + } + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs new file mode 100644 index 00000000..95bf9fea --- /dev/null +++ b/src/ipld/newtype.rs @@ -0,0 +1,201 @@ +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Object, Uint8Array}; + +// FIXME push into the submodules +/// A wrapper around [`Ipld`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// let ipld = Ipld::String("hello".into()); +/// let wrapped = ipld::Newtype(ipld.clone()); +/// // wrapped.some_trait_method(); +/// ``` +/// +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// # let ipld = Ipld::String("hello".into()); +/// # let wrapped = ipld::Newtype(ipld.clone()); +/// # +/// assert_eq!(wrapped.0, ipld); +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Newtype(pub Ipld); + +impl From for Newtype { + fn from(ipld: Ipld) -> Self { + Self(ipld) + } +} + +impl From for Ipld { + fn from(wrapped: Newtype) -> Self { + wrapped.0 + } +} + +#[cfg(target_arch = "wasm32")] +impl Newtype { + pub fn try_from_js>(js_val: JsValue) -> Result + where + JsError: From<>::Error>, + { + match Newtype::try_from(js_val) { + Err(_err) => Err(JsError::new("can't convert")), // FIXME + Ok(nt) => nt.0.try_into().map_err(JsError::from), + } + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(wrapped: Newtype) -> Self { + match wrapped.0 { + Ipld::Null => JsValue::NULL, + Ipld::Bool(b) => JsValue::from(b), + Ipld::Integer(i) => JsValue::from(i), + Ipld::Float(f) => JsValue::from_f64(f), + Ipld::String(s) => JsValue::from_str(&s), + Ipld::Bytes(bs) => { + let u8arr = Uint8Array::new(&bs.len().into()); + for (i, b) in bs.iter().enumerate() { + u8arr.set_index(i as u32, *b); + } + JsValue::from(u8arr) + } + Ipld::List(ls) => { + let arr = Array::new(); + for ipld in ls { + arr.push(&JsValue::from(Newtype(ipld))); + } + JsValue::from(arr) + } + Ipld::Map(m) => { + let map = Map::new(); + for (k, v) in m { + map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); + } + JsValue::from(map) + } + Ipld::Link(cid) => cid::Newtype::from(cid).into(), + } + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = (); // FIXME + + fn try_from(js_val: JsValue) -> Result { + if js_val.is_null() { + return Ok(Newtype(Ipld::Null)); + } + + if let Some(b) = js_val.as_bool() { + return Ok(Newtype(Ipld::Bool(b))); + } + + if let Some(f) = js_val.as_f64() { + return Ok(Newtype(Ipld::Float(f))); + } + + if let Some(s) = js_val.as_string() { + return Ok(Newtype(Ipld::String(s))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut list = vec![]; + for x in arr.to_vec().iter() { + let ipld = Newtype::try_from(x.clone())?.into(); + list.push(ipld); + } + + return Ok(Newtype(Ipld::List(list))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut v = vec![]; + for item in arr.to_vec().iter() { + v.push(item.clone()); + } + + return Ok(Newtype(Ipld::Bytes(v))); + } + + if let Some(map) = js_val.dyn_ref::() { + let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); + + // Weird order, but correct per the docs + // vvvvvvvvvv + map.for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match (key.as_string(), Newtype::try_from(value.clone())) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + _ => { + acc = Err(()); + } + } + }); + + return acc.map(|_| Newtype(Ipld::Map(m))); + } + + // NOTE *must* come before `is_object` (which is hopefully below) + if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { + return Ok(Newtype(Ipld::Link(nt.into()))); + } + + if js_val.is_object() { + let obj = Object::from(js_val); + let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); + + Object::entries(&obj).for_each(&mut |js_val, _, _| { + if acc.is_err() { + return; + } + + // By definition this must be the array [value, key], in that order + let arr = Array::from(&js_val); + + match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + // FIXME more specific errors + _ => { + acc = Err(()); + } + } + }); + + return acc.map(|_| Newtype(Ipld::Map(m))); + } + + // NOTE fails on `undefined` and `function` + + Err(()) + } +} diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 5f78741e..d64ada74 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,177 +1,74 @@ -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}; -use libipld_core::{cid::Cid, ipld::Ipld}; +use super::enriched::Enriched; +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::{error::SerdeError, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; /// A promise to recursively resolve to an [`Ipld`] value. -/// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum PromisedIpld { - /// Lifted [`Ipld::Null`] - Null, +#[serde(transparent)] +pub struct Promised(pub Promise, Enriched>); - /// Lifted [`Ipld::Bool`] - Bool(bool), - - /// Lifted [`Ipld::Integer`] - Integer(i128), - - /// Lifted [`Ipld::Float`] - Float(f64), - - /// Lifted [`Ipld::String`] - String(String), - - /// Lifted [`Ipld::Bytes`] (byte array) - Bytes(Vec), - - /// [`Ipld::List`], but where the values are [`PromiseIpld`]. - List(Vec), - - /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. - Map(BTreeMap), - - /// Lifted [`Ipld::Link`] - Link(Cid), - - /// The `await/ok` promise - PromiseOk(Cid), - - /// The `await/err` promise - PromiseErr(Cid), - - /// The `await/*` promise - PromiseAny(Cid), -} - -impl PromisedIpld { - pub fn is_resolved(&self) -> bool { - match self { - PromisedIpld::Null => true, - PromisedIpld::Bool(_) => true, - PromisedIpld::Integer(_) => true, - PromisedIpld::Float(_) => true, - PromisedIpld::String(_) => true, - PromisedIpld::Bytes(_) => true, - PromisedIpld::List(list) => list.iter().all(PromisedIpld::is_resolved), - PromisedIpld::Map(map) => map.values().all(PromisedIpld::is_resolved), - PromisedIpld::Link(_) => true, - PromisedIpld::PromiseOk(_) => false, - PromisedIpld::PromiseErr(_) => false, - PromisedIpld::PromiseAny(_) => false, - } - } - - pub fn is_pending(&self) -> bool { - !self.is_resolved() - } -} - -impl From for PromisedIpld { - fn from(ipld: Ipld) -> Self { - match ipld { - Ipld::Null => PromisedIpld::Null, - Ipld::Bool(b) => PromisedIpld::Bool(b), - Ipld::Integer(i) => PromisedIpld::Integer(i), - Ipld::Float(f) => PromisedIpld::Float(f), - Ipld::String(s) => PromisedIpld::String(s), - Ipld::Bytes(b) => PromisedIpld::Bytes(b), - Ipld::List(list) => { - PromisedIpld::List(list.into_iter().map(PromisedIpld::from).collect()) - } - Ipld::Map(map) => PromisedIpld::Map( - map.into_iter() - .map(|(k, v)| (k, PromisedIpld::from(v))) - .collect(), - ), - Ipld::Link(cid) => PromisedIpld::Link(cid), - } - } -} - -impl From> for PromisedIpld { - fn from(promise: PromiseOk) -> Self { - match promise { - PromiseOk::Fulfilled(ipld) => ipld.into(), - PromiseOk::Pending(cid) => PromisedIpld::PromiseOk(cid), - } +impl From, Enriched>> for Promised { + fn from(promise: Promise, Enriched>) -> Self { + Promised(promise) } } -impl From> for PromisedIpld { - fn from(promise: PromiseErr) -> Self { - match promise { - PromiseErr::Rejected(ipld) => ipld.into(), - PromiseErr::Pending(cid) => PromisedIpld::PromiseErr(cid), - } +impl From>> for Promised { + fn from(p_ok: PromiseOk>) -> Self { + Promised(p_ok.into()) } } -impl From> for PromisedIpld { - fn from(promise: PromiseAny) -> Self { - match promise { - PromiseAny::Fulfilled(ipld) => ipld.into(), - PromiseAny::Rejected(ipld) => ipld.into(), - PromiseAny::Pending(cid) => PromisedIpld::PromiseAny(cid), - } +impl From>> for Promised { + fn from(p_err: PromiseErr>) -> Self { + Promised(p_err.into()) } } -impl From> for PromisedIpld { - fn from(resolves: Resolves) -> Self { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } +impl From, Enriched>> for Promised { + fn from(p_any: PromiseAny, Enriched>) -> Self { + Promised(p_any.into()) } } -impl From> for PromisedIpld { - fn from(promise: Promise) -> Self { - match promise { - Promise::Ok(p_ok) => p_ok.into(), - Promise::Err(p_err) => p_err.into(), - Promise::Any(p_any) => p_any.into(), - } +impl From for Promised { + fn from(ipld: Ipld) -> Self { + Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) } } -impl TryFrom for Ipld { - type Error = PromisedIpld; - - fn try_from(p: PromisedIpld) -> Result { - match p { - PromisedIpld::Null => Ok(Ipld::Null), - PromisedIpld::Bool(b) => Ok(Ipld::Bool(b)), - PromisedIpld::Integer(i) => Ok(Ipld::Integer(i)), - PromisedIpld::Float(f) => Ok(Ipld::Float(f)), - PromisedIpld::String(s) => Ok(Ipld::String(s)), - PromisedIpld::Bytes(b) => Ok(Ipld::Bytes(b)), - PromisedIpld::List(ref list) => { - let result: Result, ()> = list.iter().try_fold(vec![], |mut acc, x| { - let ipld = Ipld::try_from(x.clone()).map_err(|_| ())?; - acc.push(ipld); - Ok(acc) - }); - - Ok(Ipld::List(result.map_err(|_| p.clone())?)) - } - PromisedIpld::Map(ref map) => { - let map: Result, ()> = - map.into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - // FIXME non-tail recursion, and maybe even repeated clones - let ipld = Ipld::try_from(v.clone()).map_err(|_| ())?; - acc.insert(k.clone(), ipld); - Ok(acc) - }); - - Ok(Ipld::Map(map.map_err(|_| p)?)) - } - PromisedIpld::Link(cid) => Ok(Ipld::Link(cid)), - PromisedIpld::PromiseOk(_cid) => Err(p), - PromisedIpld::PromiseErr(_cid) => Err(p), - PromisedIpld::PromiseAny(_cid) => Err(p), +// FIXME THIS is a great example of a try_resolve +impl TryFrom for Ipld { + type Error = Promised; + + fn try_from(p: Promised) -> Result { + match p.0 { + Promise::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(inner) => { + inner.try_into().map_err(|e| PromiseOk::Fulfilled(e).into()) + } + + PromiseOk::Pending(inner) => Err(PromiseOk::Pending(inner).into()), + }, + Promise::Err(p_err) => match p_err { + PromiseErr::Rejected(inner) => { + inner.try_into().map_err(|e| PromiseErr::Rejected(e).into()) + } + + PromiseErr::Pending(inner) => Err(PromiseErr::Pending(inner).into()), + }, + Promise::Any(p_any) => match p_any { + PromiseAny::Fulfilled(inner) => inner + .try_into() + .map_err(|e| Promise::Any(PromiseAny::Fulfilled(e)).into()), + + PromiseAny::Rejected(inner) => { + inner.try_into().map_err(|e| PromiseAny::Rejected(e).into()) + } + + PromiseAny::Pending(inner) => Err(PromiseAny::Pending(inner).into()), + }, } } } diff --git a/src/proof.rs b/src/proof.rs index dbe2b43a..c0b401a7 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -7,6 +7,7 @@ pub mod parentless; pub mod parents; pub mod prove; pub mod same; +pub mod util; // NOTE must remain *un*exported! pub(super) mod internal; diff --git a/src/proof/error.rs b/src/proof/error.rs index a36b5208..c9bf339d 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -19,11 +19,11 @@ pub enum OptionalFieldError { /// A required field is missing. /// /// For example, when its proof has a vaue, but the target does not. - #[error("missing")] + #[error("Field missing")] Missing, /// A field is present but has a different value in its proof - #[error("unequal")] + #[error("Field value unequal")] Unequal, } diff --git a/src/proof/util.rs b/src/proof/util.rs new file mode 100644 index 00000000..05926acd --- /dev/null +++ b/src/proof/util.rs @@ -0,0 +1,15 @@ +use super::error::OptionalFieldError; + +pub fn check_optional( + target: Option, + proof: Option, +) -> Result<(), OptionalFieldError> { + if let Some(target_value) = target { + let proof_value = proof.ok_or(OptionalFieldError::Missing)?; + if target_value != proof_value { + return Err(OptionalFieldError::Unequal); + } + } + + Ok(()) +} diff --git a/src/task.rs b/src/task.rs index 454b6876..6e80ca88 100644 --- a/src/task.rs +++ b/src/task.rs @@ -71,6 +71,9 @@ impl From for Cid { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { + /// The CID of the [`Task`]. + /// + /// This acts as a unique identifier for the task. pub cid: Cid, } From 6fefae505a615029290803dd572ca331a135cd27 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 18:21:02 -0800 Subject: [PATCH 123/234] I believe that's most of teh abilities done. --- src/ability.rs | 35 +++ src/ability/arguments/named.rs | 7 +- src/ability/crud.rs | 3 +- src/ability/crud/create.rs | 261 ++++++++++++++++++++-- src/ability/crud/destroy.rs | 252 ++++++++++++++++++--- src/ability/crud/error.rs | 39 ++-- src/ability/crud/js.rs | 2 + src/ability/crud/parents.rs | 73 ++++--- src/ability/crud/read.rs | 339 +++++++++++++++++------------ src/ability/crud/update.rs | 322 ++++++++++++++++++++------- src/invocation/payload.rs | 2 +- src/invocation/promise/resolves.rs | 1 + src/invocation/resolvable.rs | 5 - src/ipld.rs | 2 +- src/ipld/newtype.rs | 7 + src/ipld/promised.rs | 14 +- 16 files changed, 1046 insertions(+), 318 deletions(-) diff --git a/src/ability.rs b/src/ability.rs index f9547f56..105a1bd6 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,3 +1,38 @@ +//! Abilities describe the semantics of what a UCAN is allowed to do. +//! +//! # Top Level Structure +//! +//! They always follow the same format at the top level: +//! +//! | Field | Name | Description | +//! |--------|-----------------------------|----------------------------------| +//! | `cmd` | [Command](command::Command) | Roughly a function name. Determines the shape of the `args`. | +//! | `args` | [Arguments](arguments) | Roughly the function's arguments | +//! +//! # Proof Hierarchy +//! +//! Any UCAN can be proven by the `*` ability. This has been special-cased +//! into the library, and you don't have to worry about it directly when +//! implementing a new ability. +//! +//! Most abilities have no additional parents. If they do, they follow a +//! strict hierararchy. The [CRUD hierarchy](crate::abilities::crud::Any) +//! is a good example. +//! +//! Not all abilities in the hierarchy are invocable: some abstract over +//! multiple `cmd`s (such as [`crud/*`](crate::abilities::crud::Any) for +//! all CRUD actions). This allows for flexibility in adding more abilities +//! under the same hierarchy in the future without having to reissue all of +//! your certificates. +//! +//! # Lifecycle +//! +//! All abilities start as a delegation, which can omit fields (but must +//! stay the same or add more at each delegatoion). When they are invoked, +//! all field much be present. The only exception is promises, where a +//! field may include a promise pointing at another invocation. Once fully +//! resolved ("ready"), they must be validatable against the delegation chain. + // FIXME feature flag each? // FIXME ability implementers guide (e.g. serde deny fields) // diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index a63e6ee0..710d91e5 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -10,9 +10,6 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Reflect}; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - /// Named arguments /// /// Being such a common pattern, but with so few trait implementations, @@ -77,6 +74,10 @@ impl Named { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn contains(&self, other: &Named) -> Result<(), NamedError> where T: PartialEq, diff --git a/src/ability/crud.rs b/src/ability/crud.rs index b06fb0f7..39e6c79c 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -39,16 +39,17 @@ mod any; mod mutate; +mod parents; pub mod create; pub mod destroy; pub mod error; -pub mod parents; pub mod read; pub mod update; pub use any::Any; pub use mutate::Mutate; +pub use parents::MutableParents; #[cfg(target_arch = "wasm32")] pub mod js; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 4e3e4b22..51f04b8e 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,46 +1,174 @@ +//! Create new resources. +use super::{error::ProofError, parents::MutableParents}; use crate::{ - ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ability::{arguments, command::Command}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::MutableParents; +// FIXME deserialize instance -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Create { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, +pub struct Generic { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, } -impl Command for Create { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/create` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style createready stroke:orange; +/// ``` +pub type Ready = Generic>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for creating other agents. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/create` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style create stroke:orange; +/// ``` +pub type Builder = Generic>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/create` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style createpromise stroke:orange; +/// ``` +pub type Promised = Generic, arguments::Promised>; + +impl Command for Generic { const COMMAND: &'static str = "crud/create"; } -impl From for Ipld { - fn from(create: Create) -> Self { +impl, A: Into> From> for Ipld { + fn from(create: Generic) -> Self { create.into() } } -impl TryFrom for Create { - type Error = SerdeError; +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } -impl Checkable for Create { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Create { +impl CheckSame for Builder { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -52,7 +180,7 @@ impl CheckSame for Create { } } -impl CheckParents for Create { +impl CheckParents for Builder { type Parents = MutableParents; type ParentError = (); // FIXME @@ -60,6 +188,7 @@ impl CheckParents for Create { if let Some(self_path) = &self.path { match other { MutableParents::Any(any) => { + // FIXME check the args, too! if let Some(proof_path) = &any.path { if self_path != proof_path { return Err(()); @@ -67,6 +196,7 @@ impl CheckParents for Create { } } MutableParents::Mutate(mutate) => { + // FIXME check the args, too! if let Some(proof_path) = &mutate.path { if self_path != proof_path { return Err(()); @@ -79,3 +209,94 @@ impl CheckParents for Create { Ok(()) } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } + + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } + + named + } +} + +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path, args }) + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index d448a34f..b1ad4189 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,58 +1,258 @@ +//! Destroy a resource. +use super::{error::ProofError, parents::MutableParents}; use crate::{ - ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ability::{arguments, command::Command}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::MutableParents; +// FIXME deserialize instance -// Destroy is its own builder -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Destroy { +pub struct Generic { + /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, + pub path: Option, } -impl Command for Destroy { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/destroy` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroyready stroke:orange; +/// ``` +pub type Ready = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for destroying resources. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/destroy` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroy stroke:orange; +/// ``` +pub type Builder = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/destroy` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroypromise stroke:orange; +/// ``` +pub type Promised = Generic>; + +impl

Command for Generic

{ const COMMAND: &'static str = "crud/destroy"; } -impl From for Ipld { - fn from(destroy: Destroy) -> Self { +impl> From> for Ipld { + fn from(destroy: Generic

) -> Self { destroy.into() } } -impl TryFrom for Destroy { - type Error = SerdeError; +impl> TryFrom for Generic

{ + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 1 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } -impl Checkable for Destroy { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Destroy { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for Builder { + type Error = (); // FIXME better error + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.path == proof.path { + Ok(()) + } else { + Err(()) + } } } -impl CheckParents for Destroy { +impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = (); + type ParentError = (); // FIXME fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - MutableParents::Mutate(_mutate) => Ok(()), // FIXME - MutableParents::Any(_any) => Ok(()), // FIXME + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } + } } + + Ok(()) + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } + + named + } +} + +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path }) } } diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs index 544ca58c..a4103586 100644 --- a/src/ability/crud/error.rs +++ b/src/ability/crud/error.rs @@ -1,10 +1,12 @@ -use crate::{ability::arguments, proof::error::OptionalFieldError}; +//! CRUD-specific errors + +use crate::{ + ability::arguments, + proof::{error::OptionalFieldError, parents::CheckParents, same::CheckSame}, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] pub enum ProofError { #[error("An issue with the path field")] @@ -17,13 +19,22 @@ pub enum ProofError { MissingProofArgs, } -// FIXME Is this just OptionalFieldError? -// #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -// pub enum PathError { -// #[error("Path required in proof, but was not present")] -// Missing, -// -// #[error("Proof path did not match")] -// Mismatch, -// } +/// Error cases when checking [`MutableParents`] proofs +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum ParentError { + /// Error when comparing `crud/*` to another `crud/*`. + #[error(transparent)] + InvalidAnyProof(::Error), + + /// Error when comparing `crud/mutate` to another `crud/mutate`. + #[error(transparent)] + InvalidMutateProof(::Error), + + /// Error when comparing `crud/*` as a proof for `crud/mutate`. + #[error(transparent)] + InvalidMutateParent(::ParentError), + + /// "Expected `crud/*`, but got `crud/mutate`". + #[error("Expected `crud/*`, but got `crud/mutate`")] + CommandEscelation, +} diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index c66f87e4..1153b7b0 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -1,3 +1,5 @@ +//! JavaScript bindings for the CRUD abilities. + use super::{read, Any}; use wasm_bindgen::prelude::*; diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 93f223b3..cad0494d 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -4,13 +4,52 @@ //! This only needs to handle "inner" delegation types, not the topmost `*` //! ability, or the invocable leaves of a delegation hierarchy. +use super::error::ParentError; use crate::proof::{parents::CheckParents, same::CheckSame}; use serde::{Deserialize, Serialize}; use thiserror::Error; +#[cfg_attr(doc, aquamarine::aquamarine)] /// The union of mutable parents. /// /// This is helpful as a flat type to put in [`CheckParents::Parents`]. +/// +/// # Delegation Hierarchy +/// +/// The parents captured here are highlted in the following diagram: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// read("crud/read") +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style any stroke:orange; +/// style mutate stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum MutableParents { @@ -22,46 +61,26 @@ pub enum MutableParents { } impl CheckSame for MutableParents { - type Error = Error; + type Error = ParentError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { MutableParents::Mutate(mutate) => match proof { MutableParents::Mutate(proof_mutate) => mutate .check_same(proof_mutate) - .map_err(Error::InvalidMutateProof), + .map_err(ParentError::InvalidMutateProof), MutableParents::Any(proof_any) => mutate .check_parent(proof_any) - .map_err(Error::InvalidMutateParent), + .map_err(ParentError::InvalidMutateParent), }, MutableParents::Any(any) => match proof { - MutableParents::Mutate(_) => Err(Error::CommandEscelation), - MutableParents::Any(proof_any) => { - any.check_same(proof_any).map_err(Error::InvalidAnyProof) - } + MutableParents::Mutate(_) => Err(ParentError::CommandEscelation), + MutableParents::Any(proof_any) => any + .check_same(proof_any) + .map_err(ParentError::InvalidAnyProof), }, } } } - -/// Error cases when checking [`MutableParents`] proofs -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum Error { - /// Error when comparing `crud/*` to another `crud/*`. - #[error(transparent)] - InvalidAnyProof(::Error), - - /// Error when comparing `crud/mutate` to another `crud/mutate`. - #[error(transparent)] - InvalidMutateProof(::Error), - - /// Error when comparing `crud/*` as a proof for `crud/mutate`. - #[error(transparent)] - InvalidMutateParent(::ParentError), - - /// "Expected `crud/*`, but got `crud/mutate`". - #[error("Expected `crud/*`, but got `crud/mutate`")] - CommandEscelation, -} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index bdc0b764..6b899eec 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,35 +1,40 @@ -//! The ability to read (fetch) from a resource. -//! -//! * This ability may be invoked when [`Ready`]. -//! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). -//! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. +//! Read from a resource. -use super::error::ProofError; +use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, invocation::{promise, promise::Resolves, Resolvable}, + ipld, proof::{ checkable::Checkable, error::OptionalFieldError, parentful::Parentful, parents::CheckParents, same::CheckSame, util::check_optional, }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +// FIXME deserialize instance + +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Generic { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] -/// The CRUD ability to retrieve data from a resource. -/// -/// # Invocation -/// -/// The executable/dispatchable variant of the `msg/send` ability. +/// This ability is used to fetch messages from other actors. /// /// # Lifecycle /// -/// The hierarchy of message abilities is as follows: +/// The relevant hierarchy of CRUD abilities is as follows: /// /// ```mermaid /// flowchart LR @@ -44,65 +49,112 @@ use wasm_bindgen::prelude::*; /// end /// /// readpromise("crud::read::Promised") -/// readrun("crud::read::Ready") +/// readready("crud::read::Ready") /// -/// top --> any -/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} /// -/// style readrun stroke:orange; +/// style readready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Ready { - /// Optional path within the resource. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +pub type Ready = Generic>; - /// Additional arugments to pass in the request. - pub args: arguments::Named, -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for reading resources. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/read` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readready("crud::read::Ready") +/// +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} +/// +/// style read stroke:orange; +/// ``` +pub type Builder = Generic>; #[cfg_attr(doc, aquamarine::aquamarine)] -/// The CRUD ability to retrieve data from a resource. +/// An invoked `crud/read` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). /// /// # Delegation Hierarchy /// /// The hierarchy of CRUD abilities is as follows: /// /// ```mermaid -/// flowchart TB -/// top("*") +/// flowchart LR +/// subgraph Delegations +/// top("*") /// -/// subgraph Message Abilities -/// any("crud/*") +/// subgraph CRUD Abilities +/// any("crud/*") /// -/// subgraph Invokable -/// read("crud/read") +/// subgraph Invokable +/// read("crud/read") +/// end /// end /// end /// -/// readrun{{"invoke"}} +/// readpromise("crud::read::Promised") +/// readready("crud::read::Ready") /// -/// top --> any -/// any --> read -.-> readrun +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} /// -/// style read stroke:orange; +/// style readpromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - // FIXME ^^^^^^ rename delegation as a pattern - /// Optional path within the resource. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +pub type Promised = Generic, arguments::Promised>; - #[serde(default, skip_serializing_if = "Option::is_none")] - /// Additional arugments to pass in the request. - pub args: Option>, +impl Command for Generic { + const COMMAND: &'static str = "crud/read"; } -impl Command for Ready { - const COMMAND: &'static str = "crud/read"; +impl, A: Into> From> for Ipld { + fn from(read: Generic) -> Self { + read.into() + } +} + +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } + } } impl Checkable for Builder { @@ -110,30 +162,40 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = ProofError; + type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - check_optional(self.path.as_ref(), proof.path.as_ref()) - .map_err(Into::::into)?; - - let args = self.args.as_ref().ok_or(ProofError::MissingProofArgs)?; - if let Some(proof_args) = &proof.args { - args.contains(proof_args).map_err(Into::into) - } else { + if self.path == proof.path { Ok(()) + } else { + Err(()) } } } impl CheckParents for Builder { - type Parents = super::Any; - type ParentError = OptionalFieldError; - - fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { - if let Some(path) = &self.path { - let crud_any_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; - if path != crud_any_path { - return Err(OptionalFieldError::Unequal); + type Parents = MutableParents; + type ParentError = (); // FIXME + + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + // FIXME check the args, too! + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + // FIXME check the args, too! + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } } } @@ -141,82 +203,57 @@ impl CheckParents for Builder { } } -impl From for Ipld { - fn from(ready: Ready) -> Self { - ready.into() - } -} -impl TryFrom for Ready { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); -#[cfg_attr(doc, aquamarine::aquamarine)] -/// This ability is used to fetch messages from other actors. -/// -/// # Lifecycle -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// any("crud/*") -/// -/// subgraph Invokable -/// read("crud/read") -/// end -/// end -/// -/// readpromise("crud::read::Promised") -/// readrun("crud::read::Ready") -/// -/// top --> any -/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} -/// -/// style readpromise stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Promised { - /// Optional path within the resource - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub path: promise::Resolves>, + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } - pub args: arguments::Promised, -} + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } -impl From for Ipld { - fn from(promised: Promised) -> Self { - promised.into() + named } } -impl TryFrom for Promised { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { Promised { - path: promise::PromiseOk::Fulfilled(r.path).into(), - args: promise::PromiseOk::Fulfilled(r.args.into()).into(), - } - } -} + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { - p.into() + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } } } @@ -224,15 +261,35 @@ impl Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - match Resolves::try_resolve_2(p.path, p.args) { - Ok((path, promise_args)) => match promise_args.try_into() { - Ok(args) => Ok(Ready { path, args }), - Err(args) => Err(Promised { - args: promise::PromiseOk::Fulfilled(args).into(), - path: promise::PromiseOk::Fulfilled(path).into(), - }), + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), }, - Err((path, args)) => Err(Promised { path, args }), + None => None, } + .transpose()?; + + Ok(Ready { path, args }) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 3c8ef326..44e29305 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,63 +1,166 @@ +//! Update existing resources. use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, - invocation::{promise, Resolvable}, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// FIXME deserialize instance + +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Ready { - /// Optional path within the resource. +pub struct Generic { + /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Additional arugments to pass in the request. - pub args: arguments::Named, -} + pub path: Option, -impl From for Ipld { - fn from(udpdate: Ready) -> Self { - udpdate.into() - } + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, } -impl TryFrom for Ready { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/create` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style updateready stroke:orange; +/// ``` +pub type Ready = Generic>; -impl Command for Ready { - const COMMAND: &'static str = "crud/update"; -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for updating existing agents. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/create` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style update stroke:orange; +/// ``` +pub type Builder = Generic>; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/update` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style updatepromise stroke:orange; +/// ``` +pub type Promised = Generic, arguments::Promised>; - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, +impl Command for Generic { + const COMMAND: &'static str = "crud/create"; } -impl From for Ipld { - fn from(udpdate: Builder) -> Self { - udpdate.into() +impl, A: Into> From> for Ipld { + fn from(create: Generic) -> Self { + create.into() } } -impl TryFrom for Builder { - type Error = SerdeError; +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } @@ -66,51 +169,98 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = ProofError; + type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.path - .check_same(&proof.path) - .map_err(Into::::into)?; - self.args.check_same(&proof.args).map_err(Into::into) + if self.path == proof.path { + Ok(()) + } else { + Err(()) + } } } impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = ProofError; + type ParentError = (); // FIXME - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - match proof { - MutableParents::Any(any) => self.path.check_same(&any.path).map_err(Into::into), - MutableParents::Mutate(mutate) => { - self.path.check_same(&mutate.path).map_err(Into::into) + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + // FIXME check the args, too! + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + // FIXME check the args, too! + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } } } + + Ok(()) } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Promised { - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub path: promise::Resolves>, +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } - pub args: arguments::Promised, + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } + + named + } } +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + impl From for Promised { fn from(r: Ready) -> Promised { Promised { - path: promise::PromiseOk::Fulfilled(r.path).into(), - args: promise::PromiseOk::Fulfilled(r.args.into()).into(), - } - } -} + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { - p.into() + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } } } @@ -118,19 +268,35 @@ impl Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - // FIXME resolve2? - // FIXME lots of clone - Ok(Ready { - path: p.path.clone().try_resolve().map_err(|path| Promised { - path, - args: p.args.clone(), - })?, - - args: p - .args - .clone() - .try_into() - .map_err(|args| Promised { path: p.path, args })?, - }) + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path, args }) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index adf6dc9b..b7fcdd48 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -33,7 +33,7 @@ pub struct Payload { // FIXME To TaskId // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload<::Promised>; // type Dynamic = Payload; <- ? // FIXME parser for both versions diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index a8df1488..216939aa 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Resolves { Ok(PromiseOk), Err(PromiseErr), diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 3a1f23da..bc9103ea 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -6,9 +6,4 @@ pub trait Resolvable: Sized { // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; - - // FIXME remove - fn try_resolve0(promised: Self::Promised) -> Result { - Self::try_resolve(promised) - } } diff --git a/src/ipld.rs b/src/ipld.rs index d57a311f..bec0bc37 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`] +//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld] mod enriched; mod newtype; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 95bf9fea..f2252979 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,5 +1,6 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -48,6 +49,12 @@ impl From for Ipld { } } +impl From for Newtype { + fn from(path: PathBuf) -> Self { + Newtype(Ipld::String(path.to_string_lossy().to_string())) + } +} + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index d64ada74..8f62f9f3 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,6 +1,6 @@ use super::enriched::Enriched; use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::{error::SerdeError, ipld::Ipld}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; /// A promise to recursively resolve to an [`Ipld`] value. @@ -8,6 +8,18 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); +// impl From for Ipld { +// fn from(promised: Promised) -> Self { +// promised.into() +// } +// } + +impl Promised { + pub fn serialize_as_ipld(&self) -> Ipld { + ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand + } +} + impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) From 3c2e49a6677ea8e4802fcc191a087c92bb70b23a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 21:18:44 -0800 Subject: [PATCH 124/234] Save poijt --- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_key.rs | 15 ++++--- src/delegation/condition/max_number.rs | 2 +- src/delegation/payload.rs | 54 ++++++++++++------------ src/ipld/promised.rs | 22 +++++----- src/number.rs | 5 +-- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 87b53498..df6844df 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -35,7 +35,7 @@ use serde_derive::{Deserialize, Serialize}; /// assert!(!cond.validate(&list)); /// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsKey { /// Name of the field to check. diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index 58fbfc64..09ec8523 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -16,26 +16,29 @@ use serde_derive::{Deserialize, Serialize}; /// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; /// # use libipld::ipld; /// # -/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); /// let cond = ExcludesKey{ /// field: "a".into(), /// key: "b".into() /// }; /// -/// assert!(!cond.validate(&args)); +/// let args1 = ipld!({"a": "b", "c": "d"}).try_into().unwrap(); +/// let args2 = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// +/// assert!(cond.validate(&args1)); +/// assert!(!cond.validate(&args2)); /// /// // Succeeds when the key is not present /// assert!(ExcludesKey { -/// field: "yep".into(), +/// field: "nope".into(), /// key: "b".into() -/// }.validate(&args)); +/// }.validate(&args2)); /// /// // Also succeeds when the input is not a map /// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); /// assert!(cond.validate(&list)); /// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesKey { /// Name of the field to check. @@ -62,7 +65,7 @@ impl TryFrom for ExcludesKey { impl Condition for ExcludesKey { fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { - Some(Ipld::Map(map)) => map.contains_key(&self.field), + Some(Ipld::Map(map)) => !map.contains_key(&self.key), _ => true, } } diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 981e63ea..9e2609fd 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -8,7 +8,7 @@ use serde_derive::{Deserialize, Serialize}; /// /// A condition that checks if the length of an integer /// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxNumber { /// Name of the field to check diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8b12153a..921601d7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -15,7 +15,10 @@ use crate::{ }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, +}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -25,8 +28,8 @@ pub struct Payload { pub audience: Did, pub ability_builder: T::Builder, - pub conditions: Vec, - + pub conditions: BTreeSet, // FIXME BTreeSet? + // pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -103,12 +106,14 @@ impl< where invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From> + Clone + Into>, + arguments::Named: From, { - let start: Acc<'a, T> = Acc { + // FIXME this is a task + let start: Acc<'_, T> = Acc { issuer: &invoked.issuer, subject: &invoked.subject, - to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone + ability: invoked.clone().into(), // FIXME surely we can eliminate this clone }; let args: arguments::Named = invoked.ability.clone().into(); @@ -116,6 +121,7 @@ impl< let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { match step(&prev, proof, &args, now) { + // FIXME add a `Outdcome::is_success` of into(result) method Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), Outcome::InvalidParents(_) => Err(()), @@ -124,12 +130,12 @@ impl< Outcome::ProvenByAny => Ok(Acc { issuer: &proof.issuer, subject: &proof.subject, - to_check: prev.to_check, + ability: prev.ability, }), Outcome::Proven => Ok(Acc { issuer: &proof.issuer, subject: &proof.subject, - to_check: proof.ability_builder.clone().into(), // FIXME double check + ability: proof.ability_builder.clone().into(), // FIXME double check }), } } else { @@ -149,7 +155,7 @@ impl< struct Acc<'a, T: Checkable> { issuer: &'a Did, subject: &'a Did, - to_check: T::Hierarchy, + ability: T::Hierarchy, } // FIXME this should move to Delegatable @@ -162,6 +168,8 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( // FIXME ^^^^^^^^^^^^ Outcome types where U::Builder: Into + Clone, + arguments::Named: From, + T::Hierarchy: Clone + Into>, { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); @@ -181,30 +189,22 @@ where return Outcome::InvalidProofChain(()); } - // FIXME check the spec - // if self.conditions.len() < proof.conditions { - // ...etc etc - // return Err(()); - // } - - let cond_result = - proof.conditions.iter().try_fold( - (), - |_acc, c| { - if c.validate(&args) { - Ok(()) - } else { - Err(()) - } - }, - ); + // FIXME coudl be more efficient, but sets need Ord and we have floats + let cond_result = &proof.conditions.iter().try_fold((), |_acc, c| { + // FIXME revisit + if c.validate(&args) && c.validate(&prev.ability.clone().into()) { + Ok(()) + } else { + Err(()) + } + }); if let Err(_) = cond_result { return Outcome::InvalidProofChain(()); } // FIXME pretty sure we can avoid this clone - let outcome = prev.to_check.check(&proof.ability_builder.clone().into()); + let outcome = prev.ability.check(&proof.ability_builder.clone().into()); match outcome { Outcome::Proven => Outcome::Proven, diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8f62f9f3..8e0f62b1 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -8,24 +8,28 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); -// impl From for Ipld { -// fn from(promised: Promised) -> Self { -// promised.into() -// } -// } - impl Promised { + // FIXME note that this is different from the failable version which is more like + // a try_reoslve... which has a note at the bottom on this module pub fn serialize_as_ipld(&self) -> Ipld { ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand } } +// Promise variants into Promised + impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) } } +impl From, Enriched>> for Promised { + fn from(p_any: PromiseAny, Enriched>) -> Self { + Promised(p_any.into()) + } +} + impl From>> for Promised { fn from(p_ok: PromiseOk>) -> Self { Promised(p_ok.into()) @@ -38,11 +42,7 @@ impl From>> for Promised { } } -impl From, Enriched>> for Promised { - fn from(p_any: PromiseAny, Enriched>) -> Self { - Promised(p_any.into()) - } -} +// IPLD impl From for Promised { fn from(ipld: Ipld) -> Self { diff --git a/src/number.rs b/src/number.rs index f29978d4..1c38f6b5 100644 --- a/src/number.rs +++ b/src/number.rs @@ -4,10 +4,7 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; /// The union of [`Ipld`] numeric types -/// -/// This is helpful when working with JavaScript, or with -/// values that may be given as either an integer or a float. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number From 366c416517e30e8cffeb79480be4978ec20956ca Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 23:33:50 -0800 Subject: [PATCH 125/234] Time to go through the type constraints --- src/delegation.rs | 1 + src/delegation/error.rs | 33 ++++++++ src/delegation/payload.rs | 172 +++++++++++++++++--------------------- src/proof/checkable.rs | 6 +- src/proof/parentful.rs | 30 ++++--- src/proof/parentless.rs | 23 +++-- src/proof/prove.rs | 22 ++--- 7 files changed, 144 insertions(+), 143 deletions(-) create mode 100644 src/delegation/error.rs diff --git a/src/delegation.rs b/src/delegation.rs index 3fe71ed2..b3f7259c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -2,6 +2,7 @@ mod delegatable; mod payload; pub mod condition; +pub mod error; pub mod store; pub use delegatable::Delegatable; diff --git a/src/delegation/error.rs b/src/delegation/error.rs new file mode 100644 index 00000000..1ff31a12 --- /dev/null +++ b/src/delegation/error.rs @@ -0,0 +1,33 @@ +pub enum EnvelopeError { + InvalidSubject, + MisalignedIssAud, + Expired, + NotYetValid, +} + +// FIXME Error, etc +pub enum DelegationError { + Envelope(EnvelopeError), + + FailedCondition, // FIXME add context? + + SemanticError(Semantic), // // FIXME these are duplicated in Outcome + // // + // /// An error in the command chain. + // CommandEscelation, + + // /// An error in the argument chain. + // ArgumentEscelation(ArgErr), + + // /// An error in the proof chain. + // InvalidProofChain(ChainErr), + + // /// An error in the parents. + // InvalidParents(ParentErr), +} + +impl From for DelegationError { + fn from(err: EnvelopeError) -> Self { + DelegationError::Envelope(err) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 921601d7..cbf00378 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,24 +1,23 @@ -use super::{condition::Condition, delegatable::Delegatable}; +use super::{ + condition::Condition, + delegatable::Delegatable, + error::{DelegationError, EnvelopeError}, +}; use crate::{ ability::{arguments, command::Command}, capsule::Capsule, did::Did, - invocation, - invocation::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, -}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -28,8 +27,7 @@ pub struct Payload { pub audience: Did, pub ability_builder: T::Builder, - pub conditions: BTreeSet, // FIXME BTreeSet? - // pub conditions: Vec, // FIXME BTreeSet? + pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -92,128 +90,112 @@ impl From> for Ipld { } // FIXME this likely should move to invocation -impl< - 'a, - T: Delegatable + Resolvable + Checkable + Clone + Into>, - C: Condition, - > Payload -{ - pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version +impl<'a, T: Delegatable, C: Condition> Payload { + pub fn check( + delegated: &'a Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, - ) -> Result<(), ()> + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - invocation::Payload: Clone, - U::Builder: Clone + Into, - T::Hierarchy: From> + Clone + Into>, arguments::Named: From, + Payload: Clone, + U: Delegatable + Clone, + U::Builder: Clone, + T::Builder: Checkable + CheckSame + Clone, + ::Hierarchy: CheckSame + + From + + Clone + + Into> + + From<::Builder>, { + let builder = &delegated.ability_builder; + let hierarchy = ::Hierarchy::from(builder.clone()); + // FIXME this is a task - let start: Acc<'_, T> = Acc { - issuer: &invoked.issuer, - subject: &invoked.subject, - ability: invoked.clone().into(), // FIXME surely we can eliminate this clone + let start: Acc = Acc { + issuer: delegated.issuer.clone(), + subject: delegated.subject.clone(), + hierarchy, }; - let args: arguments::Named = invoked.ability.clone().into(); - - let result = proofs.iter().fold(Ok(start), |acc, proof| { - if let Ok(prev) = acc { - match step(&prev, proof, &args, now) { - // FIXME add a `Outdcome::is_success` of into(result) method - Outcome::ArgumentEscelation(_) => Err(()), - Outcome::InvalidProofChain(_) => Err(()), - Outcome::InvalidParents(_) => Err(()), - Outcome::CommandEscelation => Err(()), - // NOTE this case! - Outcome::ProvenByAny => Ok(Acc { - issuer: &proof.issuer, - subject: &proof.subject, - ability: prev.ability, - }), - Outcome::Proven => Ok(Acc { - issuer: &proof.issuer, - subject: &proof.subject, - ability: proof.ability_builder.clone().into(), // FIXME double check - }), + let args: arguments::Named = delegated.ability_builder.clone().into(); + + proofs + .into_iter() + .fold(Ok(start), |prev, proof| { + if let Ok(prev_) = prev { + step(&prev_, &proof, &args, now).map(move |success| { + match success { + Success::ProvenByAny => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: prev_.hierarchy, + }, + Success::Proven => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: ::Hierarchy::from( + proof.ability_builder.clone(), + ), // FIXME double check + }, + } + }) + } else { + prev } - } else { - acc - } - }); - - // FIXME - match result { - Ok(_) => Ok(()), - Err(_) => Err(()), - } + }) + .map(|_| ()) } } #[derive(Debug, Clone)] -struct Acc<'a, T: Checkable> { - issuer: &'a Did, - subject: &'a Did, - ability: T::Hierarchy, +pub(crate) struct Acc { + issuer: Did, + subject: Did, + hierarchy: B::Hierarchy, } -// FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition>( - prev: &Acc<'a, T>, +// FIXME this should move to Delegatable? +pub(crate) fn step<'a, B: Checkable, U: Delegatable, C: Condition>( + prev: &Acc, proof: &Payload, args: &arguments::Named, now: SystemTime, -) -> Outcome<(), (), ()> -// FIXME ^^^^^^^^^^^^ Outcome types +) -> Result::Error>> where - U::Builder: Into + Clone, arguments::Named: From, - T::Hierarchy: Clone + Into>, + U::Builder: Into + Clone, + B::Hierarchy: Clone + Into>, { if let Err(_) = prev.issuer.check_same(&proof.audience) { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::InvalidSubject.into()); } if let Err(_) = prev.subject.check_same(&proof.subject) { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::MisalignedIssAud.into()); + } + + if SystemTime::from(proof.expiration.clone()) > now { + return Err(EnvelopeError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > now { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::NotYetValid.into()); } } - if SystemTime::from(proof.expiration.clone()) > now { - return Outcome::InvalidProofChain(()); - } - // FIXME coudl be more efficient, but sets need Ord and we have floats - let cond_result = &proof.conditions.iter().try_fold((), |_acc, c| { + for c in proof.conditions.iter() { // FIXME revisit - if c.validate(&args) && c.validate(&prev.ability.clone().into()) { - Ok(()) - } else { - Err(()) + if !c.validate(&args) || !c.validate(&prev.hierarchy.clone().into()) { + return Err(DelegationError::FailedCondition); } - }); - - if let Err(_) = cond_result { - return Outcome::InvalidProofChain(()); } - // FIXME pretty sure we can avoid this clone - let outcome = prev.ability.check(&proof.ability_builder.clone().into()); - - match outcome { - Outcome::Proven => Outcome::Proven, - Outcome::ProvenByAny => Outcome::ProvenByAny, - Outcome::CommandEscelation => Outcome::CommandEscelation, - Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), - Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), - Outcome::InvalidParents(_) => Outcome::InvalidParents(()), - } + prev.hierarchy + .check(&proof.ability_builder.clone().into()) + .map_err(DelegationError::SemanticError) } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 87b08f4f..ee855eae 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,8 +1,8 @@ //! Define the hierarchy of an ability (or mark as not having one) -use super::{internal::Checker, prove::Prove, same::CheckSame}; +use super::{prove::Prove, same::CheckSame}; -// FIXME move to Delegatbel? +// FIXME mo ve to Delegatbel? /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { @@ -11,5 +11,5 @@ pub trait Checkable: CheckSame { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: Checker + CheckSame + Prove; + type Hierarchy: CheckSame + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 340b45f4..54b29938 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -3,7 +3,7 @@ use super::{ internal::Checker, parents::CheckParents, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -129,34 +129,32 @@ where impl Checker for Parentful {} -impl Prove> for Parentful +impl Prove for Parentful where T::Parents: CheckSame, { - type ArgumentError = T::Error; - type ProofChainError = T::ParentError; - type ParentsError = ::Error; // FIXME better name + type Error = ParentfulError::Error>; - fn check(&self, proof: &Parentful) -> Outcome { + fn check(&self, proof: &Parentful) -> Result { match proof { - Parentful::Any => Outcome::ProvenByAny, + Parentful::Any => Ok(Success::ProvenByAny), Parentful::Parents(their_parents) => match self { - Parentful::Any => Outcome::CommandEscelation, + Parentful::Any => Err(ParentfulError::CommandEscelation), Parentful::Parents(parents) => match parents.check_same(their_parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidParents(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::InvalidParents(e)), }, Parentful::This(this) => match this.check_parent(their_parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidProofChain(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::InvalidProofChain(e)), }, }, Parentful::This(that) => match self { - Parentful::Any => Outcome::CommandEscelation, - Parentful::Parents(_) => Outcome::CommandEscelation, + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), Parentful::This(this) => match this.check_same(that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::ArgumentEscelation(e)), }, }, } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 390c99ac..4e3659fa 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -3,12 +3,11 @@ use super::{ checkable::Checkable, internal::Checker, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::convert::Infallible; /// The possible cases for an [ability][crate::ability]'s /// [Delegation][crate::delegation::Delegation] chain when @@ -68,8 +67,8 @@ impl + DeserializeOwned> TryFrom for Parentless { impl CheckSame for Parentless { type Error = ParentlessError; - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - match other { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { Parentless::Any => Ok(()), Parentless::This(that) => match self { Parentless::Any => Err(ParentlessError::CommandEscelation), @@ -83,19 +82,17 @@ impl CheckSame for Parentless { impl Checker for Parentless {} -impl Prove> for Parentless { - type ArgumentError = T::Error; - type ProofChainError = Infallible; - type ParentsError = Infallible; +impl Prove for Parentless { + type Error = ParentlessError; - fn check(&self, proof: &Parentless) -> Outcome { + fn check(&self, proof: &Parentless) -> Result { match proof { - Parentless::Any => Outcome::Proven, + Parentless::Any => Ok(Success::ProvenByAny), Parentless::This(that) => match self { - Parentless::Any => Outcome::Proven, + Parentless::Any => Ok(Success::Proven), Parentless::This(this) => match this.check_same(that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentlessError::ArgumentEscelation(e)), }, }, } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 34e32de0..fbf4d54d 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -3,31 +3,21 @@ use super::internal::Checker; /// An internal trait that checks based on the other traits for an ability type. -pub trait Prove { - /// The error if the argument is invalid. - type ArgumentError; +pub trait Prove: Checker { + type Error; - /// The error if the proof chain is invalid. - type ProofChainError; - - /// The error if the parents are invalid. - type ParentsError; - - fn check( - &self, - proof: &T, - ) -> Outcome; + fn check(&self, proof: &Self) -> Result; } -// FIXME that's a lot of error type params -/// The outcome of a proof check. -pub enum Outcome { +pub enum Success { /// Success Proven, /// Special case for success by checking against `*`. ProvenByAny, +} +pub enum Failure { /// An error in the command chain. CommandEscelation, From ae01353d61e0614dc92ec64799315100a4d4e2b7 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 23:51:21 -0800 Subject: [PATCH 126/234] Much simplified delegationpayload --- src/delegation/payload.rs | 87 +++++++++++++++------------------------ 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index cbf00378..91bccd4d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,5 @@ use super::{ condition::Condition, - delegatable::Delegatable, error::{DelegationError, EnvelopeError}, }; use crate::{ @@ -21,12 +20,12 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, - pub ability_builder: T::Builder, + pub delegated_ability: D, pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -35,25 +34,25 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); + let s = InternalSerializer::from(self.clone()); // FIXME Serialize::serialize(&s, serializer) } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload +impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -71,7 +70,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -83,42 +82,29 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable, C: Condition> Payload { - pub fn check( +impl<'a, T: Checkable + CheckSame + Clone + Prove + Into>, C: Condition> + Payload +{ + pub fn check( delegated: &'a Payload, // FIXME promisory version - proofs: Vec>, + proofs: Vec>, now: SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> - where - arguments::Named: From, - Payload: Clone, - U: Delegatable + Clone, - U::Builder: Clone, - T::Builder: Checkable + CheckSame + Clone, - ::Hierarchy: CheckSame - + From - + Clone - + Into> - + From<::Builder>, - { - let builder = &delegated.ability_builder; - let hierarchy = ::Hierarchy::from(builder.clone()); - - // FIXME this is a task - let start: Acc = Acc { + ) -> Result<(), DelegationError<::Error>> +where { + let start: Acc = Acc { issuer: delegated.issuer.clone(), subject: delegated.subject.clone(), - hierarchy, + hierarchy: delegated.delegated_ability.clone(), }; - let args: arguments::Named = delegated.ability_builder.clone().into(); + let args: arguments::Named = delegated.delegated_ability.clone().into(); proofs .into_iter() @@ -134,9 +120,7 @@ impl<'a, T: Delegatable, C: Condition> Payload { Success::Proven => Acc { issuer: proof.issuer.clone(), subject: proof.subject.clone(), - hierarchy: ::Hierarchy::from( - proof.ability_builder.clone(), - ), // FIXME double check + hierarchy: proof.delegated_ability.clone(), // FIXME double check }, } }) @@ -149,24 +133,19 @@ impl<'a, T: Delegatable, C: Condition> Payload { } #[derive(Debug, Clone)] -pub(crate) struct Acc { +pub(crate) struct Acc { issuer: Did, subject: Did, - hierarchy: B::Hierarchy, + hierarchy: T, } // FIXME this should move to Delegatable? -pub(crate) fn step<'a, B: Checkable, U: Delegatable, C: Condition>( - prev: &Acc, - proof: &Payload, +pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condition>( + prev: &Acc, + proof: &Payload, args: &arguments::Named, now: SystemTime, -) -> Result::Error>> -where - arguments::Named: From, - U::Builder: Into + Clone, - B::Hierarchy: Clone + Into>, -{ +) -> Result::Error>> { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Err(EnvelopeError::InvalidSubject.into()); } @@ -194,7 +173,7 @@ where } prev.hierarchy - .check(&proof.ability_builder.clone().into()) + .check(&proof.delegated_ability.clone()) .map_err(DelegationError::SemanticError) } @@ -226,18 +205,18 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer +impl> From> for InternalSerializer where - BTreeMap: From, + arguments::Named: From, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - command: T::COMMAND.into(), - arguments: payload.ability_builder.into(), + command: B::COMMAND.into(), + arguments: payload.delegated_ability.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), metadata: payload.metadata, From a51202fdb83f390166450bf2862f8080e50785fc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 10 Feb 2024 00:40:55 -0800 Subject: [PATCH 127/234] Much simplification --- src/delegation/payload.rs | 183 ++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 106 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 91bccd4d..d0cd8304 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -3,7 +3,10 @@ use super::{ error::{DelegationError, EnvelopeError}, }; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ToCommand}, + }, capsule::Capsule, did::Did, nonce::Nonce, @@ -14,8 +17,8 @@ use crate::{ }, time::Timestamp, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -25,6 +28,9 @@ pub struct Payload { pub subject: Did, pub audience: Did, + /// A delegatable ability chain. + /// + /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? pub delegated_ability: D, pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, @@ -38,46 +44,66 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + Ipld: From, + arguments::Named: From, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); // FIXME - Serialize::serialize(&s, serializer) + let mut state = serializer.serialize_struct("delegation::Payload", 9)?; + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("sub", &self.subject)?; + state.serialize_field("aud", &self.audience)?; + state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field( + "args", + &arguments::Named::from(self.delegated_ability.clone()), + )?; + + state.serialize_field( + "cond", + &self + .conditions + .iter() + .map(|c| Ipld::from(c.clone())) + .collect::>(), + )?; + + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("exp", &self.expiration)?; + state.serialize_field("nbf", &self.not_before)?; + state.end() } } impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where D: serde::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => s - .try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error - } + InternalDeserializer::deserialize(d).and_then(|s| { + s.try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) // FIXME better error + }) } } impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + let s: InternalDeserializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; // FIXME s.try_into().map_err(|_| ()) // FIXME } } @@ -88,16 +114,15 @@ impl From> for Ipld { } } -// FIXME this likely should move to invocation -impl<'a, T: Checkable + CheckSame + Clone + Prove + Into>, C: Condition> - Payload -{ +impl<'a, T, C: Condition> Payload { pub fn check( delegated: &'a Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), DelegationError<::Error>> -where { + where + T: Checkable + CheckSame + Clone + Prove + Into>, + { let start: Acc = Acc { issuer: delegated.issuer.clone(), subject: delegated.subject.clone(), @@ -133,14 +158,14 @@ where { } #[derive(Debug, Clone)] -pub(crate) struct Acc { +struct Acc { issuer: Did, subject: Did, hierarchy: T, } // FIXME this should move to Delegatable? -pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condition>( +fn step<'a, T: Prove + Clone + Into>, C: Condition>( prev: &Acc, proof: &Payload, args: &arguments::Named, @@ -177,9 +202,9 @@ pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condi .map_err(DelegationError::SemanticError) } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] -struct InternalSerializer { +struct InternalDeserializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] @@ -205,93 +230,39 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer -where - arguments::Named: From, +impl>, C: Condition + From> + TryFrom for Payload { - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, + type Error = (); // FIXME + + fn try_from(d: InternalDeserializer) -> Result { + let p: Self = Payload { + issuer: d.issuer, + subject: d.subject, + audience: d.audience, - command: B::COMMAND.into(), - arguments: payload.delegated_ability.into(), - conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), + delegated_ability: d.arguments.try_into().map_err(|_| ())?, // d.command.into(), + conditions: d.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata, - nonce: payload.nonce, + metadata: d.metadata, + nonce: d.nonce, + + not_before: d.not_before, + expiration: d.expiration, + }; - not_before: payload.not_before, - expiration: payload.expiration, + if p.delegated_ability.to_command() != d.command { + return Err(()); } + + Ok(p) } } -impl TryFrom for InternalSerializer { - type Error = (); // FIXME +impl TryFrom for InternalDeserializer { + type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } - -// FIXME -// impl, E: meta::MultiKeyed + Clone> TryFrom -// for Payload, C, E> -// { -// type Error = (); // FIXME -// -// fn try_from(s: InternalSerializer) -> Result, C, E>, ()> { -// Ok(Payload { -// issuer: s.issuer, -// subject: s.subject, -// audience: s.audience, -// -// ability_builder: dynamic::Dynamic { -// cmd: s.command, -// args: s.arguments, -// }, -// conditions: s -// .conditions -// .iter() -// .try_fold(Vec::new(), |mut acc, c| { -// C::try_from(c.clone()).map(|x| { -// acc.push(x); -// acc -// }) -// }) -// .map_err(|_| ())?, // FIXME better error (collect all errors -// -// metadata: Metadata::extract(s.metadata), -// nonce: s.nonce, -// -// not_before: s.not_before, -// expiration: s.expiration, -// }) -// } -// } -// -// impl, E: meta::MultiKeyed + Clone, F> -// From, C, E>> for InternalSerializer -// where -// Metadata: Mergable, -// { -// fn from(p: Payload, C, E>) -> Self { -// InternalSerializer { -// issuer: p.issuer, -// subject: p.subject, -// audience: p.audience, -// -// command: p.ability_builder.cmd, -// arguments: p.ability_builder.args, -// conditions: p.conditions.into_iter().map(|c| c.into()).collect(), -// -// metadata: p.metadata.merge(), -// nonce: p.nonce, -// -// not_before: p.not_before, -// expiration: p.expiration, -// } -// } -// } From 3097b3d3299f1789e3e1ebfa601feaa02a0aabef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 10 Feb 2024 14:54:51 -0800 Subject: [PATCH 128/234] Needs renaming, but custom serializer feels much better --- src/ability/arguments/named.rs | 23 ++++- src/ability/command.rs | 38 ++++++++ src/ability/crud/any.rs | 2 + src/ability/crud/js.rs | 8 +- src/ability/crud/parents.rs | 49 +++++++++- src/ability/crud/update.rs | 2 +- src/delegation/payload.rs | 166 ++++++++++++++++++++++++++++----- src/invocation/payload.rs | 6 +- 8 files changed, 260 insertions(+), 34 deletions(-) diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 710d91e5..1cf45018 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -118,11 +118,28 @@ impl FromIterator<(String, T)> for Named { } } -impl Deserialize<'de>> TryFrom for Named { - type Error = SerdeError; +// FIXME move to common ipld module? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum FromIpldError { + NotAMap, + LeafError(#[from] E), +} + +impl> TryFrom for Named { + type Error = FromIpldError<>::Error>; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::Map(map) => Ok(map + .into_iter() + .try_fold(Named::new(), |mut named, (k, v)| { + let value = T::try_from(v).map_err(FromIpldError::LeafError)?; + named.insert(k, value); + Ok::, Self::Error>(named) + })?), + + _ => Err(FromIpldError::NotAMap), + } } } diff --git a/src/ability/command.rs b/src/ability/command.rs index 756562cb..ec220822 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -16,6 +16,11 @@ //! } //! ``` +use crate::ability::arguments; +use libipld_core::ipld::Ipld; +use std::fmt; +use thiserror::Error; + /// Attach a `cmd` field to a type /// /// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. @@ -40,6 +45,7 @@ pub trait Command { /// The value that will be placed in the UCAN's `cmd` field for the given type /// + /// FIXME /// This is a `const` because it *must not*[^dynamic] depend on the runtime values of a type /// in order to ensure type safety. /// @@ -49,6 +55,38 @@ pub trait Command { const COMMAND: &'static str; } +pub trait ParseAbility: Sized { + type Error: fmt::Display; + // FIXME rename this trait to Ability? + fn try_parse(cmd: &str, args: &arguments::Named) -> Result; +} + +#[derive(Debug, Clone, Error)] +pub enum ParseAbilityError { + #[error("Unknown command")] + UnknownCommand, + + #[error(transparent)] + InvalidArgs(#[from] E), +} + +impl> ParseAbility for T +where + >::Error: fmt::Display, +{ + type Error = ParseAbilityError<>::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand); + } + + Ipld::from(args.clone()) + .try_into() + .map_err(ParseAbilityError::InvalidArgs) + } +} + // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] pub(crate) trait ToCommand { diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 077d1d70..d28d867a 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -60,6 +60,8 @@ pub struct Any { pub path: Option, } +use crate::ability::command::ParseAbility; + impl Command for Any { const COMMAND: &'static str = "crud/*"; } diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index 1153b7b0..f7978aa9 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -18,8 +18,8 @@ impl CrudAny { ipld::Newtype::try_from_js(js).map(CrudAny) } - pub fn command(&self) -> String { - Any::COMMAND.to_string() + pub fn to_command(&self) -> String { + self.to_command() } pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { @@ -50,8 +50,8 @@ impl CrudRead { ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) } - pub fn command(&self) -> String { - Read::COMMAND.to_string() + pub fn to_command(&self) -> String { + Read::to_command() } pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index cad0494d..884b64fc 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -5,7 +5,10 @@ //! ability, or the invocable leaves of a delegation hierarchy. use super::error::ParentError; -use crate::proof::{parents::CheckParents, same::CheckSame}; +use crate::{ + ability::command::Command, + proof::{parents::CheckParents, same::CheckSame}, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -51,7 +54,7 @@ use thiserror::Error; /// style mutate stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] +#[serde(deny_unknown_fields, untagged)] pub enum MutableParents { /// The `crud/*` ability. Any(super::Any), @@ -60,6 +63,48 @@ pub enum MutableParents { Mutate(super::Mutate), } +// FIXME +use crate::ability::command::{ParseAbility, ToCommand}; + +impl ToCommand for MutableParents { + fn to_command(&self) -> String { + match self { + MutableParents::Any(any) => any.to_command(), + MutableParents::Mutate(mutate) => mutate.to_command(), + } + } +} + +use crate::ability::{arguments, command::ParseAbilityError}; +use libipld_core::ipld::Ipld; + +#[derive(Debug, Clone, Error)] +pub enum ParseError { + #[error("Invalid `crud/*` arguments: {0}")] + InvalidAnyArgs(#[source] ::Error), + + #[error("Invalid `crud/mutate` arguments: {0}")] + InvalidMutateArgs(#[source] ::Error), +} + +impl ParseAbility for MutableParents { + type Error = ParseAbilityError; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + super::Any::try_parse(cmd, args) + .map(MutableParents::Any) + .map_err(ParseError::InvalidAnyArgs) + .map_err(ParseAbilityError::InvalidArgs)?; + + super::Mutate::try_parse(cmd, args) + .map(MutableParents::Mutate) + .map_err(ParseError::InvalidMutateArgs) + .map_err(ParseAbilityError::InvalidArgs)?; + + Err(ParseAbilityError::UnknownCommand) + } +} + impl CheckSame for MutableParents { type Error = ParentError; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 44e29305..ff3d153c 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -129,7 +129,7 @@ pub type Builder = Generic>; pub type Promised = Generic, arguments::Promised>; impl Command for Generic { - const COMMAND: &'static str = "crud/create"; + const COMMAND: &'static str = "crud/update"; } impl, A: Into> From> for Ipld { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index d0cd8304..8270d96e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -18,8 +18,12 @@ use crate::{ time::Timestamp, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use serde::{ + de::{self, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -32,6 +36,7 @@ pub struct Payload { /// /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? pub delegated_ability: D, + pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -80,31 +85,150 @@ where } } -impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload -where - Payload: TryFrom, - as TryFrom>::Error: Debug, +// FIXME +use crate::ability::command::ParseAbility; + +impl< + 'de, + T: ParseAbility + Deserialize<'de> + ToCommand, + C: Condition + TryFrom + Deserialize<'de>, + > Deserialize<'de> for Payload { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - InternalDeserializer::deserialize(d).and_then(|s| { - s.try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) // FIXME better error - }) - } -} + struct DelegationPayloadVisitor(std::marker::PhantomData<(T, C)>); + + const FIELDS: &'static [&'static str] = &[ + "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", + ]; + + impl< + 'de, + T: Deserialize<'de> + ParseAbility + ToCommand, + C: Condition + TryFrom + Deserialize<'de>, + > Visitor<'de> for DelegationPayloadVisitor + { + type Value = Payload; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct delegation::Payload") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut issuer = None; + let mut subject = None; + let mut audience = None; + let mut command = None; + let mut arguments = None; + let mut conditions = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut not_before = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "sub" => { + if subject.is_some() { + return Err(de::Error::duplicate_field("sub")); + } + subject = Some(map.next_value()?); + } + "aud" => { + if audience.is_some() { + return Err(de::Error::duplicate_field("aud")); + } + audience = Some(map.next_value()?); + } + "cmd" => { + if command.is_some() { + return Err(de::Error::duplicate_field("cmd")); + } + command = Some(map.next_value()?); + } + "args" => { + if arguments.is_some() { + return Err(de::Error::duplicate_field("args")); + } + arguments = Some(map.next_value()?); + } + "cond" => { + if conditions.is_some() { + return Err(de::Error::duplicate_field("cond")); + } + conditions = Some(map.next_value()?); + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "exp" => { + if expiration.is_some() { + return Err(de::Error::duplicate_field("exp")); + } + expiration = Some(map.next_value()?); + } + "nbf" => { + if not_before.is_some() { + return Err(de::Error::duplicate_field("nbf")); + } + not_before = Some(map.next_value()?); + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } -impl TryFrom for Payload -where - Payload: TryFrom, -{ - type Error = (); // FIXME + let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; + let args = arguments.ok_or(de::Error::missing_field("args"))?; + + let delegated_ability = ::try_parse(cmd.as_str(), &args) + .map_err(|e| { + de::Error::custom(format!( + "Unable to parse ability field for {} because {}", + cmd, e + )) + })?; + + Ok(Payload { + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + subject: subject.ok_or(de::Error::missing_field("sub"))?, + audience: audience.ok_or(de::Error::missing_field("aud"))?, + delegated_ability, + conditions: conditions.ok_or(de::Error::missing_field("cond"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, + expiration: expiration.ok_or(de::Error::missing_field("exp"))?, + not_before, + }) + } + } - fn try_from(ipld: Ipld) -> Result { - let s: InternalDeserializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; // FIXME - s.try_into().map_err(|_| ()) // FIXME + deserializer.deserialize_struct( + "DelegationPayload", + FIELDS, + DelegationPayloadVisitor(Default::default()), + ) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b7fcdd48..39c89c3f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,6 +1,6 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments, command::Command}, + ability::{arguments, command::ToCommand}, capsule::Capsule, did::Did, nonce::Nonce, @@ -196,14 +196,14 @@ impl TryFrom for InternalSerializer { // } // } -impl>> From> for InternalSerializer { +impl>> From> for InternalSerializer { fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - command: T::COMMAND.into(), + command: payload.ability.to_command(), arguments: payload.ability.into(), proofs: payload.proofs, From 3588c293059f9453bdfc9323032aa41da84db5b0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 00:38:32 -0800 Subject: [PATCH 129/234] Lost of docs, wired invocation into the delegation checker --- Cargo.toml | 1 + src/ability.rs | 1 - src/ability/command.rs | 7 +- src/ability/crud/parents.rs | 5 +- src/ability/dynamic.rs | 24 ++- src/capsule.rs | 2 +- src/delegation/payload.rs | 309 +++++++++++++++++++----------------- src/delegation/store.rs | 217 +++++++++++++++++-------- src/did.rs | 6 +- src/invocation.rs | 9 +- src/invocation/payload.rs | 140 +++++++++++++--- src/ipld.rs | 2 +- src/lib.rs | 4 + src/nonce.rs | 4 +- src/number.rs | 2 +- src/proof.rs | 2 +- src/proof/checkable.rs | 4 +- src/proof/parentful.rs | 9 ++ src/proof/parentless.rs | 14 +- src/reader.rs | 28 ++-- src/receipt.rs | 9 +- src/receipt/payload.rs | 254 +++++++++++++++++++---------- src/receipt/receipt.rs | 4 - src/receipt/responds.rs | 11 ++ src/receipt/store.rs | 4 + src/signature.rs | 6 + src/task.rs | 2 +- src/time.rs | 21 ++- src/url.rs | 2 +- 29 files changed, 737 insertions(+), 366 deletions(-) delete mode 100644 src/receipt/receipt.rs diff --git a/Cargo.toml b/Cargo.toml index 4f2a9d83..4c558ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multibase = "0.9" multihash = { version = "0.18", features = ["sha2"] } +nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } diff --git a/src/ability.rs b/src/ability.rs index 105a1bd6..f52c7966 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -48,7 +48,6 @@ pub mod command; pub mod js; // // TODO move to crate::wasm? or hide behind "dynamic" feature flag? -#[cfg(target_arch = "wasm32")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/command.rs b/src/ability/command.rs index ec220822..30eca578 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -55,8 +55,10 @@ pub trait Command { const COMMAND: &'static str; } +// FIXME definitely needs a better name pub trait ParseAbility: Sized { type Error: fmt::Display; + // FIXME rename this trait to Ability? fn try_parse(cmd: &str, args: &arguments::Named) -> Result; } @@ -89,7 +91,10 @@ where // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] -pub(crate) trait ToCommand { +// FIXME ^^^^ NOT ANYMORE +// Either that needs to be re-locked down, or (because it's all abstract anyways) +// just note that you probably don;t want this one. +pub trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 884b64fc..6c2f106f 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -6,7 +6,7 @@ use super::error::ParentError; use crate::{ - ability::command::Command, + ability::command::{ParseAbility, ToCommand}, proof::{parents::CheckParents, same::CheckSame}, }; use serde::{Deserialize, Serialize}; @@ -63,9 +63,6 @@ pub enum MutableParents { Mutate(super::Mutate), } -// FIXME -use crate::ability::command::{ParseAbility, ToCommand}; - impl ToCommand for MutableParents { fn to_command(&self) -> String { match self { diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index e4584d13..b8f58f56 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,13 +1,20 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support -use super::{arguments, command::ToCommand}; +use super::{ + arguments, + command::{ParseAbility, ToCommand}, +}; use crate::{ipld, proof::same::CheckSame}; -use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, convert::Infallible, fmt::Debug}; + +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use js_sys; + // NOTE the lack of checking functions! /// A "dynamic" ability with the bare minimum of statics @@ -35,6 +42,17 @@ pub struct Dynamic { pub args: arguments::Named, } +impl ParseAbility for Dynamic { + type Error = Infallible; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Dynamic { + cmd: cmd.to_string(), + args: args.clone(), + }) + } +} + impl ToCommand for Dynamic { fn to_command(&self) -> String { self.cmd.clone() diff --git a/src/capsule.rs b/src/capsule.rs index 2b4a9e25..af2f1b2e 100644 --- a/src/capsule.rs +++ b/src/capsule.rs @@ -1,4 +1,4 @@ -//! Capsule type utilities +//! Capsule type utilities. //! //! Capsule types are a pattern where you associate a string to type, //! and use the tag as a key and the payload as a value in a map. diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8270d96e..75647b9d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ ability::{ arguments, - command::{Command, ToCommand}, + command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, did::Did, @@ -26,25 +26,95 @@ use serde::{ use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; +/// The payload portion of a [`Delegation`][super::Delegation]. +/// +/// This contains the semantic information about the delegation, including the +/// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq)] pub struct Payload { - pub issuer: Did, + /// The subject of the [`Delegation`]. + /// + /// This role *must* have issued the earlier (root) + /// delegation in the chain. This makes the chains + /// self-certifying. + /// + /// The semantics of the delegation are established + /// by the subject. + /// + /// [`Delegation`]: super::Delegation pub subject: Did, + + /// The issuer of the [`Delegation`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Delegation`]. + /// + /// [`Delegation`]: super::Delegation + pub issuer: Did, + + /// The agent being delegated to. pub audience: Did, /// A delegatable ability chain. /// - /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? + /// Note that this should be is some [Proof::Hierarchy] pub delegated_ability: D, - pub conditions: Vec, // FIXME BTreeSet? + /// Any [`Condition`]s on the `delegated_ability`. + pub conditions: Vec, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + /// [`Cid`]: libipld_core::cid::Cid ; pub nonce: Nonce, + /// The latest wall-clock time that the UCAN is valid until, + /// given as a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub expiration: Timestamp, + + /// An optional earliest wall-clock time that the UCAN is valid from, + /// given as a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub not_before: Option, } +impl Payload { + pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + delegated_ability: f(self.delegated_ability), + conditions: self.conditions, + metadata: self.metadata, + nonce: self.nonce, + expiration: self.expiration, + not_before: self.not_before, + } + } + + pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + delegated_ability: self.delegated_ability, + conditions: self.conditions.into_iter().map(f).collect(), + metadata: self.metadata, + nonce: self.nonce, + expiration: self.expiration, + not_before: self.not_before, + } + } +} + impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } @@ -62,7 +132,13 @@ where state.serialize_field("iss", &self.issuer)?; state.serialize_field("sub", &self.subject)?; state.serialize_field("aud", &self.audience)?; + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("exp", &self.expiration)?; + state.serialize_field("nbf", &self.not_before)?; + state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field( "args", &arguments::Named::from(self.delegated_ability.clone()), @@ -77,17 +153,10 @@ where .collect::>(), )?; - state.serialize_field("meta", &self.metadata)?; - state.serialize_field("nonce", &self.nonce)?; - state.serialize_field("exp", &self.expiration)?; - state.serialize_field("nbf", &self.not_before)?; state.end() } } -// FIXME -use crate::ability::command::ParseAbility; - impl< 'de, T: ParseAbility + Deserialize<'de> + ToCommand, @@ -106,7 +175,7 @@ impl< impl< 'de, - T: Deserialize<'de> + ParseAbility + ToCommand, + T: ParseAbility + ToCommand + Deserialize<'de>, C: Condition + TryFrom + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor { @@ -214,11 +283,11 @@ impl< issuer: issuer.ok_or(de::Error::missing_field("iss"))?, subject: subject.ok_or(de::Error::missing_field("sub"))?, audience: audience.ok_or(de::Error::missing_field("aud"))?, - delegated_ability, conditions: conditions.ok_or(de::Error::missing_field("cond"))?, metadata: metadata.ok_or(de::Error::missing_field("meta"))?, nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, expiration: expiration.ok_or(de::Error::missing_field("exp"))?, + delegated_ability, not_before, }) } @@ -232,161 +301,117 @@ impl< } } +impl< + T: ParseAbility + Command + for<'de> Deserialize<'de>, + C: Condition + for<'de> Deserialize<'de>, + > TryFrom for Payload +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } -impl<'a, T, C: Condition> Payload { - pub fn check( - delegated: &'a Payload, // FIXME promisory version - proofs: Vec>, +impl Payload { + pub fn check<'a>( + &'a self, + proofs: Vec>, now: SystemTime, - ) -> Result<(), DelegationError<::Error>> + ) -> Result<(), DelegationError<::Error>> where - T: Checkable + CheckSame + Clone + Prove + Into>, + T::Hierarchy: Clone + Into>, + T: Clone + Checkable + Prove + Into>, { - let start: Acc = Acc { - issuer: delegated.issuer.clone(), - subject: delegated.subject.clone(), - hierarchy: delegated.delegated_ability.clone(), + let start: Acc = Acc { + issuer: self.issuer.clone(), + subject: self.subject.clone(), + hierarchy: T::Hierarchy::from(self.delegated_ability.clone()), }; - let args: arguments::Named = delegated.delegated_ability.clone().into(); - - proofs - .into_iter() - .fold(Ok(start), |prev, proof| { - if let Ok(prev_) = prev { - step(&prev_, &proof, &args, now).map(move |success| { - match success { - Success::ProvenByAny => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: prev_.hierarchy, - }, - Success::Proven => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: proof.delegated_ability.clone(), // FIXME double check - }, - } - }) - } else { - prev - } - }) - .map(|_| ()) + let args: arguments::Named = self.delegated_ability.clone().into(); + + proofs.into_iter().fold(Ok(start), |prev, proof| { + if let Ok(prev_) = prev { + prev_.step(&proof, &args, now).map(move |success| { + match success { + Success::ProvenByAny => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: prev_.hierarchy, + }, + Success::Proven => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: proof.delegated_ability.clone(), // FIXME double check + }, + } + }) + } else { + prev + } + })?; + + Ok(()) } } #[derive(Debug, Clone)] -struct Acc { +struct Acc { issuer: Did, subject: Did, - hierarchy: T, + hierarchy: H, } -// FIXME this should move to Delegatable? -fn step<'a, T: Prove + Clone + Into>, C: Condition>( - prev: &Acc, - proof: &Payload, - args: &arguments::Named, - now: SystemTime, -) -> Result::Error>> { - if let Err(_) = prev.issuer.check_same(&proof.audience) { - return Err(EnvelopeError::InvalidSubject.into()); - } - - if let Err(_) = prev.subject.check_same(&proof.subject) { - return Err(EnvelopeError::MisalignedIssAud.into()); - } - - if SystemTime::from(proof.expiration.clone()) > now { - return Err(EnvelopeError::Expired.into()); - } - - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > now { - return Err(EnvelopeError::NotYetValid.into()); +impl Acc { + // FIXME this should move to Delegatable? + fn step<'a, C: Condition>( + &self, + proof: &Payload, + args: &arguments::Named, + now: SystemTime, + ) -> Result::Error>> + where + H: Prove + Clone + Into>, + { + if let Err(_) = self.issuer.check_same(&proof.audience) { + return Err(EnvelopeError::InvalidSubject.into()); } - } - // FIXME coudl be more efficient, but sets need Ord and we have floats - for c in proof.conditions.iter() { - // FIXME revisit - if !c.validate(&args) || !c.validate(&prev.hierarchy.clone().into()) { - return Err(DelegationError::FailedCondition); + if let Err(_) = self.subject.check_same(&proof.subject) { + return Err(EnvelopeError::MisalignedIssAud.into()); } - } - - prev.hierarchy - .check(&proof.delegated_ability.clone()) - .map_err(DelegationError::SemanticError) -} -#[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalDeserializer { - #[serde(rename = "iss")] - issuer: Did, - #[serde(rename = "sub")] - subject: Did, - #[serde(rename = "aud")] - audience: Did, - - #[serde(rename = "cmd")] - command: String, - #[serde(rename = "args")] - arguments: arguments::Named, - #[serde(rename = "cond")] - conditions: Vec, - - #[serde(rename = "nonce")] - nonce: Nonce, - #[serde(rename = "meta")] - metadata: BTreeMap, - - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - not_before: Option, - #[serde(rename = "exp")] - expiration: Timestamp, -} - -impl>, C: Condition + From> - TryFrom for Payload -{ - type Error = (); // FIXME - - fn try_from(d: InternalDeserializer) -> Result { - let p: Self = Payload { - issuer: d.issuer, - subject: d.subject, - audience: d.audience, - - delegated_ability: d.arguments.try_into().map_err(|_| ())?, // d.command.into(), - conditions: d.conditions.into_iter().map(|c| c.into()).collect(), - - metadata: d.metadata, - nonce: d.nonce, - - not_before: d.not_before, - expiration: d.expiration, - }; - - if p.delegated_ability.to_command() != d.command { - return Err(()); + if SystemTime::from(proof.expiration.clone()) > now { + return Err(EnvelopeError::Expired.into()); } - Ok(p) - } -} + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > now { + return Err(EnvelopeError::NotYetValid.into()); + } + } -impl TryFrom for InternalDeserializer { - type Error = SerdeError; + // This could be more efficient (dedup) with sets, but floats don't Ord :( + for c in proof.conditions.iter() { + // Validate both current & proof integrity. + // This should have the same semantic guarantees as looking at subsets, + // but for all known conditions will run much faster on average. + // Plz let me know if I got this wrong. + // —@expede + if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { + return Err(DelegationError::FailedCondition); + } + } - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + self.hierarchy + .check(&proof.delegated_ability.clone()) + .map_err(DelegationError::SemanticError) } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 1680cdb5..f6c5c4c2 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,83 +1,166 @@ use super::{condition::Condition, delegatable::Delegatable, Delegation}; use crate::did::Did; use libipld_core::cid::Cid; +use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use web_time::SystemTime; -// NOTE can already look up by CID in other traits -pub trait IndexedStore { - type Error; +pub trait Store { + fn insert(&mut self, cid: &Cid, delegation: Delegation); + fn revoke(&mut self, cid: &Cid); - fn get_by(query: Query) -> Result>, Self::Error>; + fn get_chain( + &self, + aud: &Did, + subject: &Did, + now: &SystemTime, + ) -> Option)>>; +} - fn previously_checked(cid: Cid) -> Result; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// A simple in-memory store for delegations. +/// +/// The store is laid out as follows: +/// +/// `{Subject => {Audience => {Cid => Delegation}}}` +/// +/// ```mermaid +/// flowchart LR +/// subgraph Subjects +/// direction TB +/// +/// Akiko +/// Boris +/// Carol +/// +/// subgraph aud[Boris's Audiences] +/// direction TB +/// +/// Denzel +/// Erin +/// Frida +/// Georgia +/// Hugo +/// +/// subgraph cid[Frida's CIDs] +/// direction LR +/// +/// CID1 --> Delegation1 +/// CID2 --> Delegation2 +/// CID3 --> Delegation3 +/// end +/// end +/// end +/// +/// Akiko ~~~ Hugo +/// Carol ~~~ Hugo +/// Boris --> Frida --> CID2 +/// +/// Boris -.-> Denzel +/// Boris -.-> Erin +/// Boris -.-> Georgia +/// Boris -.-> Hugo +/// +/// Frida -.-> CID1 +/// Frida -.-> CID3 +/// +/// style Boris stroke:orange; +/// style Frida stroke:orange; +/// style CID2 stroke:orange; +/// style Delegation2 stroke:orange; +/// +/// linkStyle 5 stroke:orange; +/// linkStyle 6 stroke:orange; +/// linkStyle 1 stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, + revocations: BTreeSet, +} - // NOTE you can override this with something much more efficient in e.g. SQL - // FIXME "should" be checked and indexed on the way in - fn chains_for( - &self, - subject: Did, - command: String, - audience: Did, - ) -> Result)>>, Self::Error>; - // if let Ok(possible) = self.get_by(Query { - // audience: Some(audience), - // command: Some(command), - // after_not_before: Some(SystemTime::now()), - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) { - // let acc = Ok(vec![]); - // let iss = possible.iter().next().unwrap().1.issuer; - - // // FIXME actually more complex than this: - // // sicne the chain also has to be valid - // // ...we shoud probably index on the way in - // while acc.is_ok() { - // if let Ok(latest) = get_one(Query { - // subject: Some(subject), - // command: Some(command), - // audience: Some(latest_iss), - // after_not_before: Some(SystemTime::now()), - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) { - // acc.push(latest); - - // if delegation. - // latest_iss = delegation.issuer; - // } else { - // acc = Err(()); // FIXME - // } - // } - // } - - fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { - todo!() - //let mut results = Self::get_by(query)?; - //results.pop().ok_or_else(|_| todo!()) +use std::ops::ControlFlow; + +// FIXME check that UCAN is valid +impl Store for MemoryStore { + fn insert(&mut self, cid: &Cid, delegation: Delegation) { + self.index + .entry(delegation.payload.subject.clone()) + .or_default() + .entry(delegation.payload.audience.clone()) + .or_default() + .insert(cid.clone()); + + self.ucans.insert(cid.clone(), delegation); } - fn expired(&self) -> Result>, Self::Error> { - todo!() - // self.get_by(Query { - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) + fn revoke(&mut self, cid: &Cid) { + self.revocations.insert(cid.clone()); } -} -#[derive(Default, Debug, Clone, PartialEq)] -pub struct Query { - pub subject: Option, - pub command: Option, - pub issuer: Option, - pub audience: Option, + fn get_chain( + &self, + aud: &Did, + subject: &Did, + now: &SystemTime, + ) -> Option)>> { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } + + let mut status = Status::Looking; + let mut target_aud = aud; + let mut chain = vec![]; - pub prior_to_not_before: Option, // FIXME time - pub after_not_before: Option, // FIXME time + let delegation_subtree = self + .index + .get(subject) + .and_then(|aud_map| aud_map.get(aud))?; - pub expires_before: Option, // FIXME time - pub expires_aftre: Option, // FIXME time + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if self.revocations.contains(&cid) { + return ControlFlow::Continue(()); + } + + if let Some(d) = self.ucans.get(cid) { + if SystemTime::from(d.payload.expiration.clone()) < *now { + return ControlFlow::Continue(()); + } + + if let Some(nbf) = &d.payload.not_before { + if SystemTime::from(nbf.clone()) > *now { + return ControlFlow::Continue(()); + } + } + + chain.push((cid, d)); + + if &d.payload.issuer == subject { + status = Status::Complete; + } else { + target_aud = &d.payload.issuer; + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; + } + } + + match status { + Status::Complete => NonEmpty::from_vec(chain), + _ => None, + } + } } diff --git a/src/did.rs b/src/did.rs index c5312722..10ddb45e 100644 --- a/src/did.rs +++ b/src/did.rs @@ -1,4 +1,6 @@ -//! Decentralized Identifier (DID) utilities +//! Decentralized Identifier ([DID][wiki]) utilities. +//! +//! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier use did_url::DID; use libipld_core::ipld::Ipld; @@ -6,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; use thiserror::Error; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(into = "String", try_from = "String")] /// A [Decentralized Identifier (DID)][wiki] /// diff --git a/src/invocation.rs b/src/invocation.rs index d217ca07..e93aa57a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,9 +4,16 @@ mod serializer; pub mod promise; -pub use payload::{Payload, Unresolved}; +pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; use crate::signature; +/// The complete, signed [`invocation::Payload`][Payload]. +/// +/// # Promises +/// +/// For a version that can include [`Promise`][promise::Promise]s, +/// wrap your `T` in [`invocation::Promised`](Promised) to get +/// `Invocation>`. pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 39c89c3f..216fab5a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,19 +2,28 @@ use super::resolvable::Resolvable; use crate::{ ability::{arguments, command::ToCommand}, capsule::Capsule, + delegation, + delegation::{ + condition::Condition, + error::{DelegationError, EnvelopeError}, + Delegatable, + }, did::Did, nonce::Nonce, + proof::{ + checkable::Checkable, + prove::{Prove, Success}, + same::CheckSame, + }, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData}; +use web_time::SystemTime; -// FIXME this version should not be resolvable... -// FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { - // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, pub audience: Option, @@ -30,29 +39,116 @@ pub struct Payload { pub expiration: Timestamp, } -// FIXME To TaskId - -// NOTE This is the version that accepts promises -pub type Unresolved = Payload<::Promised>; -// type Dynamic = Payload; <- ? - -// FIXME parser for both versions -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(untagged)] -// pub enum MaybeResolved + Into> -// where -// Payload: From, -// Unresolved: From, -// T::Promised: Clone + Command + Debug + PartialEq, -// { -// Resolved(Payload), -// Unresolved(Unresolved), -// } +// FIXME cleanup traits +// one idea, because they keep comingup together: put hierarchy and builder on the same +// trair (as associated tyeps) to klet us skip the ::bulder::hierarchy indirection. +// +// This probably means putting the delegation T back to the upper level and bieng explicit about +// the T::Builder in the type +impl Payload { + pub fn check( + self, + proofs: Vec::Hierarchy, C>>, + now: SystemTime, + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + where + T: Delegatable, + T::Builder: Clone + Checkable + Prove + Into>, + ::Hierarchy: Clone + Into>, + { + let builder_payload: delegation::Payload = self.into(); + builder_payload.check(proofs, now) + } +} impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } +impl From> for delegation::Payload { + fn from(payload: Payload) -> Self { + delegation::Payload { + issuer: payload.issuer, + subject: payload.subject.clone(), + audience: payload.audience.unwrap_or(payload.subject), + + delegated_ability: T::Builder::from(payload.ability), + conditions: vec![], + + metadata: payload.metadata, + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, + } + } +} + +impl> From> for arguments::Named { + fn from(payload: Payload) -> Self { + let mut args = arguments::Named::from_iter([ + ("iss".into(), payload.issuer.into()), + ("sub".into(), payload.subject.into()), + ("cmd".into(), payload.ability.to_command().into()), + ("args".into(), payload.ability.into()), + ( + "prf".into(), + Ipld::List(payload.proofs.iter().map(Into::into).collect()), + ), + ("nonce".into(), payload.nonce.into()), + ("exp".into(), payload.expiration.into()), + ]); + + if let Some(audience) = payload.audience { + args.insert("aud".into(), audience.into()); + } + + if let Some(not_before) = payload.not_before { + args.insert("nbf".into(), not_before.into()); + } + + args + } +} + +/// A variant that accepts [`Promise`]s. +/// +/// [`Promise`]: crate::invocation::promise::Promise +pub type Promised = Payload<::Promised>; + +impl Resolvable for Payload +where + arguments::Named: From, + Ipld: From, + T::Promised: ToCommand, +{ + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match ::try_resolve(promised.ability) { + Ok(resolved_ability) => Ok(Payload { + issuer: promised.issuer, + subject: promised.subject, + audience: promised.audience, + + ability: resolved_ability, + + proofs: promised.proofs, + cause: promised.cause, + metadata: promised.metadata, + nonce: promised.nonce, + + not_before: promised.not_before, + expiration: promised.expiration, + }), + Err(promised_ability) => Err(Payload { + ability: promised_ability, + ..promised + }), + } + } +} + impl Serialize for Payload where Payload: Clone, diff --git a/src/ipld.rs b/src/ipld.rs index bec0bc37..d492e4da 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld] +//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld]. mod enriched; mod newtype; diff --git a/src/lib.rs b/src/lib.rs index ab83b718..8c7b0437 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,10 @@ pub mod task; pub mod time; pub mod url; +pub use delegation::Delegation; +pub use invocation::Invocation; +pub use receipt::Receipt; + // FIXME consider a fact-system // /// The empty fact // #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/nonce.rs b/src/nonce.rs index c2837684..1cee2fc3 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -1,4 +1,6 @@ -//! Nonce utilities +//! [Nonce]s & utilities. +//! +//! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce use enum_as_inner::EnumAsInner; use getrandom::getrandom; diff --git a/src/number.rs b/src/number.rs index 1c38f6b5..bfa72fa5 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`] numerics +//! Helpers for working with [`Ipld`] numerics. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/proof.rs b/src/proof.rs index c0b401a7..a80cd781 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,4 +1,4 @@ -//! Proof chains, checking, and utilities +//! Proof chains, checking, and utilities. pub mod checkable; pub mod error; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index ee855eae..952efc5d 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -5,11 +5,11 @@ use super::{prove::Prove, same::CheckSame}; // FIXME mo ve to Delegatbel? /// Plug a type into the delegation checking pipeline -pub trait Checkable: CheckSame { +pub trait Checkable: CheckSame + Sized { /// The type of hierarchy this ability has /// /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove; + type Hierarchy: CheckSame + Prove + From; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 54b29938..11a859ab 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -28,6 +28,15 @@ pub enum Parentful { This(T), } +impl From for Parentful +where + T: CheckParents, +{ + fn from(this: T) -> Self { + Parentful::This(this) + } +} + /// Error cases when checking proofs (including parents) #[derive(Debug, Error, PartialEq)] pub enum ParentfulError { diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 4e3659fa..2400b756 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -24,6 +24,12 @@ pub enum Parentless { This(T), } +impl From for Parentless { + fn from(this: T) -> Self { + Parentless::This(this) + } +} + // FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? /// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] @@ -56,14 +62,6 @@ where } } -impl + DeserializeOwned> TryFrom for Parentless { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - impl CheckSame for Parentless { type Error = ParentlessError; diff --git a/src/reader.rs b/src/reader.rs index 7ba78b78..81e43ca3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,10 +1,12 @@ -//! Configure & attach an ambient environment to a value +//! Configure & attach an ambient environment to a value. use crate::{ - ability::{arguments, command::ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegatable, - invocation::{promise, Resolvable}, - proof::{checkable::Checkable, same::CheckSame}, + invocation::Resolvable, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -158,19 +160,23 @@ impl>> From> for arguments::N } } -impl Checkable for Reader -where - Reader: CheckSame, -{ - type Hierarchy = Env::Hierarchy; -} - impl ToCommand for Reader { fn to_command(&self) -> String { self.env.to_command() } } +impl ParseAbility for Reader { + type Error = ParseAbilityError<::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Reader { + env: Default::default(), + val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + }) + } +} + /// A helper newtype that marks a value as being a [`Delegatable::Builder`]. /// /// The is often used as: diff --git a/src/receipt.rs b/src/receipt.rs index 1b5284cf..497ebc76 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,9 +1,14 @@ +//! The (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. + mod payload; -mod receipt; mod responds; mod store; pub use payload::Payload; -pub use receipt::Receipt; pub use responds::Responds; pub use store::Store; + +use crate::signature; + +/// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index a76939c5..4e6a654b 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,69 +1,207 @@ +//! The payload (non-signature) portion of a response from an [`Invocation`]. +//! +//! [`Invocation`]: crate::invocation::Invocation + use super::responds::Responds; use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; - -// FIXME serialize/deseialize split out for when the T has implementations - +use serde::{ + de::{self, DeserializeOwned, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt, fmt::Debug}; + +/// The payload (non-signature) portion of a response from an [`Invocation`]. +/// +/// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] pub struct Payload { + /// The issuer of the [`Receipt`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Receipt`]. + /// + /// [`Receipt`]: super::Receipt pub issuer: Did, + /// The [`Cid`] of the [`Invocation`] that was run. + /// + /// [`Invocation`]: crate::invocation::Invocation pub ran: Cid, + + /// The output of the [`Invocation`]. + /// + /// This is always of the form `{"ok": ...}` or `{"err": ...}`. + /// + /// [`Invocation`]: crate::invocation::Invocation pub out: Result>, + + /// Any further [`Invocation`]s that the `ran` [`Invocation`] + /// requested to be queued next. + /// + /// [`Invocation`]: crate::invocation::Invocation pub next: Vec, // FIXME rename here or in spec? + /// An optional proof chain authorizing a different [`Did`] to + /// be the receipt `iss` than the audience (or subject) of the + /// [`Invocation`] that was run. + /// + /// [`Invocation`]: crate::invocation::Invocation pub proofs: Vec, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + /// [`Cid`]: libipld_core::cid::Cid pub nonce: Nonce, + + /// An optional [Unix timestamp] (wall-clock time) at which the + /// receipt claims to have been issued at. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub issued_at: Option, } impl Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - T::Success: Serialize + DeserializeOwned, + T::Success: Serialize, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from((*self).clone()); // FIXME kill that clone with tons of refs? - serde::Serialize::serialize(&s, serializer) + let mut state = serializer.serialize_struct("receipt::Payload", 8)?; + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("ran", &self.ran)?; + state.serialize_field("out", &self.out)?; + state.serialize_field("next", &self.next)?; + state.serialize_field("prf", &self.proofs)?; + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("iat", &self.issued_at)?; + + state.end() } } -impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +impl<'de, T: Responds> Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, - T::Success: DeserializeOwned + Serialize, + T::Success: Deserialize<'de>, { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => Ok(s.into()), + struct ReceiptPayloadVisitor(std::marker::PhantomData); + + const FIELDS: &'static [&'static str] = + &["iss", "ran", "out", "next", "prf", "meta", "nonce", "iat"]; + + impl<'de, T: Responds> Visitor<'de> for ReceiptPayloadVisitor + where + T::Success: Deserialize<'de>, + { + type Value = Payload; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct delegation::Payload") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut issuer = None; + let mut ran = None; + let mut out = None; + let mut next = None; + let mut proofs = None; + let mut metadata = None; + let mut nonce = None; + let mut issued_at = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "ran" => { + if ran.is_some() { + return Err(de::Error::duplicate_field("ran")); + } + ran = Some(map.next_value()?); + } + "out" => { + if out.is_some() { + return Err(de::Error::duplicate_field("out")); + } + out = Some(map.next_value()?); + } + "next" => { + if next.is_some() { + return Err(de::Error::duplicate_field("next")); + } + next = Some(map.next_value()?); + } + "prf" => { + if proofs.is_some() { + return Err(de::Error::duplicate_field("prf")); + } + proofs = Some(map.next_value()?); + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "iat" => { + if issued_at.is_some() { + return Err(de::Error::duplicate_field("iat")); + } + issued_at = map.next_value()?; + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } + + Ok(Payload { + issuer: issuer.ok_or_else(|| de::Error::missing_field("iss"))?, + ran: ran.ok_or_else(|| de::Error::missing_field("ran"))?, + out: out.ok_or_else(|| de::Error::missing_field("out"))?, + next: next.ok_or_else(|| de::Error::missing_field("next"))?, + proofs: proofs.ok_or_else(|| de::Error::missing_field("prf"))?, + metadata: metadata.ok_or_else(|| de::Error::missing_field("meta"))?, + nonce: nonce.ok_or_else(|| de::Error::missing_field("nonce"))?, + issued_at, + }) + } } - } -} - -impl TryFrom for Payload -where - T::Success: Serialize + DeserializeOwned, -{ - type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld)?; - Ok(s.into()) + deserializer.deserialize_struct( + "ReceiptPayload", + FIELDS, + ReceiptPayloadVisitor(Default::default()), + ) } } @@ -73,61 +211,13 @@ impl From> for Ipld { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalSerializer -where - T::Success: Serialize + DeserializeOwned, -{ - #[serde(rename = "iss")] - issuer: Did, - - ran: Cid, - out: Result>, - next: Vec, // FIXME rename here or in spec? - - #[serde(rename = "prf")] - proofs: Vec, - #[serde(rename = "meta")] - metadata: BTreeMap, - - nonce: Nonce, - #[serde(rename = "iat")] - issued_at: Option, -} - -impl From> for Payload +impl TryFrom for Payload where - T::Success: Serialize + DeserializeOwned, + T::Success: DeserializeOwned, { - fn from(s: InternalSerializer) -> Self { - Payload { - issuer: s.issuer, - ran: s.ran, - out: s.out, - next: s.next, - proofs: s.proofs, - metadata: s.metadata, - nonce: s.nonce, - issued_at: s.issued_at, - } - } -} + type Error = SerdeError; -impl From> for InternalSerializer -where - T::Success: Serialize + DeserializeOwned, -{ - fn from(s: Payload) -> Self { - InternalSerializer { - issuer: s.issuer, - ran: s.ran, - out: s.out, - next: s.next, - proofs: s.proofs, - metadata: s.metadata.into(), - nonce: s.nonce, - issued_at: s.issued_at, - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs deleted file mode 100644 index dfd1db01..00000000 --- a/src/receipt/receipt.rs +++ /dev/null @@ -1,4 +0,0 @@ -use super::payload::Payload; -use crate::signature; - -pub type Receipt = signature::Envelope>; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index e35f5d60..dace3436 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,10 +1,21 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; +/// Describe the relationship between an ability and the [`Receipt`]s. +/// +/// This is used for constucting [`Receipt`]s, and indexing them for +/// reverse lookup. +/// +/// [`Receipt`]: crate::receipt::Receipt pub trait Responds { + /// The successful return type for running `Self`. type Success; + /// Convert an Ability (`Self`) into a [`Task`]. + /// + /// This is used to index receipts by a minimal [`Id`]. fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + /// Convert an Ability (`Self`) directly into a [`Task`]'s [`Id`]. fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { task::Id { cid: self.to_task(subject, nonce).into(), diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 75cd0691..66feb0d9 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -2,13 +2,17 @@ use super::{Receipt, Responds}; use crate::task; use libipld_core::ipld::Ipld; +/// A store for [`Receipt`]s indexed by their [`task::Id`]s. pub trait Store { + /// The error type representing all the ways a store operation can fail. type Error; + /// Retrieve a [`Receipt`] by its [`task::Id`]. fn get(id: task::Id) -> Result, Self::Error> where ::Success: TryFrom; + /// Store a [`Receipt`] by its [`task::Id`]. fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; diff --git a/src/signature.rs b/src/signature.rs index 392a9f9a..322aac62 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,11 +1,17 @@ +//! Signatures and cryptographic envelopes. + use crate::capsule::Capsule; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq)] pub struct Envelope { + /// The signture of the `payload`. pub sig: Signature, + + /// The payload that's being signed over. pub payload: T, } diff --git a/src/task.rs b/src/task.rs index 6e80ca88..f41cbc54 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,4 +1,4 @@ -//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup +//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. use crate::{ability::arguments, did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; diff --git a/src/time.rs b/src/time.rs index 37a4b838..50a8e95c 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,4 +1,4 @@ -//! Time utilities +//! Time utilities. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -30,6 +30,19 @@ pub enum Timestamp { Postel(SystemTime), } +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.into(), + Timestamp::Postel(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + impl Serialize for Timestamp { fn serialize(&self, serializer: S) -> Result where @@ -70,12 +83,6 @@ impl From for SystemTime { } } -impl From for Ipld { - fn from(timestamp: Timestamp) -> Self { - timestamp.into() - } -} - impl TryFrom for Timestamp { type Error = SerdeError; diff --git a/src/url.rs b/src/url.rs index a69b78bc..b97d9b64 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,4 +1,4 @@ -//! URL utilities +//! URL utilities. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; From 4f3468f5fc88aa048ce17d34d7f3ed2dba279fcf Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 17:45:55 -0800 Subject: [PATCH 130/234] Save before trait reorg --- src/ability/command.rs | 2 +- src/ability/dynamic.rs | 2 ++ src/ability/ucan/revoke.rs | 2 +- src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 5 ++--- src/delegation/payload.rs | 9 +++------ src/delegation/store.rs | 31 +++++++++++++++++++++++------- src/delegation/traits.rs | 5 ----- src/proof/prove.rs | 2 +- src/reader.rs | 4 ---- src/receipt/responds.rs | 1 + 11 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 src/delegation/traits.rs diff --git a/src/ability/command.rs b/src/ability/command.rs index 30eca578..18458fc9 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -83,7 +83,7 @@ where return Err(ParseAbilityError::UnknownCommand); } - Ipld::from(args.clone()) + Ipld::Map(args.0.clone()) .try_into() .map_err(ParseAbilityError::InvalidArgs) } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b8f58f56..64e7af1e 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -17,6 +17,8 @@ use js_sys; // NOTE the lack of checking functions! +// FIXME make a NOTE somewhere that Hierarchy is availavle on ability/js/... + /// A "dynamic" ability with the bare minimum of statics /// ///

diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index bf45a63d..6f9d3fd8 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -25,7 +25,7 @@ impl Command for Generic { /// The fully resolved variant: ready to execute. pub type Ready = Generic; -impl NoParents for Ready {} +impl NoParents for Builder {} impl Delegatable for Ready { type Builder = Builder; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 7ff605e8..632d5615 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -4,7 +4,7 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; /// A trait for conditions that can be run on named IPLD arguments. -pub trait Condition: TryFrom + Into { +pub trait Condition { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 7dbbd2bd..4bf4fde9 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,10 +1,9 @@ -use crate::ability::arguments; +use crate::{ability::arguments, proof::checkable::Checkable}; use libipld_core::ipld::Ipld; // FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more - /// FIXME require CheckSame? - type Builder: TryInto + From + Into>; + type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 75647b9d..be73086e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -157,11 +157,8 @@ where } } -impl< - 'de, - T: ParseAbility + Deserialize<'de> + ToCommand, - C: Condition + TryFrom + Deserialize<'de>, - > Deserialize<'de> for Payload +impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deserialize<'de>> + Deserialize<'de> for Payload { fn deserialize(deserializer: D) -> Result where @@ -176,7 +173,7 @@ impl< impl< 'de, T: ParseAbility + ToCommand + Deserialize<'de>, - C: Condition + TryFrom + Deserialize<'de>, + C: Condition + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor { type Value = Payload; diff --git a/src/delegation/store.rs b/src/delegation/store.rs index f6c5c4c2..31d8dd8e 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -3,10 +3,18 @@ use crate::did::Did; use libipld_core::cid::Cid; use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; +use thiserror::Error; use web_time::SystemTime; pub trait Store { + type Error; + + fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn insert(&mut self, cid: &Cid, delegation: Delegation); fn revoke(&mut self, cid: &Cid); @@ -15,7 +23,7 @@ pub trait Store { aud: &Did, subject: &Did, now: &SystemTime, - ) -> Option)>>; + ) -> Result)>, Self::Error>; } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -81,10 +89,14 @@ pub struct MemoryStore { revocations: BTreeSet, } -use std::ops::ControlFlow; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; // FIXME check that UCAN is valid impl Store for MemoryStore { + type Error = NotFound; + fn insert(&mut self, cid: &Cid, delegation: Delegation) { self.index .entry(delegation.payload.subject.clone()) @@ -100,12 +112,16 @@ impl Store for MemoryStore { self.revocations.insert(cid.clone()); } + fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(NotFound) + } + fn get_chain( &self, aud: &Did, subject: &Did, now: &SystemTime, - ) -> Option)>> { + ) -> Result)>, NotFound> { #[derive(PartialEq)] enum Status { Complete, @@ -120,7 +136,8 @@ impl Store for MemoryStore { let delegation_subtree = self .index .get(subject) - .and_then(|aud_map| aud_map.get(aud))?; + .and_then(|aud_map| aud_map.get(aud)) + .ok_or(NotFound)?; while status == Status::Looking { let found = delegation_subtree.iter().try_for_each(|cid| { @@ -159,8 +176,8 @@ impl Store for MemoryStore { } match status { - Status::Complete => NonEmpty::from_vec(chain), - _ => None, + Status::Complete => NonEmpty::from_vec(chain).ok_or(NotFound), + _ => Err(NotFound), } } } diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs deleted file mode 100644 index 0d102d22..00000000 --- a/src/delegation/traits.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::{ability::arguments, did::Did, nonce::Nonce, task, task::Task}; - -pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; -} diff --git a/src/proof/prove.rs b/src/proof/prove.rs index fbf4d54d..b97589ba 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -3,7 +3,7 @@ use super::internal::Checker; /// An internal trait that checks based on the other traits for an ability type. -pub trait Prove: Checker { +pub(crate) trait Prove: Checker { type Error; fn check(&self, proof: &Self) -> Result; diff --git a/src/reader.rs b/src/reader.rs index 81e43ca3..c918ee35 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -205,10 +205,6 @@ impl From> for Reader> { } } -impl>> Delegatable for Reader { - type Builder = Reader>; -} - /// A helper newtype that marks a value as being a [`Resolvable::Promised`]. /// /// The is often used as: diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index dace3436..4e27ef3a 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,5 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; +use libipld_core::ipld::Ipld; /// Describe the relationship between an ability and the [`Receipt`]s. /// From 1b6892262655f9b69c526215a38f46d9dbaa3950 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 22:43:31 -0800 Subject: [PATCH 131/234] Actually chekc the delegation chain --- src/ability/msg/send.rs | 4 +- src/ability/ucan/proxy.rs | 4 +- src/ability/ucan/revoke.rs | 4 +- src/ability/wasm/run.rs | 4 +- src/agent.rs | 72 +++++++++---------- src/delegation.rs | 30 ++++++-- .../{delegatable.rs => delegable.rs} | 2 +- src/delegation/payload.rs | 46 +++++++++++- src/delegation/store.rs | 72 +++++++++++-------- src/invocation.rs | 2 +- src/invocation/payload.rs | 6 +- src/invocation/serializer.rs | 0 src/invocation/store.rs | 37 ++++++++++ src/proof/prove.rs | 1 + src/proof/same.rs | 1 + src/reader.rs | 4 +- src/receipt.rs | 4 +- src/receipt/store.rs | 35 ++++++++- src/signature.rs | 34 +++++---- src/task.rs | 2 +- 20 files changed, 260 insertions(+), 104 deletions(-) rename src/delegation/{delegatable.rs => delegable.rs} (89%) delete mode 100644 src/invocation/serializer.rs create mode 100644 src/invocation/store.rs diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index b7a63b94..1a0b7538 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, @@ -123,7 +123,7 @@ pub type Builder = Generic, Option, Option>; pub type Promised = Generic, promise::Resolves, promise::Resolves>; -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index 04eb5c33..1aa056c1 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -1,6 +1,6 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::Promise, }; use libipld_core::ipld::Ipld; @@ -26,7 +26,7 @@ impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; } -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 6f9d3fd8..d3da8d3a 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; @@ -27,7 +27,7 @@ pub type Ready = Generic; impl NoParents for Builder {} -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index b32f7880..ab08db22 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,7 +3,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; @@ -31,7 +31,7 @@ impl Command for Generic { /// A variant with all of the required fields filled in pub type Ready = Generic>; -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/agent.rs b/src/agent.rs index 62968faa..336f9988 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,6 +1,6 @@ use crate::{ ability::command::ToCommand, - delegation::{condition::Condition, Delegatable, Delegation}, + delegation::{condition::Condition, Delegable, Delegation}, did::Did, invocation::Invocation, proof::parents::CheckParents, @@ -18,41 +18,41 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( - &self, - delegation: Delegation, - proof_chain: Vec>, // FIXME T must also accept Self and * - ) -> () - where - T::Parents: Delegatable, - { - todo!() - } - - pub fn try_invoke(&self, ability: A) { - todo!() - } - - pub fn revoke( - &self, - delegation: Delegation, - ) -> () -// where -// T::Parents: Delegatable, - { - todo!() - } - - pub fn receive_delegation( - &self, - delegation: Delegation, - ) -> () { - todo!() - } - - pub fn receive_invocation(&self, invocation: Invocation) -> () { - todo!() - } + // pub fn invoke( + // &self, + // delegation: Delegation, + // proof_chain: Vec>, // FIXME T must also accept Self and * + // ) -> () + // where + // T::Parents: Delegable, + // { + // todo!() + // } + + // pub fn try_invoke(&self, ability: A) { + // todo!() + // } + + // pub fn revoke( + // &self, + // delegation: Delegation, + // ) -> () + // // where + // // T::Parents: Delegable, + // { + // todo!() + // } + + // pub fn receive_delegation( + // &self, + // delegation: Delegation, + // ) -> () { + // todo!() + // } + + // pub fn receive_invocation(&self, invocation: Invocation) -> () { + // todo!() + // } // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache } diff --git a/src/delegation.rs b/src/delegation.rs index b3f7259c..7faafc3c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,15 +1,15 @@ -mod delegatable; +mod delegable; mod payload; pub mod condition; pub mod error; pub mod store; -pub use delegatable::Delegatable; +pub use delegable::Delegable; pub use payload::Payload; +use crate::proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}; use condition::Condition; -// use store::IndexedStore; use crate::signature; @@ -21,8 +21,30 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; +impl CheckSame for Delegation { + type Error = ::Error; + + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + self.payload.check_same(&proof.payload) + } +} + +impl CheckParents for Delegation { + type Parents = Delegation; + type ParentError = ::ParentError; + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.payload.check_parent(&proof.payload) + } +} + +// // FIXME relax the checkable constraint for this? Or make this an instance of checker? +// impl>, C> Checkable for Delegation { +// type Hierarchy = Parentful, C>; +// } + // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/delegatable.rs b/src/delegation/delegable.rs similarity index 89% rename from src/delegation/delegatable.rs rename to src/delegation/delegable.rs index 4bf4fde9..eb93ea1d 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegable.rs @@ -2,7 +2,7 @@ use crate::{ability::arguments, proof::checkable::Checkable}; use libipld_core::ipld::Ipld; // FIXME require checkable? -pub trait Delegatable: Sized { +pub trait Delegable: Sized { /// A delegation with some arguments filled /// FIXME add more type Builder: TryInto + From + Checkable; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index be73086e..65998780 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -12,6 +12,7 @@ use crate::{ nonce::Nonce, proof::{ checkable::Checkable, + parents::CheckParents, prove::{Prove, Success}, same::CheckSame, }, @@ -24,6 +25,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; /// The payload portion of a [`Delegation`][super::Delegation]. @@ -113,12 +115,54 @@ impl Payload { not_before: self.not_before, } } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + if SystemTime::from(self.expiration.clone()) < now { + return Err(TimeBoundError::Expired); + } + + if let Some(nbf) = self.not_before.clone() { + if SystemTime::from(nbf) > now { + return Err(TimeBoundError::NotYetValid); + } + } + + Ok(()) + } +} + +// FIXME move to time.rs +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] +pub enum TimeBoundError { + #[error("The UCAN delegation has expired")] + Expired, + + #[error("The UCAN delegation is not yet valid")] + NotYetValid, } impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } +impl CheckSame for Payload { + type Error = ::Error; + + fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { + self.delegated_ability.check_same(&proof.delegated_ability) + } +} + +impl CheckParents for Payload { + type Parents = Payload; + type ParentError = ::ParentError; + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.delegated_ability + .check_parent(&proof.delegated_ability) + } +} + impl Serialize for Payload where Ipld: From, @@ -367,7 +411,7 @@ struct Acc { } impl Acc { - // FIXME this should move to Delegatable? + // FIXME this should move to Delegable? fn step<'a, C: Condition>( &self, proof: &Payload, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 31d8dd8e..bbd2c48d 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,8 +1,10 @@ -use super::{condition::Condition, delegatable::Delegatable, Delegation}; -use crate::did::Did; +use super::{condition::Condition, Delegable, Delegation}; +use crate::{ + did::Did, + proof::{checkable::Checkable, prove::Prove}, +}; use libipld_core::cid::Cid; use nonempty::NonEmpty; -use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, ops::ControlFlow, @@ -10,20 +12,23 @@ use std::{ use thiserror::Error; use web_time::SystemTime; -pub trait Store { +// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? +pub trait Store { type Error; - fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + + fn insert(&mut self, cid: Cid, delegation: Delegation); - fn insert(&mut self, cid: &Cid, delegation: Delegation); - fn revoke(&mut self, cid: &Cid); + fn revoke(&mut self, cid: Cid); fn get_chain( &self, aud: &Did, subject: &Did, + builder: &B, now: &SystemTime, - ) -> Result)>, Self::Error>; + ) -> Result)>, Self::Error>; } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -83,45 +88,52 @@ pub trait Store { /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, +pub struct MemoryStore { + ucans: BTreeMap>, index: BTreeMap>>, revocations: BTreeSet, } +// FIXME extract #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] #[error("Delegation not found")] pub struct NotFound; // FIXME check that UCAN is valid -impl Store for MemoryStore { +impl Store for MemoryStore { type Error = NotFound; - fn insert(&mut self, cid: &Cid, delegation: Delegation) { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(NotFound) + } + + fn insert(&mut self, cid: Cid, delegation: Delegation) { self.index .entry(delegation.payload.subject.clone()) .or_default() .entry(delegation.payload.audience.clone()) .or_default() - .insert(cid.clone()); + .insert(cid); - self.ucans.insert(cid.clone(), delegation); - } + let hierarchy: Delegation = Delegation { + signature: delegation.signature, + payload: delegation.payload.map_ability(Into::into), + }; - fn revoke(&mut self, cid: &Cid) { - self.revocations.insert(cid.clone()); + self.ucans.insert(cid.clone(), hierarchy); } - fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(NotFound) + fn revoke(&mut self, cid: Cid) { + self.revocations.insert(cid); } fn get_chain( &self, aud: &Did, subject: &Did, + builder: &B, now: &SystemTime, - ) -> Result)>, NotFound> { + ) -> Result)>, NotFound> { #[derive(PartialEq)] enum Status { Complete, @@ -129,9 +141,11 @@ impl Store for MemoryStore { NoPath, } + // FIXME move these into an Acc let mut status = Status::Looking; let mut target_aud = aud; let mut chain = vec![]; + let mut args: &B::Hierarchy = &builder.clone().into(); let delegation_subtree = self .index @@ -141,19 +155,19 @@ impl Store for MemoryStore { while status == Status::Looking { let found = delegation_subtree.iter().try_for_each(|cid| { - if self.revocations.contains(&cid) { - return ControlFlow::Continue(()); - } - if let Some(d) = self.ucans.get(cid) { - if SystemTime::from(d.payload.expiration.clone()) < *now { + if self.revocations.contains(&cid) { return ControlFlow::Continue(()); } - if let Some(nbf) = &d.payload.not_before { - if SystemTime::from(nbf.clone()) > *now { - return ControlFlow::Continue(()); - } + if d.payload.check_time(*now).is_err() { + return ControlFlow::Continue(()); + } + + if args.check(&d.payload.delegated_ability).is_ok() { + args = &d.payload.delegated_ability; + } else { + return ControlFlow::Continue(()); } chain.push((cid, d)); diff --git a/src/invocation.rs b/src/invocation.rs index e93aa57a..9cfca04f 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,8 +1,8 @@ mod payload; mod resolvable; -mod serializer; pub mod promise; +pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 216fab5a..310525f6 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,7 +6,7 @@ use crate::{ delegation::{ condition::Condition, error::{DelegationError, EnvelopeError}, - Delegatable, + Delegable, }, did::Did, nonce::Nonce, @@ -52,7 +52,7 @@ impl Payload { now: SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - T: Delegatable, + T: Delegable, T::Builder: Clone + Checkable + Prove + Into>, ::Hierarchy: Clone + Into>, { @@ -65,7 +65,7 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> for delegation::Payload { +impl From> for delegation::Payload { fn from(payload: Payload) -> Self { delegation::Payload { issuer: payload.issuer, diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/invocation/store.rs b/src/invocation/store.rs new file mode 100644 index 00000000..c153cc13 --- /dev/null +++ b/src/invocation/store.rs @@ -0,0 +1,37 @@ +use super::Invocation; +use libipld_core::cid::Cid; +use std::collections::BTreeMap; +use thiserror::Error; + +pub trait Store { + type Error; + + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + + fn has(&self, cid: &Cid) -> Result { + Ok(self.get(cid).is_ok()) + } +} + +pub struct MemoryStore { + store: BTreeMap>, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; + +impl Store for MemoryStore { + type Error = NotFound; + + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { + self.store.get(cid).ok_or(NotFound) + } + + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + self.store.insert(cid, invocation); + Ok(()) + } +} diff --git a/src/proof/prove.rs b/src/proof/prove.rs index b97589ba..9c2a517c 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -6,6 +6,7 @@ use super::internal::Checker; pub(crate) trait Prove: Checker { type Error; + // FIXME make the same as the trait name (prove) fn check(&self, proof: &Self) -> Result; } diff --git a/src/proof/same.rs b/src/proof/same.rs index 2e295b68..5ca43d71 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -47,6 +47,7 @@ pub trait CheckSame { /// it has violated the delegation chain rules. fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } + impl CheckSame for Did { type Error = Unequal; diff --git a/src/reader.rs b/src/reader.rs index c918ee35..c2614569 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -5,7 +5,7 @@ use crate::{ arguments, command::{ParseAbility, ParseAbilityError, ToCommand}, }, - delegation::Delegatable, + delegation::Delegable, invocation::Resolvable, }; use libipld_core::ipld::Ipld; @@ -177,7 +177,7 @@ impl ParseAbility for Reader { } } -/// A helper newtype that marks a value as being a [`Delegatable::Builder`]. +/// A helper newtype that marks a value as being a [`Delegable::Builder`]. /// /// The is often used as: /// diff --git a/src/receipt.rs b/src/receipt.rs index 497ebc76..a76bd704 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -2,11 +2,11 @@ mod payload; mod responds; -mod store; + +pub mod store; pub use payload::Payload; pub use responds::Responds; -pub use store::Store; use crate::signature; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 66feb0d9..6f20edd1 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,6 +1,8 @@ use super::{Receipt, Responds}; use crate::task; use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt}; +use thiserror::Error; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. pub trait Store { @@ -8,12 +10,41 @@ pub trait Store { type Error; /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get(id: task::Id) -> Result, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore +where + T::Success: fmt::Debug + Clone + PartialEq, +{ + store: BTreeMap>, +} + +// FIXME extract +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; + +impl Store for MemoryStore +where + ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, +{ + type Error = NotFound; + + fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { + self.store.get(id).ok_or(NotFound) + } + + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + self.store.insert(id, receipt); + Ok(()) + } +} diff --git a/src/signature.rs b/src/signature.rs index 322aac62..6846004c 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] pub struct Envelope { /// The signture of the `payload`. - pub sig: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, @@ -21,7 +21,7 @@ pub struct Envelope { pub enum Signature { One(Vec), Batch { - sig: Vec, + signature: Vec, merkle_proof: Vec>, }, } @@ -79,13 +79,16 @@ pub enum Signature { // } impl From<&Signature> for Ipld { - fn from(sig: &Signature) -> Self { - match sig { + fn from(signature: &Signature) -> Self { + match signature { Signature::One(sig) => sig.clone().into(), - Signature::Batch { sig, merkle_proof } => { + Signature::Batch { + signature, + merkle_proof, + } => { let mut map = BTreeMap::new(); let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); - map.insert("sig".into(), sig.clone().into()); + map.insert("sig".into(), signature.clone().into()); map.insert("prf".into(), proof.into()); map.into() } @@ -94,13 +97,16 @@ impl From<&Signature> for Ipld { } impl From for Ipld { - fn from(sig: Signature) -> Self { - match sig { + fn from(signature: Signature) -> Self { + match signature { Signature::One(sig) => sig.into(), - Signature::Batch { sig, merkle_proof } => { + Signature::Batch { + signature, + merkle_proof, + } => { let mut map = BTreeMap::new(); let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("prf".into(), proof.into()); map.into() } @@ -110,12 +116,12 @@ impl From for Ipld { // FIXME Store or BTreeMap? Also eliminate that Clone constraint impl + Clone> From<&Envelope> for Ipld { - fn from(Envelope { sig, payload }: &Envelope) -> Self { + fn from(Envelope { signature, payload }: &Envelope) -> Self { let mut inner = BTreeMap::new(); inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link let mut map = BTreeMap::new(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("pld".into(), Ipld::Map(inner)); Ipld::Map(map) @@ -123,12 +129,12 @@ impl + Clone> From<&Envelope> for Ipld { } impl + Clone> From> for Ipld { - fn from(Envelope { sig, payload }: Envelope) -> Self { + fn from(Envelope { signature, payload }: Envelope) -> Self { let mut inner = BTreeMap::new(); inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link let mut map = BTreeMap::new(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("pld".into(), Ipld::Map(inner)); Ipld::Map(map) diff --git a/src/task.rs b/src/task.rs index f41cbc54..ed80fec7 100644 --- a/src/task.rs +++ b/src/task.rs @@ -68,7 +68,7 @@ impl From for Cid { } /// The unique identifier for a [`Task`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { /// The CID of the [`Task`]. From d5e0b134cea3d0dc55cad2ed784687d5a0da32a2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 00:09:06 -0800 Subject: [PATCH 132/234] On to the crypto --- src/agent.rs | 189 +++++++++++++++++++++++++++--------- src/delegation.rs | 28 ------ src/delegation/delegable.rs | 6 +- src/delegation/payload.rs | 31 +++--- src/delegation/store.rs | 8 +- src/invocation/payload.rs | 2 +- src/invocation/store.rs | 1 + src/proof/checkable.rs | 2 +- src/time.rs | 12 +++ 9 files changed, 180 insertions(+), 99 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 336f9988..f245b0e1 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,58 +1,153 @@ use crate::{ - ability::command::ToCommand, + delegation, delegation::{condition::Condition, Delegable, Delegation}, did::Did, - invocation::Invocation, - proof::parents::CheckParents, + invocation, + nonce::Nonce, + proof::checkable::Checkable, + receipt, + receipt::Responds, + time::JsTime, }; +use libipld_core::ipld::Ipld; +use nonempty::NonEmpty; +use std::{collections::BTreeMap, marker::PhantomData}; +use web_time::SystemTime; -pub struct Agent { +// FIXME move proofs to under delegation? + +#[derive(Debug, Clone, PartialEq)] +pub struct Agent< + T: Delegable + Responds, + C: Condition, + S: delegation::store::Store + + invocation::store::Store + + receipt::store::Store, +> { pub did: Did, // pub key: signature::Key, pub store: S, + pub _phantom: PhantomData<(T, C)>, } -impl Agent { - // pub fn delegate(&self, payload: Payload) -> Delegation { - // let signature = self.key.sign(payload); - // signature::Envelope::new(payload, signature) - // } - - // pub fn invoke( - // &self, - // delegation: Delegation, - // proof_chain: Vec>, // FIXME T must also accept Self and * - // ) -> () - // where - // T::Parents: Delegable, - // { - // todo!() - // } - - // pub fn try_invoke(&self, ability: A) { - // todo!() - // } - - // pub fn revoke( - // &self, - // delegation: Delegation, - // ) -> () - // // where - // // T::Parents: Delegable, - // { - // todo!() - // } - - // pub fn receive_delegation( - // &self, - // delegation: Delegation, - // ) -> () { - // todo!() - // } - - // pub fn receive_invocation(&self, invocation: Invocation) -> () { - // todo!() - // } - - // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache +impl< + T: Delegable + Responds, + C: Condition + Clone, + S: delegation::store::Store + + invocation::store::Store + + receipt::store::Store, + > Agent +{ + fn new(did: Did, store: S) -> Self { + Self { + did, + store, + _phantom: PhantomData, + } + } + + pub fn delegate( + &self, + audience: Did, + subject: Did, + ability_builder: T::Builder, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, ()> { + // FIXME check if possible in store first; + + let conditions = if subject == self.did { + new_conditions + } else { + let mut conds = self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(|_| ())? // FIXME + .first() + .1 + .payload + .conditions; + + let mut new = new_conditions; + conds.append(&mut new); + conds + }; + + let mut salt = self.did.clone().to_string().into_bytes(); + + let payload = delegation::Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce: Nonce::generate_16(&mut salt), + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + Ok(self.sign_delegation(payload)) + } + + pub fn sign_delegation( + &self, + payload: delegation::Payload, + ) -> delegation::Delegation { + // FIXME check if possible in store first; + let signature = todo!(); // self.key.sign(payload); + Delegation { payload, signature } + } } + +// impl Agent { +// } +// +// +// +// +// +// pub fn delegate(&self, payload: Payload) -> Delegation { +// let signature = self.key.sign(payload); +// signature::Envelope::new(payload, signature) +// } + +// pub fn invoke( +// &self, +// delegation: Delegation, +// proof_chain: Vec>, // FIXME T must also accept Self and * +// ) -> () +// where +// T::Parents: Delegable, +// { +// todo!() +// } + +// pub fn try_invoke(&self, ability: A) { +// todo!() +// } + +// pub fn revoke( +// &self, +// delegation: Delegation, +// ) -> () +// // where +// // T::Parents: Delegable, +// { +// todo!() +// } + +// pub fn receive_delegation( +// &self, +// delegation: Delegation, +// ) -> () { +// todo!() +// } + +// pub fn receive_invocation(&self, invocation: Invocation) -> () { +// todo!() +// } + +// pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache diff --git a/src/delegation.rs b/src/delegation.rs index 7faafc3c..47d51096 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -37,31 +37,3 @@ impl CheckParents for Delegation { self.payload.check_parent(&proof.payload) } } - -// // FIXME relax the checkable constraint for this? Or make this an instance of checker? -// impl>, C> Checkable for Delegation { -// type Hierarchy = Parentful, C>; -// } - -// FIXME -impl Delegation { - // FIXME include cache - //pub fn check>(&self, store: &S) -> Result<(), ()> { - // if let Ok(is_valid) = store.previously_checked(self) { - // if is_valid { - // return Ok(()); - // } - // } - - // if let Ok(chains) = store.chains_for(self) { - // for chain in chains { - // todo!() - // // if self.check_self(self).is_ok() { - // // return Ok(()); - // // } - // } - // } - - // Err(()) - //} -} diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index eb93ea1d..925aee08 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,9 +1,7 @@ -use crate::{ability::arguments, proof::checkable::Checkable}; -use libipld_core::ipld::Ipld; +use crate::proof::checkable::Checkable; -// FIXME require checkable? pub trait Delegable: Sized { /// A delegation with some arguments filled - /// FIXME add more + /// FIXME add more text type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 65998780..78fac30e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -60,9 +60,9 @@ pub struct Payload { /// A delegatable ability chain. /// /// Note that this should be is some [Proof::Hierarchy] - pub delegated_ability: D, + pub ability_builder: D, - /// Any [`Condition`]s on the `delegated_ability`. + /// Any [`Condition`]s on the `ability_builder`. pub conditions: Vec, /// Extensible, free-form fields. @@ -93,7 +93,7 @@ impl Payload { issuer: self.issuer, subject: self.subject, audience: self.audience, - delegated_ability: f(self.delegated_ability), + ability_builder: f(self.ability_builder), conditions: self.conditions, metadata: self.metadata, nonce: self.nonce, @@ -107,7 +107,7 @@ impl Payload { issuer: self.issuer, subject: self.subject, audience: self.audience, - delegated_ability: self.delegated_ability, + ability_builder: self.ability_builder, conditions: self.conditions.into_iter().map(f).collect(), metadata: self.metadata, nonce: self.nonce, @@ -149,7 +149,7 @@ impl CheckSame for Payload { type Error = ::Error; fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { - self.delegated_ability.check_same(&proof.delegated_ability) + self.ability_builder.check_same(&proof.ability_builder) } } @@ -158,8 +158,7 @@ impl CheckParents for Payload { type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.delegated_ability - .check_parent(&proof.delegated_ability) + self.ability_builder.check_parent(&proof.ability_builder) } } @@ -181,11 +180,11 @@ where state.serialize_field("exp", &self.expiration)?; state.serialize_field("nbf", &self.not_before)?; - state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field("cmd", &self.ability_builder.to_command())?; state.serialize_field( "args", - &arguments::Named::from(self.delegated_ability.clone()), + &arguments::Named::from(self.ability_builder.clone()), )?; state.serialize_field( @@ -312,8 +311,8 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; let args = arguments.ok_or(de::Error::missing_field("args"))?; - let delegated_ability = ::try_parse(cmd.as_str(), &args) - .map_err(|e| { + let ability_builder = + ::try_parse(cmd.as_str(), &args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {} because {}", cmd, e @@ -328,7 +327,7 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria metadata: metadata.ok_or(de::Error::missing_field("meta"))?, nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, expiration: expiration.ok_or(de::Error::missing_field("exp"))?, - delegated_ability, + ability_builder, not_before, }) } @@ -373,10 +372,10 @@ impl Payload { let start: Acc = Acc { issuer: self.issuer.clone(), subject: self.subject.clone(), - hierarchy: T::Hierarchy::from(self.delegated_ability.clone()), + hierarchy: T::Hierarchy::from(self.ability_builder.clone()), }; - let args: arguments::Named = self.delegated_ability.clone().into(); + let args: arguments::Named = self.ability_builder.clone().into(); proofs.into_iter().fold(Ok(start), |prev, proof| { if let Ok(prev_) = prev { @@ -390,7 +389,7 @@ impl Payload { Success::Proven => Acc { issuer: proof.issuer.clone(), subject: proof.subject.clone(), - hierarchy: proof.delegated_ability.clone(), // FIXME double check + hierarchy: proof.ability_builder.clone(), // FIXME double check }, } }) @@ -452,7 +451,7 @@ impl Acc { } self.hierarchy - .check(&proof.delegated_ability.clone()) + .check(&proof.ability_builder.clone()) .map_err(DelegationError::SemanticError) } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index bbd2c48d..38c861d0 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -29,6 +29,10 @@ pub trait Store { builder: &B, now: &SystemTime, ) -> Result)>, Self::Error>; + + fn can_delegate(&self, iss: &Did, aud: &Did, builder: &B, now: &SystemTime) -> bool { + self.get_chain(aud, iss, builder, now).is_ok() + } } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -164,8 +168,8 @@ impl Store for MemoryStore From> for delegation::Payload { } } +#[derive(Debug, Clone, PartialEq)] pub struct MemoryStore { store: BTreeMap>, } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 952efc5d..584c1ee7 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -2,7 +2,7 @@ use super::{prove::Prove, same::CheckSame}; -// FIXME mo ve to Delegatbel? +// FIXME move to Delegatbel? /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame + Sized { diff --git a/src/time.rs b/src/time.rs index 50a8e95c..e697f2fd 100644 --- a/src/time.rs +++ b/src/time.rs @@ -30,6 +30,18 @@ pub enum Timestamp { Postel(SystemTime), } +impl From for Timestamp { + fn from(js_time: JsTime) -> Self { + Timestamp::JsSafe(js_time) + } +} + +impl From for Timestamp { + fn from(sys_time: SystemTime) -> Self { + Timestamp::Postel(sys_time) + } +} + impl From for Ipld { fn from(timestamp: Timestamp) -> Self { match timestamp { From ef394312847a86d88d00be6b880942af7d876579 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 13:39:36 -0800 Subject: [PATCH 133/234] OKay I lied; I wrote the delegation agent instead --- src/agent.rs | 2 + src/delegation.rs | 65 ++++++++++++++-- src/delegation/:50 | 117 ++++++++++++++++++++++++++++ src/delegation/agent.rs | 124 ++++++++++++++++++++++++++++++ src/delegation/payload.rs | 13 +--- src/delegation/store.rs | 157 ++++++++++++++++++++------------------ src/lib.rs | 2 +- src/signature.rs | 15 +++- src/time.rs | 10 +++ 9 files changed, 411 insertions(+), 94 deletions(-) create mode 100644 src/delegation/:50 create mode 100644 src/delegation/agent.rs diff --git a/src/agent.rs b/src/agent.rs index f245b0e1..c26be6c1 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -100,6 +100,8 @@ impl< let signature = todo!(); // self.key.sign(payload); Delegation { payload, signature } } + + pub fn recieve_delegation() {} } // impl Agent { diff --git a/src/delegation.rs b/src/delegation.rs index 47d51096..deebfa8e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,17 +1,26 @@ -mod delegable; -mod payload; - pub mod condition; pub mod error; pub mod store; +mod agent; +mod delegable; +mod payload; + +pub use agent::Agent; pub use delegable::Delegable; pub use payload::Payload; -use crate::proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}; +use crate::{ + did::Did, + nonce::Nonce, + proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + signature, + time::{TimeBoundError, Timestamp}, +}; use condition::Condition; - -use crate::signature; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, convert::AsRef}; +use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -21,6 +30,50 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; +// FIXME checkable -> provable? + +impl Delegation { + pub fn issuer(&self) -> &Did { + &self.payload.issuer + } + + pub fn subject(&self) -> &Did { + &self.payload.subject + } + + pub fn audience(&self) -> &Did { + &self.payload.audience + } + + pub fn ability_builder(&self) -> &B { + &self.payload.ability_builder + } + + pub fn conditions(&self) -> &[C] { + &self.payload.conditions + } + + pub fn metadata(&self) -> &BTreeMap { + &self.payload.metadata + } + + pub fn nonce(&self) -> &Nonce { + &self.payload.nonce + } + + pub fn not_before(&self) -> Option<&Timestamp> { + self.payload.not_before.as_ref() + } + + pub fn expiration(&self) -> &Timestamp { + &self.payload.expiration + } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + self.payload.check_time(now) + } +} + impl CheckSame for Delegation { type Error = ::Error; diff --git a/src/delegation/:50 b/src/delegation/:50 new file mode 100644 index 00000000..c74e50a7 --- /dev/null +++ b/src/delegation/:50 @@ -0,0 +1,117 @@ +use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { + pub did: &'a Did, + pub store: &'a mut S, + _marker: PhantomData<(B, C)>, +} + +// FIXME show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this + +impl<'a, B: Checkable, C: Condition, S: Store> Agent<'a, B, C, S> { + pub fn new(did: &'a Did, store: &'a mut S) -> Self { + Self { + did, + store, + _marker: PhantomData, + } + } + + pub fn delegate( + &self, + cid: &Cid, // FIXME remove and generate from the capsule header? + audience: Did, + subject: Did, + ability_builder: B, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, DelegateError> { + if !self + .store + .can_delegate(self.did, &audience, &ability_builder, &SystemTime::now()) + { + return Err(DelegateError::ProofsNotFound); + } + + let conditions = if subject == *self.did { + new_conditions + } else { + let mut conds = self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(|_| ())? // FIXME + .first() + .1 + .payload + .conditions; + + let mut new = new_conditions; + conds.append(&mut new); + conds + }; + + let mut salt = self.did.clone().to_string().into_bytes(); + + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce: Nonce::generate_16(&mut salt), + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + Ok(self.sign(payload)) + } + + pub fn recieve( + &self, + cid: &Cid, // FIXME remove and generate from the capsule header? + delegation: Delegation, + ) -> Result<(), ReceiveError<'a, >::Error>> { + if self.store.get(cid).is_ok() { + return Ok(()); + } + + if delegation.audience() != *self.did { + return Err(ReceiveError::WrongAudience(delegation.audience())); + } + + delegation + .validate_signature() + .map_err(|_| ReceiveError::InvalidSignature(cid))?; + + self.store + .insert(self.store, delegation) + .map_err(Into::into) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum DelegateError { + #[error("The current agent does not have the necessary proofs to delegate.")] + ProofsNotFound, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum ReceiveError<'a, StoreErr> { + #[error("The current agent ({0}) is not the intended audience of the delegation.")] + WrongAudience(&'a Did), + + #[error("Signature for UCAN with CID {0} is invalid.")] + InvalidSignature(&'a Cid), + + #[error(transparent)] + StoreError(#[from] StoreErr), +} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs new file mode 100644 index 00000000..ba0e8b4c --- /dev/null +++ b/src/delegation/agent.rs @@ -0,0 +1,124 @@ +use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { + pub did: &'a Did, + pub store: &'a mut S, + _marker: PhantomData<(B, C)>, +} + +// FIXME show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this + +impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> { + pub fn new(did: &'a Did, store: &'a mut S) -> Self { + Self { + did, + store, + _marker: PhantomData, + } + } + + pub fn delegate( + &self, + audience: Did, + subject: Did, + ability_builder: B, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, DelegateError<>::Error>> { + let mut salt = self.did.clone().to_string().into_bytes(); + let nonce = Nonce::generate_16(&mut salt); + + if subject == *self.did { + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + conditions: new_conditions, + }; + + // FIXME add signer info + return Ok(Delegation::sign(payload)); + } + + let to_delegate = &self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(DelegateError::StoreError)? + .ok_or(DelegateError::ProofsNotFound)? + .first() + .1 + .payload; + + let mut conditions = to_delegate.conditions.clone(); + conditions.append(&mut new_conditions.clone()); + + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + // FIXME add signing material + Ok(Delegation::sign(payload)) + } + + pub fn recieve( + &mut self, + cid: Cid, // FIXME remove and generate from the capsule header? + delegation: Delegation, + ) -> Result<(), ReceiveError<>::Error>> { + if self.store.get(&cid).is_ok() { + return Ok(()); + } + + if delegation.audience() != self.did { + return Err(ReceiveError::WrongAudience(delegation.audience().clone())); + } + + delegation + .validate_signature() + .map_err(|_| ReceiveError::InvalidSignature(cid))?; + + self.store.insert(cid, delegation).map_err(Into::into) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum DelegateError { + #[error("The current agent does not have the necessary proofs to delegate.")] + ProofsNotFound, + + #[error(transparent)] + StoreError(#[from] StoreErr), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum ReceiveError { + #[error("The current agent ({0}) is not the intended audience of the delegation.")] + WrongAudience(Did), + + #[error("Signature for UCAN with CID {0} is invalid.")] + InvalidSignature(Cid), + + #[error(transparent)] + StoreError(#[from] StoreErr), +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 78fac30e..369e6d5c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -16,7 +16,7 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, - time::Timestamp, + time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ @@ -25,7 +25,6 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; -use thiserror::Error; use web_time::SystemTime; /// The payload portion of a [`Delegation`][super::Delegation]. @@ -131,16 +130,6 @@ impl Payload { } } -// FIXME move to time.rs -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] -pub enum TimeBoundError { - #[error("The UCAN delegation has expired")] - Expired, - - #[error("The UCAN delegation is not yet valid")] - NotYetValid, -} - impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 38c861d0..b0010146 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,4 +1,4 @@ -use super::{condition::Condition, Delegable, Delegation}; +use super::{condition::Condition, Delegation}; use crate::{ did::Did, proof::{checkable::Checkable, prove::Prove}, @@ -7,20 +7,22 @@ use libipld_core::cid::Cid; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, + convert::Infallible, ops::ControlFlow, }; -use thiserror::Error; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get(&self, cid: &Cid) -> Result>, Self::Error>; - fn insert(&mut self, cid: Cid, delegation: Delegation); + // FIXME add a variant that calculated the CID from the capsulre header? + // FIXME that means changing the name to insert_by_cid or similar + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; - fn revoke(&mut self, cid: Cid); + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( &self, @@ -28,10 +30,17 @@ pub trait Store { subject: &Did, builder: &B, now: &SystemTime, - ) -> Result)>, Self::Error>; + ) -> Result)>>, Self::Error>; - fn can_delegate(&self, iss: &Did, aud: &Did, builder: &B, now: &SystemTime) -> bool { - self.get_chain(aud, iss, builder, now).is_ok() + fn can_delegate( + &self, + iss: &Did, + aud: &Did, + builder: &B, + now: &SystemTime, + ) -> Result { + self.get_chain(aud, iss, builder, now) + .map(|chain| chain.is_some()) } } @@ -98,20 +107,18 @@ pub struct MemoryStore { revocations: BTreeSet, } -// FIXME extract -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - // FIXME check that UCAN is valid -impl Store for MemoryStore { - type Error = NotFound; - - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(NotFound) +impl Store for MemoryStore +where + B::Hierarchy: PartialEq, +{ + type Error = Infallible; + + fn get(&self, cid: &Cid) -> Result>, Self::Error> { + Ok(self.ucans.get(cid)) } - fn insert(&mut self, cid: Cid, delegation: Delegation) { + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index .entry(delegation.payload.subject.clone()) .or_default() @@ -125,10 +132,12 @@ impl Store for MemoryStore Result<(), Self::Error> { self.revocations.insert(cid); + Ok(()) } fn get_chain( @@ -137,65 +146,65 @@ impl Store for MemoryStore Result)>, NotFound> { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } - - // FIXME move these into an Acc - let mut status = Status::Looking; - let mut target_aud = aud; - let mut chain = vec![]; - let mut args: &B::Hierarchy = &builder.clone().into(); - - let delegation_subtree = self - .index - .get(subject) - .and_then(|aud_map| aud_map.get(aud)) - .ok_or(NotFound)?; - - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(&cid) { - return ControlFlow::Continue(()); - } - - if d.payload.check_time(*now).is_err() { - return ControlFlow::Continue(()); - } - - if args.check(&d.payload.ability_builder).is_ok() { - args = &d.payload.ability_builder; - } else { - return ControlFlow::Continue(()); - } - - chain.push((cid, d)); + ) -> Result)>>, Self::Error> { + match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + None => Ok(None), + Some(delegation_subtree) => { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } - if &d.payload.issuer == subject { - status = Status::Complete; - } else { - target_aud = &d.payload.issuer; + let mut status = Status::Looking; + let mut target_aud = aud; + let mut args = &B::Hierarchy::from(builder.clone()); + let mut chain = vec![]; + + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if let Some(d) = self.ucans.get(cid) { + if self.revocations.contains(cid) { + return ControlFlow::Continue(()); + } + + if d.payload.check_time(*now).is_err() { + return ControlFlow::Continue(()); + } + + target_aud = &d.payload.audience; + + if args.check(&d.payload.ability_builder).is_ok() { + args = &d.payload.ability_builder; + } else { + return ControlFlow::Continue(()); + } + + chain.push((cid, d)); + + if &d.payload.issuer == subject { + status = Status::Complete; + } else { + target_aud = &d.payload.issuer; + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; } - - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) } - }); - if found.is_continue() { - status = Status::NoPath; + match status { + Status::Complete => Ok(NonEmpty::from_vec(chain)), + _ => Ok(None), + } } } - - match status { - Status::Complete => NonEmpty::from_vec(chain).ok_or(NotFound), - _ => Err(NotFound), - } } } diff --git a/src/lib.rs b/src/lib.rs index 8c7b0437..c74d1b11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ extern crate alloc; // } pub mod ability; -pub mod agent; +// pub mod agent; FIXME put back? pub mod capsule; pub mod delegation; pub mod did; diff --git a/src/signature.rs b/src/signature.rs index 6846004c..6eb2bfc1 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Envelope { /// The signture of the `payload`. pub signature: Signature, @@ -15,6 +15,19 @@ pub struct Envelope { pub payload: T, } +impl Envelope { + // FIXME need key material + pub fn sign(payload: T) -> Envelope { + // FIXME + todo!() + } + + pub fn validate_signature(&self) -> Result<(), ()> { + // FIXME + todo!() + } +} + // FIXME consider kicking Batch down the road for spec reasons? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] diff --git a/src/time.rs b/src/time.rs index e697f2fd..03101e98 100644 --- a/src/time.rs +++ b/src/time.rs @@ -206,3 +206,13 @@ impl fmt::Display for OutOfRangeError { write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) } } + +// FIXME move to time.rs +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] +pub enum TimeBoundError { + #[error("The UCAN delegation has expired")] + Expired, + + #[error("The UCAN delegation is not yet valid")] + NotYetValid, +} From 5314d827bbf537444df1b4e55a65276e950ad314 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 23:15:51 -0800 Subject: [PATCH 134/234] DID Key crypto --- Cargo.toml | 29 +++++++-- src/crypto.rs | 18 +++++- src/crypto/bls.rs | 115 --------------------------------- src/crypto/bls12381.rs | 5 ++ src/crypto/bls12381/error.rs | 43 ++++++++++++ src/crypto/bls12381/min_pk.rs | 53 +++++++++++++++ src/crypto/bls12381/min_sig.rs | 53 +++++++++++++++ src/crypto/domain_separator.rs | 3 + src/crypto/p521.rs | 30 +++++++++ src/crypto/rs256.rs | 72 ++++++++++++++++----- src/crypto/rs512.rs | 64 ++++++++++++++++++ src/did.rs | 2 + src/did/dns.rs | 1 + src/did/key.rs | 57 ++++++++++++++++ src/did/key/traits.rs | 80 +++++++++++++++++++++++ src/did_verifier/did_key.rs | 8 +-- src/lib.rs | 1 + 17 files changed, 491 insertions(+), 143 deletions(-) delete mode 100644 src/crypto/bls.rs create mode 100644 src/crypto/bls12381.rs create mode 100644 src/crypto/bls12381/error.rs create mode 100644 src/crypto/bls12381/min_pk.rs create mode 100644 src/crypto/bls12381/min_sig.rs create mode 100644 src/crypto/domain_separator.rs create mode 100644 src/crypto/p521.rs create mode 100644 src/crypto/rs512.rs create mode 100644 src/did/dns.rs create mode 100644 src/did/key.rs create mode 100644 src/did/key/traits.rs diff --git a/Cargo.toml b/Cargo.toml index 4c558ec0..d653a035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ cid = "0.11" did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" -ecdsa = { version = "0.16.8", optional = true, default-features = false } +ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } enum-as-inner = "0.6" @@ -54,12 +54,12 @@ libipld-cbor = "0.16" multibase = "0.9" multihash = { version = "0.18", features = ["sha2"] } nonempty = { version = "0.9" } -p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } -p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.0", optional = true, default-features = false } +p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } proptest = { version = "1.1", optional = true } regex = "1.10" -rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } +rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" @@ -108,9 +108,22 @@ default = [ "es384-verifier", "ps256-verifier", "rs256-verifier", + "rs512-verifier", + "es512-verifier", + "bls-verifier", + # FIXME the below while debugging + "es256", + "es256k", + "es384", + "es512", + "rs256", + "rs512", + "eddsa", + "bls" ] + test_utils = ["proptest"] -did-key = [] +did-key = [] # FIXME remove? eddsa = ["dep:ed25519", "dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] @@ -118,7 +131,10 @@ es384 = ["dep:p384"] es512 = ["dep:ecdsa", "dep:p521"] ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] +rs512 = ["dep:rsa"] bls = ["dep:blst"] +# FIXME rename for varsig? +# FIXME actually remove these since they got ported to traits eddsa-verifier = ["eddsa"] es256-verifier = ["es256"] es256k-verifier = ["es256k"] @@ -126,6 +142,7 @@ es384-verifier = ["es384"] es512-verifier = ["es512"] ps256-verifier = ["ps256"] rs256-verifier = ["rs256"] +rs512-verifier = ["rs512"] bls-verifier = ["bls"] mermaid_docs = ["aquamarine"] diff --git a/src/crypto.rs b/src/crypto.rs index 75009dbb..18fecc8a 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,23 +2,39 @@ use signature::SignatureEncoding; +pub mod domain_separator; + #[cfg(feature = "bls")] -pub mod bls; +pub mod bls12381; + +#[cfg(feature = "es512")] +pub mod p521; + #[cfg(feature = "eddsa")] pub mod eddsa; + #[cfg(feature = "es256")] pub mod es256; + #[cfg(feature = "es256k")] pub mod es256k; + #[cfg(feature = "es384")] pub mod es384; + #[cfg(feature = "es512")] pub mod es512; + #[cfg(feature = "ps256")] pub mod ps256; + #[cfg(feature = "rs256")] pub mod rs256; +#[cfg(feature = "rs512")] +pub mod rs512; + +// FIXME switch to varsig /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { /// The algorithm name under JWS diff --git a/src/crypto/bls.rs b/src/crypto/bls.rs deleted file mode 100644 index f7362b62..00000000 --- a/src/crypto/bls.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! BLS12-381 signature support - -use anyhow::anyhow; -use blst::BLST_ERROR; -use signature::SignatureEncoding; - -use super::JWSSignature; - -/// A BLS12-381 G1 signature -#[derive(Debug, Clone)] -pub struct Bls12381G1Sha256SswuRoNulSignature(pub blst::min_sig::Signature); - -impl<'a> TryFrom<&'a [u8]> for Bls12381G1Sha256SswuRoNulSignature { - type Error = BLST_ERROR; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self(blst::min_sig::Signature::uncompress(bytes)?)) - } -} - -impl From for [u8; 48] { - fn from(sig: Bls12381G1Sha256SswuRoNulSignature) -> Self { - sig.0.compress() - } -} - -impl SignatureEncoding for Bls12381G1Sha256SswuRoNulSignature { - type Repr = [u8; 48]; -} - -impl JWSSignature for Bls12381G1Sha256SswuRoNulSignature { - const ALGORITHM: &'static str = "Bls12381G1"; -} - -/// A BLS12-381 G2 signature -#[derive(Debug, Clone)] -pub struct Bls12381G2Sha256SswuRoNulSignature(pub blst::min_pk::Signature); - -impl<'a> TryFrom<&'a [u8]> for Bls12381G2Sha256SswuRoNulSignature { - type Error = BLST_ERROR; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self(blst::min_pk::Signature::uncompress(bytes)?)) - } -} - -impl From for [u8; 96] { - fn from(sig: Bls12381G2Sha256SswuRoNulSignature) -> Self { - sig.0.compress() - } -} - -impl SignatureEncoding for Bls12381G2Sha256SswuRoNulSignature { - type Repr = [u8; 96]; -} - -impl JWSSignature for Bls12381G2Sha256SswuRoNulSignature { - const ALGORITHM: &'static str = "Bls12381G2"; -} - -/// A verifier for BLS12-381 G1 signatures -#[cfg(feature = "bls-verifier")] -pub fn bls_12_381_g1_sha256_sswu_ro_nul_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { - let dst = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; - let aug = &[]; - - let key = - blst::min_sig::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; - - let signature = blst::min_sig::Signature::uncompress(signature) - .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; - - match signature.verify(true, payload, dst, aug, &key, true) { - BLST_ERROR::BLST_SUCCESS => Ok(()), - BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), - BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), - BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), - BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), - BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), - BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), - BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), - } -} - -/// A verifier for BLS12-381 G2 signatures -#[cfg(feature = "bls-verifier")] -pub fn bls_12_381_g2_sha256_sswu_ro_nul_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { - let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; - let aug = &[]; - - let key = - blst::min_pk::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; - - let signature = blst::min_pk::Signature::uncompress(signature) - .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; - - match signature.verify(true, payload, dst, aug, &key, true) { - BLST_ERROR::BLST_SUCCESS => Ok(()), - BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), - BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), - BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), - BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), - BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), - BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), - BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), - } -} diff --git a/src/crypto/bls12381.rs b/src/crypto/bls12381.rs new file mode 100644 index 00000000..2ad56f6f --- /dev/null +++ b/src/crypto/bls12381.rs @@ -0,0 +1,5 @@ +//! BLS12-381 signature support + +pub mod error; +pub mod min_pk; +pub mod min_sig; diff --git a/src/crypto/bls12381/error.rs b/src/crypto/bls12381/error.rs new file mode 100644 index 00000000..7af72c19 --- /dev/null +++ b/src/crypto/bls12381/error.rs @@ -0,0 +1,43 @@ +use blst::BLST_ERROR; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +pub enum VerificationError { + #[error("signature mismatch")] + VerifyMsgFail, + + #[error("bad encoding")] + BadEncoding, + + #[error("point not on curve")] + PointNotOnCurve, + + #[error("bad point not in group")] + PointNotInGroup, + + #[error("aggregate type mismatch")] + AggrTypeMismatch, + + #[error("public key is infinity")] + PkIsInfinity, + + #[error("bad scalar")] + BadScalar, +} + +impl TryFrom for VerificationError { + type Error = (); + + fn try_from(err: BLST_ERROR) -> Result { + match err { + BLST_ERROR::BLST_SUCCESS => Err(()), + BLST_ERROR::BLST_VERIFY_FAIL => Ok(VerificationError::VerifyMsgFail), + BLST_ERROR::BLST_BAD_ENCODING => Ok(VerificationError::BadEncoding), + BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Ok(VerificationError::PointNotOnCurve), + BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Ok(VerificationError::PointNotInGroup), + BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Ok(VerificationError::AggrTypeMismatch), + BLST_ERROR::BLST_PK_IS_INFINITY => Ok(VerificationError::PkIsInfinity), + BLST_ERROR::BLST_BAD_SCALAR => Ok(VerificationError::BadScalar), + } + } +} diff --git a/src/crypto/bls12381/min_pk.rs b/src/crypto/bls12381/min_pk.rs new file mode 100644 index 00000000..fde8887e --- /dev/null +++ b/src/crypto/bls12381/min_pk.rs @@ -0,0 +1,53 @@ +use super::error::VerificationError; +use crate::crypto::domain_separator::DomainSeparator; +use blst::BLST_ERROR; +use signature::{SignatureEncoding, Signer, Verifier}; + +/// A BLS12-381 MinPubKey signature +#[derive(Debug, Clone)] +pub struct Signature(pub blst::min_pk::Signature); + +impl DomainSeparator for Signature { + /// From the [IETF BLS Signature Spec](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-4.2.1) + const DST: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_pk::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 96] { + fn from(sig: Signature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Signature { + type Repr = [u8; 96]; +} + +impl Signer for blst::min_pk::SecretKey { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signature(self.sign(msg, Signature::DST, &[]))) + } +} + +impl Verifier for blst::min_pk::PublicKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match VerificationError::try_from(signature.0.verify( + true, + msg, + Signature::DST, + &[], + &self, + true, + )) { + Ok(err) => Err(signature::Error::from_source(err)), + Err(_) => Ok(()), + } + } +} diff --git a/src/crypto/bls12381/min_sig.rs b/src/crypto/bls12381/min_sig.rs new file mode 100644 index 00000000..121ac66d --- /dev/null +++ b/src/crypto/bls12381/min_sig.rs @@ -0,0 +1,53 @@ +use super::error::VerificationError; +use crate::crypto::domain_separator::DomainSeparator; +use blst::BLST_ERROR; +use signature::{SignatureEncoding, Signer, Verifier}; + +/// A BLS12-381 MinSig signature +#[derive(Debug, Clone)] +pub struct Signature(pub blst::min_sig::Signature); + +impl DomainSeparator for Signature { + /// From the [IETF BLS Signature Spec](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-4.2.1) + const DST: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_sig::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 48] { + fn from(sig: Signature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Signature { + type Repr = [u8; 48]; +} + +impl Signer for blst::min_sig::SecretKey { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signature(self.sign(msg, Signature::DST, &[]))) + } +} + +impl Verifier for blst::min_sig::PublicKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match VerificationError::try_from(signature.0.verify( + true, + msg, + Signature::DST, + &[], + &self, + true, + )) { + Ok(err) => Err(signature::Error::from_source(err)), + Err(_) => Ok(()), + } + } +} diff --git a/src/crypto/domain_separator.rs b/src/crypto/domain_separator.rs new file mode 100644 index 00000000..718aabc2 --- /dev/null +++ b/src/crypto/domain_separator.rs @@ -0,0 +1,3 @@ +pub trait DomainSeparator { + const DST: &'static [u8]; +} diff --git a/src/crypto/p521.rs b/src/crypto/p521.rs new file mode 100644 index 00000000..79d09d29 --- /dev/null +++ b/src/crypto/p521.rs @@ -0,0 +1,30 @@ +use p521; +use signature::Verifier; +use std::fmt; + +#[derive(Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); + +impl fmt::Debug for VerifyingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VerifyingKey").finish() + } +} + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_encoded_point(true) == other.0.to_encoded_point(true) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify( + &self, + msg: &[u8], + signature: &p521::ecdsa::Signature, + ) -> Result<(), signature::Error> { + self.0.verify(msg, &signature) + } +} diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index 9f39c1ff..419093d2 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -1,27 +1,65 @@ //! RS256 signature support -#[cfg(feature = "rs256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "rs256-verifier")] -use signature::Verifier; +use rsa; +use signature::{SignatureEncoding, Signer, Verifier}; -use super::JWSSignature; +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); -impl JWSSignature for rsa::pkcs1v15::Signature { - const ALGORITHM: &'static str = "RS256"; +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + // FIXME yikes that clone + rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + } } -/// A verifier for RS256 signatures -#[cfg(feature = "rs256-verifier")] -pub fn rs256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) - .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; +impl Eq for VerifyingKey {} - let key = rsa::pkcs1v15::VerifyingKey::::new(key); +impl Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + self.0.verify(msg, &signature.0) + } +} + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); + +impl Signer for SigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + self.0.try_sign(msg).map(Signature) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +pub struct Signature(pub rsa::pkcs1v15::Signature); + +impl SignatureEncoding for Signature { + type Repr = [u8; 256]; +} + +impl From<[u8; 256]> for Signature { + fn from(bytes: [u8; 256]) -> Self { + Signature( + rsa::pkcs1v15::Signature::try_from(bytes.as_ref()) + .expect("passed in [u8; 256], so should succeed"), + ) + } +} + +impl From for [u8; 256] { + fn from(sig: Signature) -> [u8; 256] { + sig.0 + .to_bytes() + .as_ref() + .try_into() + .expect("Signature should be exactly 256 bytes") + } +} - let signature = rsa::pkcs1v15::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = signature::Error; - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) + fn try_from(bytes: &'a [u8]) -> Result { + rsa::pkcs1v15::Signature::try_from(bytes).map(Signature) + } } diff --git a/src/crypto/rs512.rs b/src/crypto/rs512.rs new file mode 100644 index 00000000..d3af4635 --- /dev/null +++ b/src/crypto/rs512.rs @@ -0,0 +1,64 @@ +//! RS512 signature support + +use rsa; +use signature::{SignatureEncoding, Signer, Verifier}; + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + self.0.verify(msg, &signature.0) + } +} + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); + +impl Signer for SigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + self.0.try_sign(msg).map(Signature) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +pub struct Signature(pub rsa::pkcs1v15::Signature); + +impl SignatureEncoding for Signature { + type Repr = [u8; 512]; +} + +impl From<[u8; 512]> for Signature { + fn from(bytes: [u8; 512]) -> Self { + Signature( + rsa::pkcs1v15::Signature::try_from(bytes.as_ref()) + .expect("passed in [u8; 512], so should succeed"), + ) + } +} + +impl From for [u8; 512] { + fn from(sig: Signature) -> [u8; 512] { + sig.0 + .to_bytes() + .as_ref() + .try_into() + .expect("Signature should be exactly 512 bytes") + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = signature::Error; + + fn try_from(bytes: &'a [u8]) -> Result { + rsa::pkcs1v15::Signature::try_from(bytes).map(Signature) + } +} diff --git a/src/did.rs b/src/did.rs index 10ddb45e..db6059aa 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,6 +2,8 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub mod key; + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; diff --git a/src/did/dns.rs b/src/did/dns.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/did/dns.rs @@ -0,0 +1 @@ + diff --git a/src/did/key.rs b/src/did/key.rs new file mode 100644 index 00000000..5d6c2c2b --- /dev/null +++ b/src/did/key.rs @@ -0,0 +1,57 @@ +//! Support for the `did:key` scheme + +pub mod traits; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Verifier { + #[cfg(feature = "eddsa")] + Ed25519(ed25519_dalek::VerifyingKey), + + #[cfg(feature = "es256k")] + Sedcp256k1(k256::ecdsa::VerifyingKey), + + #[cfg(feature = "es256")] + P256(p256::ecdsa::VerifyingKey), + + #[cfg(feature = "es384")] + P384(p384::ecdsa::VerifyingKey), + + #[cfg(feature = "es512")] + P521(p521::VerifyingKey), + + #[cfg(feature = "rs256")] + Rs256(rs256::VerifyingKey), + + #[cfg(feature = "rs512")] + Rs512(rs512::VerifyingKey), + + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::PublicKey), + + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::PublicKey), +} diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs new file mode 100644 index 00000000..b4548e3e --- /dev/null +++ b/src/did/key/traits.rs @@ -0,0 +1,80 @@ +use crate::crypto::{bls12381, p521, rs256, rs512}; +use ::p521 as ext_p521; +use ed25519_dalek; +use k256; +use p256; +use p384; + +// FIXME +// also: e.g. HSM + +// FIXME when name conflict gone +pub trait DidKey: signature::Verifier { + const BASE58_PREFIX: &'static str; + + type Signer: signature::Signer; + type Signature: signature::SignatureEncoding; +} + +impl DidKey for ed25519_dalek::VerifyingKey { + const BASE58_PREFIX: &'static str = "6Mk"; + + type Signer = ed25519_dalek::SigningKey; + type Signature = ed25519_dalek::Signature; +} + +impl DidKey for p256::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "Dn"; + + type Signer = p256::ecdsa::SigningKey; + type Signature = p256::ecdsa::Signature; +} + +impl DidKey for k256::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "Q3s"; + + type Signer = k256::ecdsa::SigningKey; + type Signature = k256::ecdsa::Signature; +} + +impl DidKey for p384::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "82"; + + type Signer = p384::ecdsa::SigningKey; + type Signature = p384::ecdsa::Signature; +} + +impl DidKey for p521::VerifyingKey { + const BASE58_PREFIX: &'static str = "2J9"; + + type Signer = ext_p521::ecdsa::SigningKey; + type Signature = ext_p521::ecdsa::Signature; +} + +impl DidKey for rs256::VerifyingKey { + const BASE58_PREFIX: &'static str = "4MX"; + + type Signer = rs256::SigningKey; + type Signature = rs256::Signature; +} + +impl DidKey for rs512::VerifyingKey { + const BASE58_PREFIX: &'static str = "zgg"; + + type Signer = rs512::SigningKey; + type Signature = rs512::Signature; +} + +impl DidKey for blst::min_sig::PublicKey { + const BASE58_PREFIX: &'static str = "UC7"; + + type Signer = blst::min_sig::SecretKey; + type Signature = bls12381::min_sig::Signature; +} + +impl DidKey for blst::min_pk::PublicKey { + const BASE58_PREFIX: &'static str = "UC7"; + + type Signer = blst::min_pk::SecretKey; + type Signature = bls12381::min_pk::Signature; +} diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs index 4c2f658f..426df184 100644 --- a/src/did_verifier/did_key.rs +++ b/src/did_verifier/did_key.rs @@ -121,7 +121,7 @@ impl DidVerifier for DidKeyVerifier { MulticodecPubKey::P384Compressed => self .verifier_map .get(&TypeId::of::()), - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] MulticodecPubKey::P521Compressed => self .verifier_map .get(&TypeId::of::>()), @@ -162,7 +162,7 @@ pub enum MulticodecPubKey { #[cfg(feature = "es384")] P384Compressed, /// p521 compressed public key - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] P521Compressed, /// rsa pkcs1 public key #[cfg(feature = "rs256")] @@ -219,7 +219,7 @@ impl MulticodecPubKey { )); } } - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] MulticodecPubKey::P521Compressed => { if pub_key.len() > 67 { return Err(anyhow!( @@ -261,7 +261,7 @@ impl TryFrom for MulticodecPubKey { 0x1200 => Ok(MulticodecPubKey::P256Compressed), #[cfg(feature = "es384")] 0x1201 => Ok(MulticodecPubKey::P384Compressed), - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] 0x1202 => Ok(MulticodecPubKey::P521Compressed), #[cfg(feature = "rs256")] 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), diff --git a/src/lib.rs b/src/lib.rs index c74d1b11..dd97bfe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ extern crate alloc; // } pub mod ability; +pub mod crypto; // pub mod agent; FIXME put back? pub mod capsule; pub mod delegation; From 3f69fc92d67e980d5bfad39ea20ad284f2e8187c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 00:39:28 -0800 Subject: [PATCH 135/234] Parameterized by DID --- src/delegation.rs | 23 +++++---- src/delegation/agent.rs | 28 ++++++----- src/delegation/condition.rs | 30 ++++++------ src/delegation/payload.rs | 78 ++++++++++++++++------------- src/delegation/store.rs | 37 +++++++------- src/did.rs | 89 ++++++++++++++++++++++++++++------ src/invocation.rs | 2 +- src/invocation/payload.rs | 97 ++++++++++++++++++------------------- src/invocation/store.rs | 17 ++++--- src/proof/same.rs | 22 ++++----- src/receipt.rs | 2 +- src/receipt/payload.rs | 26 +++++----- src/receipt/responds.rs | 7 ++- src/receipt/store.rs | 18 +++---- src/task.rs | 4 +- 15 files changed, 275 insertions(+), 205 deletions(-) diff --git a/src/delegation.rs b/src/delegation.rs index deebfa8e..1988b137 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,7 +11,7 @@ pub use delegable::Delegable; pub use payload::Payload; use crate::{ - did::Did, + did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, signature, @@ -28,20 +28,23 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; + +// FIXME attach common abilities, too +pub type Preset = Delegation; // FIXME checkable -> provable? -impl Delegation { - pub fn issuer(&self) -> &Did { +impl Delegation { + pub fn issuer(&self) -> &DID { &self.payload.issuer } - pub fn subject(&self) -> &Did { + pub fn subject(&self) -> &DID { &self.payload.subject } - pub fn audience(&self) -> &Did { + pub fn audience(&self) -> &DID { &self.payload.audience } @@ -74,16 +77,16 @@ impl Delegation { } } -impl CheckSame for Delegation { +impl CheckSame for Delegation { type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { self.payload.check_same(&proof.payload) } } -impl CheckParents for Delegation { - type Parents = Delegation; +impl CheckParents for Delegation { + type Parents = Delegation; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index ba0e8b4c..375fcf53 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,8 +5,8 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; -pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { - pub did: &'a Did, +pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { + pub did: &'a DID, pub store: &'a mut S, _marker: PhantomData<(B, C)>, } @@ -14,8 +14,10 @@ pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { // FIXME show example of multiple hierarchies of "all things accepted" // delegating down to inner versions of this -impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> { - pub fn new(did: &'a Did, store: &'a mut S) -> Self { +impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Store> + Agent<'a, B, C, DID, S> +{ + pub fn new(did: &'a DID, store: &'a mut S) -> Self { Self { did, store, @@ -25,19 +27,19 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> pub fn delegate( &self, - audience: Did, - subject: Did, + audience: DID, + subject: DID, ability_builder: B, new_conditions: Vec, metadata: BTreeMap, expiration: JsTime, not_before: Option, - ) -> Result, DelegateError<>::Error>> { + ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(&mut salt); if subject == *self.did { - let payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -65,7 +67,7 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); - let payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -84,8 +86,8 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> pub fn recieve( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<>::Error>> { + delegation: Delegation, + ) -> Result<(), ReceiveError<>::Error, DID>> { if self.store.get(&cid).is_ok() { return Ok(()); } @@ -112,9 +114,9 @@ pub enum DelegateError { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum ReceiveError { +pub enum ReceiveError { #[error("The current agent ({0}) is not the intended audience of the delegation.")] - WrongAudience(Did), + WrongAudience(DID), #[error("Signature for UCAN with CID {0} is invalid.")] InvalidSignature(Cid), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 6b853128..79746ff1 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -30,7 +30,7 @@ use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] #[allow(missing_docs)] -pub enum Common { +pub enum Preset { ContainsAll(contains_all::ContainsAll), ContainsAny(contains_any::ContainsAny), ContainsKey(contains_key::ContainsKey), @@ -43,13 +43,13 @@ pub enum Common { MatchesRegex(matches_regex::MatchesRegex), } -impl From for Ipld { - fn from(common: Common) -> Self { +impl From for Ipld { + fn from(common: Preset) -> Self { common.into() } } -impl TryFrom for Common { +impl TryFrom for Preset { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -57,19 +57,19 @@ impl TryFrom for Common { } } -impl Condition for Common { +impl Condition for Preset { fn validate(&self, args: &arguments::Named) -> bool { match self { - Common::ContainsAll(c) => c.validate(args), - Common::ContainsAny(c) => c.validate(args), - Common::ContainsKey(c) => c.validate(args), - Common::ExcludesKey(c) => c.validate(args), - Common::ExcludesAll(c) => c.validate(args), - Common::MinLength(c) => c.validate(args), - Common::MaxLength(c) => c.validate(args), - Common::MinNumber(c) => c.validate(args), - Common::MaxNumber(c) => c.validate(args), - Common::MatchesRegex(c) => c.validate(args), + Preset::ContainsAll(c) => c.validate(args), + Preset::ContainsAny(c) => c.validate(args), + Preset::ContainsKey(c) => c.validate(args), + Preset::ExcludesKey(c) => c.validate(args), + Preset::ExcludesAll(c) => c.validate(args), + Preset::MinLength(c) => c.validate(args), + Preset::MaxLength(c) => c.validate(args), + Preset::MinNumber(c) => c.validate(args), + Preset::MaxNumber(c) => c.validate(args), + Preset::MatchesRegex(c) => c.validate(args), } } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 369e6d5c..9e4dfc53 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,6 +8,7 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, + did, did::Did, nonce::Nonce, proof::{ @@ -32,7 +33,7 @@ use web_time::SystemTime; /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -43,7 +44,7 @@ pub struct Payload { /// by the subject. /// /// [`Delegation`]: super::Delegation - pub subject: Did, + pub subject: DID, /// The issuer of the [`Delegation`]. /// @@ -51,10 +52,10 @@ pub struct Payload { /// the outer layer of [`Delegation`]. /// /// [`Delegation`]: super::Delegation - pub issuer: Did, + pub issuer: DID, /// The agent being delegated to. - pub audience: Did, + pub audience: DID, /// A delegatable ability chain. /// @@ -86,8 +87,8 @@ pub struct Payload { pub not_before: Option, } -impl Payload { - pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { +impl Payload { + pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { Payload { issuer: self.issuer, subject: self.subject, @@ -101,7 +102,7 @@ impl Payload { } } - pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { + pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { Payload { issuer: self.issuer, subject: self.subject, @@ -130,20 +131,20 @@ impl Payload { } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl CheckSame for Payload { +impl CheckSame for Payload { type Error = ::Error; - fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { self.ability_builder.check_same(&proof.ability_builder) } } -impl CheckParents for Payload { - type Parents = Payload; +impl CheckParents for Payload { + type Parents = Payload; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { @@ -151,7 +152,8 @@ impl CheckParents for Payload { } } -impl Serialize for Payload +impl Serialize + for Payload where Ipld: From, arguments::Named: From, @@ -161,9 +163,9 @@ where S: Serializer, { let mut state = serializer.serialize_struct("delegation::Payload", 9)?; - state.serialize_field("iss", &self.issuer)?; - state.serialize_field("sub", &self.subject)?; - state.serialize_field("aud", &self.audience)?; + state.serialize_field("iss", &self.issuer.clone().into().to_string())?; + state.serialize_field("sub", &self.subject.clone().into().to_string())?; + state.serialize_field("aud", &self.audience.clone().into().to_string())?; state.serialize_field("meta", &self.metadata)?; state.serialize_field("nonce", &self.nonce)?; state.serialize_field("exp", &self.expiration)?; @@ -189,14 +191,20 @@ where } } -impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deserialize<'de>> - Deserialize<'de> for Payload +impl< + 'de, + T: ParseAbility + Deserialize<'de> + ToCommand, + C: Condition + Deserialize<'de>, + DID: Did + Deserialize<'de>, + > Deserialize<'de> for Payload { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - struct DelegationPayloadVisitor(std::marker::PhantomData<(T, C)>); + struct DelegationPayloadVisitor( + std::marker::PhantomData<(T, C, DID)>, + ); const FIELDS: &'static [&'static str] = &[ "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", @@ -206,9 +214,10 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria 'de, T: ParseAbility + ToCommand + Deserialize<'de>, C: Condition + Deserialize<'de>, - > Visitor<'de> for DelegationPayloadVisitor + DID: Did + Deserialize<'de>, + > Visitor<'de> for DelegationPayloadVisitor { - type Value = Payload; + type Value = Payload; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("struct delegation::Payload") @@ -333,7 +342,8 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria impl< T: ParseAbility + Command + for<'de> Deserialize<'de>, C: Condition + for<'de> Deserialize<'de>, - > TryFrom for Payload + DID: Did + for<'de> Deserialize<'de>, + > TryFrom for Payload { type Error = SerdeError; @@ -342,23 +352,23 @@ impl< } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl Payload { +impl Payload { pub fn check<'a>( &'a self, - proofs: Vec>, + proofs: Vec>, now: SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, T: Clone + Checkable + Prove + Into>, { - let start: Acc = Acc { + let start: Acc = Acc { issuer: self.issuer.clone(), subject: self.subject.clone(), hierarchy: T::Hierarchy::from(self.ability_builder.clone()), @@ -392,28 +402,28 @@ impl Payload { } #[derive(Debug, Clone)] -struct Acc { - issuer: Did, - subject: Did, +struct Acc { + issuer: DID, + subject: DID, hierarchy: H, } -impl Acc { +impl Acc { // FIXME this should move to Delegable? fn step<'a, C: Condition>( &self, - proof: &Payload, + proof: &Payload, args: &arguments::Named, now: SystemTime, ) -> Result::Error>> where H: Prove + Clone + Into>, { - if let Err(_) = self.issuer.check_same(&proof.audience) { + if self.issuer != proof.audience { return Err(EnvelopeError::InvalidSubject.into()); } - if let Err(_) = self.subject.check_same(&proof.subject) { + if self.subject != proof.subject { return Err(EnvelopeError::MisalignedIssAud.into()); } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index b0010146..062ef0f3 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -13,29 +13,29 @@ use std::{ use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { +pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result>, Self::Error>; + fn get(&self, cid: &Cid) -> Result>, Self::Error>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( &self, - aud: &Did, - subject: &Did, + aud: &DID, + subject: &DID, builder: &B, now: &SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; fn can_delegate( &self, - iss: &Did, - aud: &Did, + iss: &DID, + aud: &DID, builder: &B, now: &SystemTime, ) -> Result { @@ -101,24 +101,25 @@ pub trait Store { /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, - index: BTreeMap>>, +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl Store for MemoryStore +impl Store + for MemoryStore where B::Hierarchy: PartialEq, { type Error = Infallible; - fn get(&self, cid: &Cid) -> Result>, Self::Error> { + fn get(&self, cid: &Cid) -> Result>, Self::Error> { Ok(self.ucans.get(cid)) } - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index .entry(delegation.payload.subject.clone()) .or_default() @@ -126,7 +127,7 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = Delegation { + let hierarchy: Delegation = Delegation { signature: delegation.signature, payload: delegation.payload.map_ability(Into::into), }; @@ -142,11 +143,11 @@ where fn get_chain( &self, - aud: &Did, - subject: &Did, + aud: &DID, + subject: &DID, builder: &B, now: &SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { diff --git a/src/did.rs b/src/did.rs index db6059aa..6757cd69 100644 --- a/src/did.rs +++ b/src/did.rs @@ -7,11 +7,49 @@ pub mod key; use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, string::ToString}; use thiserror::Error; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(into = "String", try_from = "String")] +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +pub trait Did: + PartialEq + TryFrom + Into + signature::Verifier +{ + type Signature: signature::SignatureEncoding; +} + +// impl Did for ed25519_dalek::VerifyingKey {} +// +//impl Did for key::Verifier {} +// +pub enum Preset { + // Key(key::Verifier), + // Dns(did_url::DID), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// /// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. @@ -26,49 +64,70 @@ use thiserror::Error; /// ``` /// /// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -pub struct Did(pub DID); +pub struct Newtype(pub DID); + +impl Serialize for Newtype { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + String::from(self.clone()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Newtype { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + Newtype::try_from(string).map_err(serde::de::Error::custom) + } +} -impl From for String { - fn from(did: Did) -> Self { +impl From for String { + fn from(did: Newtype) -> Self { did.0.to_string() } } -impl TryFrom for Did { +impl TryFrom for Newtype { type Error = >::Error; fn try_from(string: String) -> Result { - DID::parse(&string).map(Did) + DID::parse(&string).map(Newtype) } } -impl fmt::Display for Did { +impl fmt::Display for Newtype { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0.to_string()) } } -impl From for Ipld { - fn from(did: Did) -> Self { +impl From for Ipld { + fn from(did: Newtype) -> Self { did.into() } } -impl TryFrom for Did { +impl TryFrom for Newtype { type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { - Ipld::String(string) => Did::try_from(string).map_err(FromIpldError::StructuralError), + Ipld::String(string) => { + Newtype::try_from(string).map_err(FromIpldError::StructuralError) + } other => Err(FromIpldError::NotAnIpldString(other)), } } } -/// Errors that can occur when converting to or from a [`Did`] +/// Errors that can occur when converting to or from a [`Newtype`] #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { - /// Strutural errors in the [`Did`] + /// Strutural errors in the [`Newtype`] #[error(transparent)] StructuralError(#[from] did_url::Error), diff --git a/src/invocation.rs b/src/invocation.rs index 9cfca04f..7a60c059 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -16,4 +16,4 @@ use crate::signature; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 164c894e..b3767f4b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,31 +2,22 @@ use super::resolvable::Resolvable; use crate::{ ability::{arguments, command::ToCommand}, capsule::Capsule, - delegation, - delegation::{ - condition::Condition, - error::{DelegationError, EnvelopeError}, - Delegable, - }, - did::Did, + delegation::{self, condition::Condition, error::DelegationError, Delegable}, + did::{self, Did}, nonce::Nonce, - proof::{ - checkable::Checkable, - prove::{Prove, Success}, - same::CheckSame, - }, + proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: Did, - pub subject: Did, - pub audience: Option, +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, pub ability: T, @@ -45,10 +36,10 @@ pub struct Payload { // // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type -impl Payload { +impl Payload { pub fn check( self, - proofs: Vec::Hierarchy, C>>, + proofs: Vec::Hierarchy, C, DID>>, now: SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where @@ -56,19 +47,21 @@ impl Payload { T::Builder: Clone + Checkable + Prove + Into>, ::Hierarchy: Clone + Into>, { - let builder_payload: delegation::Payload = self.into(); + let builder_payload: delegation::Payload = self.into(); builder_payload.check(proofs, now) } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> for delegation::Payload { - fn from(payload: Payload) -> Self { +impl From> + for delegation::Payload +{ + fn from(payload: Payload) -> Self { delegation::Payload { - issuer: payload.issuer, + issuer: payload.issuer.clone(), subject: payload.subject.clone(), audience: payload.audience.unwrap_or(payload.subject), @@ -84,11 +77,11 @@ impl From> for delegation::Payload> From> for arguments::Named { - fn from(payload: Payload) -> Self { +impl, DID: Did> From> for arguments::Named { + fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.into()), - ("sub".into(), payload.subject.into()), + ("iss".into(), payload.issuer.into().to_string().into()), + ("sub".into(), payload.subject.into().to_string().into()), ("cmd".into(), payload.ability.to_command().into()), ("args".into(), payload.ability.into()), ( @@ -100,7 +93,7 @@ impl> From> for arguments::Named { ]); if let Some(audience) = payload.audience { - args.insert("aud".into(), audience.into()); + args.insert("aud".into(), audience.into().to_string().into()); } if let Some(not_before) = payload.not_before { @@ -114,17 +107,17 @@ impl> From> for arguments::Named { /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised>; +pub type Promised = Payload<::Promised, DID>; -impl Resolvable for Payload +impl Resolvable for Payload where arguments::Named: From, Ipld: From, T::Promised: ToCommand, { - type Promised = Promised; + type Promised = Promised; - fn try_resolve(promised: Promised) -> Result { + fn try_resolve(promised: Promised) -> Result { match ::try_resolve(promised.ability) { Ok(resolved_ability) => Ok(Payload { issuer: promised.issuer, @@ -149,10 +142,10 @@ where } } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -163,10 +156,10 @@ where } } -impl<'de, T> serde::Deserialize<'de> for Payload +impl<'de, T, DID: Did> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -181,9 +174,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -193,8 +186,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -203,11 +196,11 @@ impl From> for Ipld { #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - issuer: Did, + issuer: did::Newtype, #[serde(rename = "sub")] - subject: Did, + subject: did::Newtype, #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - audience: Option, + audience: Option, #[serde(rename = "cmd")] command: String, @@ -292,12 +285,14 @@ impl TryFrom for InternalSerializer { // } // } -impl>> From> for InternalSerializer { - fn from(payload: Payload) -> Self { +impl>, DID: Did> From> + for InternalSerializer +{ + fn from(payload: Payload) -> Self { InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, + issuer: payload.issuer.into(), + subject: payload.subject.into(), + audience: payload.audience.map(Into::into), command: payload.ability.to_command(), arguments: payload.ability.into(), diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 68a7cd50..f15679cf 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,14 +1,15 @@ use super::Invocation; +use crate::did::Did; use libipld_core::cid::Cid; use std::collections::BTreeMap; use thiserror::Error; -pub trait Store { +pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; fn has(&self, cid: &Cid) -> Result { Ok(self.get(cid).is_ok()) @@ -16,22 +17,22 @@ pub trait Store { } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - store: BTreeMap>, +pub struct MemoryStore { + store: BTreeMap>, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] #[error("Delegation not found")] pub struct NotFound; -impl Store for MemoryStore { +impl Store for MemoryStore { type Error = NotFound; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { self.store.get(cid).ok_or(NotFound) } - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { self.store.insert(cid, invocation); Ok(()) } diff --git a/src/proof/same.rs b/src/proof/same.rs index 5ca43d71..a6ff7640 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -48,17 +48,17 @@ pub trait CheckSame { fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } -impl CheckSame for Did { - type Error = Unequal; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.eq(proof) { - Ok(()) - } else { - Err(Unequal {}) - } - } -} +// impl CheckSame for Did { +// type Error = Unequal; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.eq(proof) { +// Ok(()) +// } else { +// Err(Unequal {}) +// } +// } +// } impl CheckSame for Option { type Error = OptionalFieldError; diff --git a/src/receipt.rs b/src/receipt.rs index a76bd704..9f3160ac 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -11,4 +11,4 @@ pub use responds::Responds; use crate::signature; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4e6a654b..4ba6f9b1 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -16,14 +16,14 @@ use std::{collections::BTreeMap, fmt, fmt::Debug}; /// /// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { /// The issuer of the [`Receipt`]. /// /// This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. /// /// [`Receipt`]: super::Receipt - pub issuer: Did, + pub issuer: DID, /// The [`Cid`] of the [`Invocation`] that was run. /// @@ -66,11 +66,11 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where T::Success: Serialize, { @@ -79,7 +79,7 @@ where S: Serializer, { let mut state = serializer.serialize_struct("receipt::Payload", 8)?; - state.serialize_field("iss", &self.issuer)?; + state.serialize_field("iss", &self.issuer.clone().into())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; @@ -92,7 +92,7 @@ where } } -impl<'de, T: Responds> Deserialize<'de> for Payload +impl<'de, T: Responds, DID: Did + Deserialize<'de>> Deserialize<'de> for Payload where T::Success: Deserialize<'de>, { @@ -100,16 +100,16 @@ where where D: serde::Deserializer<'de>, { - struct ReceiptPayloadVisitor(std::marker::PhantomData); + struct ReceiptPayloadVisitor(std::marker::PhantomData<(T, DID)>); const FIELDS: &'static [&'static str] = &["iss", "ran", "out", "next", "prf", "meta", "nonce", "iat"]; - impl<'de, T: Responds> Visitor<'de> for ReceiptPayloadVisitor + impl<'de, T: Responds, DID: Did + Deserialize<'de>> Visitor<'de> for ReceiptPayloadVisitor where T::Success: Deserialize<'de>, { - type Value = Payload; + type Value = Payload; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("struct delegation::Payload") @@ -205,15 +205,15 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl TryFrom for Payload +impl TryFrom for Payload where - T::Success: DeserializeOwned, + Payload: for<'de> Deserialize<'de>, { type Error = SerdeError; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 4e27ef3a..723d9e7d 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,5 +1,4 @@ -use crate::{did::Did, nonce::Nonce, task, task::Task}; -use libipld_core::ipld::Ipld; +use crate::{nonce::Nonce, task, task::Task}; /// Describe the relationship between an ability and the [`Receipt`]s. /// @@ -14,10 +13,10 @@ pub trait Responds { /// Convert an Ability (`Self`) into a [`Task`]. /// /// This is used to index receipts by a minimal [`Id`]. - fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + fn to_task(&self, subject: did_url::DID, nonce: Nonce) -> Task; /// Convert an Ability (`Self`) directly into a [`Task`]'s [`Id`]. - fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { + fn to_task_id(&self, subject: did_url::DID, nonce: Nonce) -> task::Id { task::Id { cid: self.to_task(subject, nonce).into(), } diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 6f20edd1..d9a7b53c 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,31 +1,31 @@ use super::{Receipt, Responds}; -use crate::task; +use crate::{did::Did, task}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt}; use thiserror::Error; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { +pub trait Store { /// The error type representing all the ways a store operation can fail. type Error; /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore +pub struct MemoryStore where T::Success: fmt::Debug + Clone + PartialEq, { - store: BTreeMap>, + store: BTreeMap>, } // FIXME extract @@ -33,17 +33,17 @@ where #[error("Delegation not found")] pub struct NotFound; -impl Store for MemoryStore +impl Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, { type Error = NotFound; - fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { + fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { self.store.get(id).ok_or(NotFound) } - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { self.store.insert(id, receipt); Ok(()) } diff --git a/src/task.rs b/src/task.rs index ed80fec7..60a0fbb2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,6 +1,6 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. -use crate::{ability::arguments, did::Did, nonce::Nonce}; +use crate::{ability::arguments, did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -22,7 +22,7 @@ const SHA2_256: u64 = 0x12; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { /// The `subject`: root issuer, and arbiter of the semantics/namespace. - pub sub: Did, + pub sub: did::Newtype, /// A unique identifier for the particular task run. /// From 23472a2998d96cb03a8473311039ad777dae29e1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 00:42:35 -0800 Subject: [PATCH 136/234] Tests passing again --- src/did.rs | 4 ++-- src/invocation.rs | 4 ++++ src/proof/same.rs | 8 ++++---- src/receipt/payload.rs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/did.rs b/src/did.rs index 6757cd69..040d29ed 100644 --- a/src/did.rs +++ b/src/did.rs @@ -57,9 +57,9 @@ pub enum Preset { /// # Examples /// /// ```rust -/// # use ucan::did::Did; +/// # use ucan::did; /// # -/// let did = Did::try_from("did:example:123".to_string()).unwrap(); +/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); /// assert_eq!(did.0.method(), "example"); /// ``` /// diff --git a/src/invocation.rs b/src/invocation.rs index 7a60c059..fd9a092c 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,6 +4,7 @@ mod resolvable; pub mod promise; pub mod store; +use crate::did; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; @@ -17,3 +18,6 @@ use crate::signature; /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. pub type Invocation = signature::Envelope>; + +// FIXME use presnet ability, too +pub type Preset = Invocation; diff --git a/src/proof/same.rs b/src/proof/same.rs index a6ff7640..ed0bb4cb 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -9,22 +9,22 @@ use crate::did::Did; /// /// ```rust /// # use ucan::proof::same::CheckSame; -/// # use ucan::did::Did; +/// # use ucan::did; /// # /// struct HelloBuilder { -/// wave_at: Option, +/// wave_at: Option, /// } /// /// enum HelloError { /// MissingWaveAt, -/// WeDontTalkTo(Did) +/// WeDontTalkTo(did::Newtype) /// } /// /// impl CheckSame for HelloBuilder { /// type Error = HelloError; /// /// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -/// if self.wave_at == Some(Did::try_from("did:example:mallory".to_string()).unwrap()) { +/// if self.wave_at == Some(did::Newtype::try_from("did:example:mallory".to_string()).unwrap()) { /// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); /// } /// diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4ba6f9b1..54e8f829 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -6,7 +6,7 @@ use super::responds::Responds; use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{self, DeserializeOwned, MapAccess, Visitor}, + de::{self, MapAccess, Visitor}, ser::SerializeStruct, Deserialize, Serialize, Serializer, }; From 3b8d57db4d528d49c648eafd242115b0d169f360 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 01:53:39 -0800 Subject: [PATCH 137/234] Working on presets --- src/ability.rs | 2 + src/ability/crud/read.rs | 39 ++-------- src/ability/msg.rs | 19 +++++ src/ability/msg/send.rs | 2 +- src/ability/preset.rs | 132 +++++++++++++++++++++++++++++++++ src/crypto/bls12381/min_pk.rs | 2 +- src/crypto/bls12381/min_sig.rs | 2 +- src/delegation.rs | 6 +- src/did.rs | 10 ++- src/did/key.rs | 75 +++++++++++++++++++ src/invocation.rs | 9 ++- src/receipt.rs | 4 +- 12 files changed, 259 insertions(+), 43 deletions(-) create mode 100644 src/ability/preset.rs diff --git a/src/ability.rs b/src/ability.rs index f52c7966..63a05c60 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -41,6 +41,8 @@ pub mod msg; pub mod ucan; pub mod wasm; +pub mod preset; + pub mod arguments; pub mod command; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 6b899eec..9edefa19 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,6 @@ //! Read from a resource. -use super::{error::ProofError, parents::MutableParents}; +use super::{any as crud, error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, invocation::{promise, promise::Resolves, Resolvable}, @@ -174,27 +174,15 @@ impl CheckSame for Builder { } impl CheckParents for Builder { - type Parents = MutableParents; + type Parents = crud::Any; type ParentError = (); // FIXME - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } + // FIXME check the args, too! + if let Some(proof_path) = &other.path { + if self_path != proof_path { + return Err(()); } } } @@ -232,19 +220,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 708d2bf6..1f3c2090 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -7,3 +7,22 @@ pub mod send; pub use any::Any; pub use receive::Receive; + +// FIXME rename invokable? +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Ready { + Receive(receive::Receive), + Send(send::Ready), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Builder { + Recieve(receive::Receive), + Send(send::Builder), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Promised { + // Recieve(receive::Promised), // FIXME + Send(send::Promised), +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 1a0b7538..fb6dbc32 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -16,7 +16,7 @@ use url::Url; /// /// This is not generally used directly, unless you want to abstract /// over all of the `msg/send` variants. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { /// The recipient of the message diff --git a/src/ability/preset.rs b/src/ability/preset.rs new file mode 100644 index 00000000..88cc0bb8 --- /dev/null +++ b/src/ability/preset.rs @@ -0,0 +1,132 @@ +use super::{msg, wasm}; +use crate::{ + ability::{ + arguments, + command::{Command, ParseAbility}, + }, + delegation::Delegable, + invocation::{promise, Resolvable}, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, + same::CheckSame, + }, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Ready { + //Crud(), + Msg(msg::Ready), + Wasm(wasm::run::Ready), +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Builder { + Msg(msg::Builder), + Wasm(wasm::run::Builder), +} + +pub enum Parents { + Msg(msg::Any), +} // NOTE WasmRun has no parents + +impl CheckSame for Parents { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + } + } +} + +impl ParseAbility for Parents { + type Error = String; // FIXME + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + todo!() + // FIXME Ok(Self {}) + } +} + +impl Delegable for Ready { + type Builder = Builder; +} + +impl CheckParents for Builder { + type Parents = Parents; + type ParentError = (); // FIXME + + fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { + match self { + Builder::Msg(builder) => builder.check_parent(proof), + Builder::Wasm(builder) => Ok(()), + } + } +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Msg(ready) => Builder::Msg(ready.into()), + Ready::Wasm(ready) => Builder::Wasm(ready.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Msg(builder) => builder.try_into().map(Ready::Msg), + Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm), + } + } +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Promised { + Msg(msg::Promised), + Wasm(wasm::run::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised { + Promised::Msg(promised) => Resolvable::try_resolve(promised) + .map(Ready::Msg) + .map_err(Promised::Msg), + Promised::Wasm(promised) => Resolvable::try_resolve(promised) + .map(Ready::Wasm) + .map_err(Promised::Wasm), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} diff --git a/src/crypto/bls12381/min_pk.rs b/src/crypto/bls12381/min_pk.rs index fde8887e..87a58f38 100644 --- a/src/crypto/bls12381/min_pk.rs +++ b/src/crypto/bls12381/min_pk.rs @@ -4,7 +4,7 @@ use blst::BLST_ERROR; use signature::{SignatureEncoding, Signer, Verifier}; /// A BLS12-381 MinPubKey signature -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub blst::min_pk::Signature); impl DomainSeparator for Signature { diff --git a/src/crypto/bls12381/min_sig.rs b/src/crypto/bls12381/min_sig.rs index 121ac66d..1298f098 100644 --- a/src/crypto/bls12381/min_sig.rs +++ b/src/crypto/bls12381/min_sig.rs @@ -4,7 +4,7 @@ use blst::BLST_ERROR; use signature::{SignatureEncoding, Signer, Verifier}; /// A BLS12-381 MinSig signature -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub blst::min_sig::Signature); impl DomainSeparator for Signature { diff --git a/src/delegation.rs b/src/delegation.rs index 1988b137..0845340c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,6 +11,7 @@ pub use delegable::Delegable; pub use payload::Payload; use crate::{ + ability, did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, @@ -19,7 +20,7 @@ use crate::{ }; use condition::Condition; use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, convert::AsRef}; +use std::collections::BTreeMap; use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] @@ -30,8 +31,7 @@ use web_time::SystemTime; /// FIXME pub type Delegation = signature::Envelope>; -// FIXME attach common abilities, too -pub type Preset = Delegation; +pub type Preset = Delegation; // FIXME checkable -> provable? diff --git a/src/did.rs b/src/did.rs index 040d29ed..f989b959 100644 --- a/src/did.rs +++ b/src/did.rs @@ -45,10 +45,18 @@ pub trait Did: //impl Did for key::Verifier {} // pub enum Preset { - // Key(key::Verifier), + Key(key::Verifier), // Dns(did_url::DID), } +impl signature::Verifier for Preset { + fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { + match self { + Preset::Key(verifier) => verifier.verify(message, signature), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// diff --git a/src/did/key.rs b/src/did/key.rs index 5d6c2c2b..07e0f281 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -2,6 +2,8 @@ pub mod traits; +use signature; + #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -17,6 +19,9 @@ use p384; #[cfg(feature = "es512")] use crate::crypto::p521; +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + #[cfg(feature = "rs256")] use crate::crypto::rs256; @@ -26,6 +31,9 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use blst; +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Verifier { #[cfg(feature = "eddsa")] @@ -55,3 +63,70 @@ pub enum Verifier { #[cfg(feature = "bls")] BlsMinSig(blst::min_sig::PublicKey), } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Signature { + #[cfg(feature = "eddsa")] + Ed25519(ed25519_dalek::Signature), + + #[cfg(feature = "es256k")] + Sedcp256k1(k256::ecdsa::Signature), + + #[cfg(feature = "es256")] + P256(p256::ecdsa::Signature), + + #[cfg(feature = "es384")] + P384(p384::ecdsa::Signature), + + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::Signature), + + #[cfg(feature = "rs256")] + Rs256(rs256::Signature), + + #[cfg(feature = "rs512")] + Rs512(rs512::Signature), + + #[cfg(feature = "bls")] + BlsMinPk(bls12381::min_pk::Signature), + + #[cfg(feature = "bls")] + BlsMinSig(bls12381::min_sig::Signature), +} + +impl signature::Verifier for Verifier { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match (self, signature) { + (Verifier::Ed25519(vk), Signature::Ed25519(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Sedcp256k1(vk), Signature::Sedcp256k1(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P256(vk), Signature::P256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P384(vk), Signature::P384(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P521(vk), Signature::P521(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs256(vk), Signature::Rs256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs512(vk), Signature::Rs512(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (_, _) => Err(signature::Error::from_source( + "invalid signature type for verifier", + )), + } + } +} diff --git a/src/invocation.rs b/src/invocation.rs index fd9a092c..0bedcbb9 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,11 +4,10 @@ mod resolvable; pub mod promise; pub mod store; -use crate::did; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::signature; +use crate::{ability, did, signature}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -19,5 +18,9 @@ use crate::signature; /// `Invocation>`. pub type Invocation = signature::Envelope>; +// FIXME rename +pub type PromisedInvocation = Invocation; + // FIXME use presnet ability, too -pub type Preset = Invocation; +pub type Preset = Invocation; +pub type PresetPromised = Invocation; diff --git a/src/receipt.rs b/src/receipt.rs index 9f3160ac..7a5b7d9f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,7 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::signature; +use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. pub type Receipt = signature::Envelope>; + +pub type Preset = Receipt; From 9012ce36f9cb6c0dec26629eca8cf35e00650eb3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 15:17:59 -0800 Subject: [PATCH 138/234] Save point after writing the signature envelope --- flake.nix | 30 ++---- pre-commit.nix | 4 +- src/ability/:63 | 132 ++++++++++++++++++++++++++ src/ability/crud.rs | 97 +++++++++++++++++++ src/ability/msg.rs | 86 ++++++++++++++++- src/ability/msg/receive.rs | 42 ++++++++- src/ability/msg/send.rs | 4 +- src/ability/preset.rs | 65 ++++++++----- src/delegation.rs | 2 +- src/delegation/agent.rs | 20 ++-- src/delegation/store.rs | 3 +- src/did.rs | 3 +- src/invocation.rs | 4 +- src/proof/checkable.rs | 2 +- src/proof/parentless.rs | 2 +- src/receipt.rs | 4 +- src/signature.rs | 188 +++++++++++++++---------------------- 17 files changed, 504 insertions(+), 184 deletions(-) create mode 100644 src/ability/:63 diff --git a/flake.nix b/flake.nix index 3cd19cb8..bee92820 100644 --- a/flake.nix +++ b/flake.nix @@ -76,7 +76,7 @@ cargo-sort cargo-udeps cargo-watch - llvmPackages.bintools + # llvmPackages.bintools twiggy unstable.cargo-component wasm-bindgen-cli @@ -88,10 +88,18 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; in rec { + formatter = pkgs.alejandra; + + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + # FIXME This explicitely does not get pulled in under devshell + # stdenv = pkgs.clangStdenv; + devShells.default = pkgs.devshell.mkShell { name = "ucan"; - imports = [./pre-commit.nix]; + imports = [ + ./pre-commit.nix + ]; packages = with pkgs; [ @@ -104,7 +112,6 @@ protobuf unstable.nodejs_20 unstable.nodePackages.pnpm - unstable.wasmtime ] ++ format-pkgs ++ cargo-installs @@ -248,12 +255,6 @@ name = "watch:test:host"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec test"; - } - { - name = "watch:test:docs:host"; - help = "Run all tests on save"; - category = "watch"; command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; } { @@ -262,12 +263,6 @@ category = "watch"; command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; } - { - name = "watch:test:docs:wasm"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown --features=mermaid_docs'"; - } # Test { name = "test:all"; @@ -358,11 +353,6 @@ doCheck = false; cargoSha256 = "sha256-2aVCNz/Lw7364B5dgGaloVPcQHm2E+b/BOxF6Qlc8Hs="; }; - - formatter = pkgs.alejandra; - - # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; } ); } diff --git a/pre-commit.nix b/pre-commit.nix index b4ed07d7..83e370ba 100644 --- a/pre-commit.nix +++ b/pre-commit.nix @@ -1,9 +1,7 @@ {pkgs, ...}: let pc = "${pkgs.pre-commit}/bin/pre-commit"; in { - config = { - devshell.startup.pre-commit.text = '' + config.devshell.startup.pre-commit.text = '' [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) ''; - }; } diff --git a/src/ability/:63 b/src/ability/:63 new file mode 100644 index 00000000..b1803b0c --- /dev/null +++ b/src/ability/:63 @@ -0,0 +1,132 @@ +use super::{msg, wasm}; +use crate::{ + ability::{ + arguments, + command::{Command, ParseAbility}, + }, + delegation::Delegable, + invocation::{promise, Resolvable}, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, + same::CheckSame, + }, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Ready { + //Crud(), + Msg(msg::Ready), + Wasm(wasm::run::Ready), +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Builder { + Msg(msg::Builder), + Wasm(wasm::run::Builder), +} + +pub enum Parents { + Msg(msg::Any), +} // NOTE WasmRun has no parents + +impl CheckSame for Parents { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + } + } +} + +impl ParseAbility for Parents { + type Error = String; // FIXME + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + todo!() + // FIXME Ok(Self {}) + } +} + +impl Delegable for Ready { + type Builder = Builder; +} + +impl CheckParents for Builder { + type Parents = Parents; + type ParentError = (); // FIXME + + fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { + match self { + Builder::Msg(builder) => builder.check_parent(proof), + Builder::Wasm(builder) => Ok(()), + } + } +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Msg(ready) => Builder::Msg(ready.into()), + Ready::Wasm(ready) => Builder::Wasm(ready.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Msg(builder) => builder.try_into().map(Ready::Msg), + Builder::Wasm(builder) => Ok(Ready::Wasm(builder.into())), + } + } +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Promised { + Msg(msg::Promised), + Wasm(wasm::run::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised { + Promised::Msg(promised) => Resolvable::try_resolve(promised) + .map(Ready::Msg) + .map_err(Promised::Msg), + Promised::Wasm(promised) => Resolvable::try_resolve(promised) + .map(Ready::Wasm) + .map_err(Promised::Wasm), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 39e6c79c..ccbd3512 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -51,5 +51,102 @@ pub use any::Any; pub use mutate::Mutate; pub use parents::MutableParents; +use crate::{ability::arguments, invocation::Resolvable, proof::same::CheckSame}; +use libipld_core::ipld::Ipld; + #[cfg(target_arch = "wasm32")] pub mod js; + +#[derive(Debug, Clone, PartialEq)] +pub enum Ready { + Create(create::Ready), + Read(read::Ready), + Update(update::Ready), + Destroy(destroy::Ready), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Builder { + Create(create::Builder), + Read(read::Builder), + Update(update::Builder), + Destroy(destroy::Builder), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Promised { + Create(create::Promised), + Read(read::Promised), + Update(update::Promised), + Destroy(destroy::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Create(create) => create.into(), + Promised::Read(read) => read.into(), + Promised::Update(update) => update.into(), + Promised::Destroy(destroy) => destroy.into(), + } + } +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Create(create) => Builder::Create(create.into()), + Ready::Read(read) => Builder::Read(read.into()), + Ready::Update(update) => Builder::Update(update.into()), + Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), + Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), + Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), + Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), + } + } +} + +impl CheckSame for Builder { + type Error = (); + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + match (self, other) { + (Builder::Create(a), Builder::Create(b)) => a.check_same(b), + (Builder::Read(a), Builder::Read(b)) => a.check_same(b), + (Builder::Update(a), Builder::Update(b)) => a.check_same(b), + (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), + _ => Err(()), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match promised { + Promised::Create(create) => Resolvable::try_resolve(create) + .map(Ready::Create) + .map_err(Promised::Create), + Promised::Read(read) => Resolvable::try_resolve(read) + .map(Ready::Read) + .map_err(Promised::Read), + Promised::Update(update) => Resolvable::try_resolve(update) + .map(Ready::Update) + .map_err(Promised::Update), + Promised::Destroy(destroy) => Resolvable::try_resolve(destroy) + .map(Ready::Destroy) + .map_err(Promised::Destroy), + } + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 1f3c2090..54a02095 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -8,21 +8,97 @@ pub mod send; pub use any::Any; pub use receive::Receive; +use crate::{ + ability::arguments, + invocation::Resolvable, + proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, +}; +use libipld_core::ipld::Ipld; + // FIXME rename invokable? -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum Ready { - Receive(receive::Receive), Send(send::Ready), + Receive(receive::Receive), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum Builder { - Recieve(receive::Receive), Send(send::Builder), + Receive(receive::Receive), } #[derive(Debug, Clone, PartialEq)] pub enum Promised { - // Recieve(receive::Promised), // FIXME Send(send::Promised), + Receive(receive::Promised), // FIXME +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Send(send) => send.try_into().map(Ready::Send).map_err(|_| ()), + Builder::Receive(receive) => Ok(Ready::Receive(receive)), + } + } +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Send(send) => Builder::Send(send.into()), + Ready::Receive(receive) => Builder::Receive(receive.into()), + } + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match promised { + Promised::Send(send) => Resolvable::try_resolve(send) + .map(Ready::Send) + .map_err(Promised::Send), + Promised::Receive(receive) => Resolvable::try_resolve(receive) + .map(Ready::Receive) + .map_err(Promised::Receive), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Send(this), Builder::Send(that)) => this.check_same(that), + (Builder::Receive(this), Builder::Receive(that)) => this.check_same(that), + _ => Err(()), + } + } +} + +impl CheckParents for Builder { + type Parents = Any; + type ParentError = (); + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match (self, proof) { + (Builder::Send(this), Any) => this.check_parent(&Any), + (Builder::Receive(this), Any) => this.check_parent(&Any), + _ => Err(()), + } + } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c8a34ce4..37f4aa23 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,12 +1,13 @@ //! The ability to receive messages use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages @@ -41,7 +42,7 @@ use url::Url; pub struct Receive { /// An *optional* URL (e.g. email, DID, socket) to receive messages from. /// This assumes that the `subject` has the authority to issue such a capability. - pub from: Option, + pub from: Option, } // FIXME needs promisory version @@ -66,7 +67,8 @@ impl CheckParents for Receive { type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&proof.from).map_err(|_| ()) + // self.from.check_same(&proof.from).map_err(|_| ()) + todo!() // FIXME } } @@ -83,3 +85,35 @@ impl TryFrom for Receive { ipld_serde::from_ipld(ipld) } } + +#[derive(Debug, Clone, PartialEq)] +pub struct Promised { + pub from: promise::Resolves>, +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut args = arguments::Named::new(); + + match promised.from { + promise::Resolves::Ok(from) => { + args.insert("from".into(), from.into()); + } + promise::Resolves::Err(from) => { + args.insert("from".into(), from.into()); + } + } + + args + } +} + +impl Resolvable for Receive { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + promise::Resolves::try_resolve(p.from) + .map(|from| Receive { from }) + .map_err(|from| Promised { from }) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fb6dbc32..d5974af7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -153,11 +153,11 @@ impl From for arguments::Named { impl From for arguments::Named { fn from(p: Promised) -> Self { - arguments::Named(BTreeMap::from_iter([ + arguments::Named::from_iter([ ("to".into(), p.to.map(url_newtype::Newtype).into()), ("from".into(), p.from.map(String::from).into()), ("message".into(), p.message.into()), - ])) + ]) } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 88cc0bb8..4fc1428e 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,4 +1,4 @@ -use super::{msg, wasm}; +use super::{crud, msg, wasm}; use crate::{ ability::{ arguments, @@ -16,18 +16,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { - //Crud(), + Crud(crud::Ready), Msg(msg::Ready), Wasm(wasm::run::Ready), } #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Builder { + Crud(crud::Builder), Msg(msg::Builder), Wasm(wasm::run::Builder), } +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Parents { + Crud(crud::MutableParents), Msg(msg::Any), } // NOTE WasmRun has no parents @@ -36,7 +39,11 @@ impl CheckSame for Parents { fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + (Parents::Msg(self_), Parents::Msg(proof_)) => self_.check_same(proof_).map_err(|_| ()), + (Parents::Crud(self_), Parents::Crud(proof_)) => { + self_.check_same(proof_).map_err(|_| ()) + } + _ => Err(()), } } } @@ -45,8 +52,15 @@ impl ParseAbility for Parents { type Error = String; // FIXME fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - todo!() - // FIXME Ok(Self {}) + if let Ok(crud) = crud::MutableParents::try_parse(cmd, args) { + return Ok(Parents::Crud(crud)); + } + + if let Ok(msg) = msg::Any::try_parse(cmd, args) { + return Ok(Parents::Msg(msg)); + } + + Err("Nope".into()) } } @@ -54,14 +68,25 @@ impl Delegable for Ready { type Builder = Builder; } +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} + impl CheckParents for Builder { type Parents = Parents; type ParentError = (); // FIXME - fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { - match self { - Builder::Msg(builder) => builder.check_parent(proof), - Builder::Wasm(builder) => Ok(()), + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match (self, proof) { + (Builder::Msg(builder), Parents::Msg(proof)) => builder.check_parent(proof), + _ => Err(()), } } } @@ -73,6 +98,7 @@ impl Checkable for Builder { impl From for Builder { fn from(ready: Ready) -> Self { match ready { + Ready::Crud(ready) => Builder::Crud(ready.into()), Ready::Msg(ready) => Builder::Msg(ready.into()), Ready::Wasm(ready) => Builder::Wasm(ready.into()), } @@ -84,14 +110,16 @@ impl TryFrom for Ready { fn try_from(builder: Builder) -> Result { match builder { - Builder::Msg(builder) => builder.try_into().map(Ready::Msg), - Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm), + Builder::Crud(builder) => builder.try_into().map(Ready::Crud).map_err(|_| ()), + Builder::Msg(builder) => builder.try_into().map(Ready::Msg).map_err(|_| ()), + Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm).map_err(|_| ()), } } } #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Promised { + Crud(crud::Promised), Msg(msg::Promised), Wasm(wasm::run::Promised), } @@ -99,6 +127,7 @@ pub enum Promised { impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { + Promised::Crud(promised) => promised.into(), Promised::Msg(promised) => promised.into(), Promised::Wasm(promised) => promised.into(), } @@ -110,6 +139,9 @@ impl Resolvable for Ready { fn try_resolve(promised: Self::Promised) -> Result { match promised { + Promised::Crud(promised) => Resolvable::try_resolve(promised) + .map(Ready::Crud) + .map_err(Promised::Crud), Promised::Msg(promised) => Resolvable::try_resolve(promised) .map(Ready::Msg) .map_err(Promised::Msg), @@ -119,14 +151,3 @@ impl Resolvable for Ready { } } } - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} diff --git a/src/delegation.rs b/src/delegation.rs index 0845340c..972f2506 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope, DID::Signature>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 375fcf53..5e337e4a 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -8,19 +8,27 @@ use web_time::SystemTime; pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { pub did: &'a DID, pub store: &'a mut S, + + signer: &'a ::Signer, _marker: PhantomData<(B, C)>, } // FIXME show example of multiple hierarchies of "all things accepted" // delegating down to inner versions of this -impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Store> - Agent<'a, B, C, DID, S> +impl< + 'a, + B: Checkable + Clone, + C: Condition + Clone, + DID: Did + ToString + Clone, + S: Store, + > Agent<'a, B, C, DID, S> { - pub fn new(did: &'a DID, store: &'a mut S) -> Self { + pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { did, store, + signer, _marker: PhantomData, } } @@ -51,8 +59,7 @@ impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Sto conditions: new_conditions, }; - // FIXME add signer info - return Ok(Delegation::sign(payload)); + return Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")); } let to_delegate = &self @@ -79,8 +86,7 @@ impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Sto not_before: not_before.map(Into::into), }; - // FIXME add signing material - Ok(Delegation::sign(payload)) + Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")) } pub fn recieve( diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 062ef0f3..a736d191 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -8,6 +8,7 @@ use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, convert::Infallible, + fmt, ops::ControlFlow, }; use web_time::SystemTime; @@ -110,8 +111,6 @@ pub struct MemoryStore { // FIXME check that UCAN is valid impl Store for MemoryStore -where - B::Hierarchy: PartialEq, { type Error = Infallible; diff --git a/src/did.rs b/src/did.rs index f989b959..30784caf 100644 --- a/src/did.rs +++ b/src/did.rs @@ -37,7 +37,8 @@ use blst; pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { - type Signature: signature::SignatureEncoding; + type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; + type Signer: signature::Signer; } // impl Did for ed25519_dalek::VerifyingKey {} diff --git a/src/invocation.rs b/src/invocation.rs index 0bedcbb9..395c53e8 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -7,7 +7,7 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, signature}; +use crate::{ability, did, did::Did, signature}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -16,7 +16,7 @@ use crate::{ability, did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope, DID::Signature>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 584c1ee7..5098c314 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -11,5 +11,5 @@ pub trait Checkable: CheckSame + Sized { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From; + type Hierarchy: CheckSame + Prove + From + PartialEq; } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 2400b756..1af372c4 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -49,7 +49,7 @@ pub enum ParentlessError { /// This behaves as an alias for `Checkable::>`. pub trait NoParents {} -impl Checkable for T { +impl Checkable for T { type Hierarchy = Parentless; } diff --git a/src/receipt.rs b/src/receipt.rs index 7a5b7d9f..9dc335c3 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,9 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::{ability, did, signature}; +use crate::{ability, did, did::Did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope, DID::Signature>; pub type Preset = Receipt; diff --git a/src/signature.rs b/src/signature.rs index 6eb2bfc1..204e8ee4 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,25 +1,72 @@ //! Signatures and cryptographic envelopes. -use crate::capsule::Capsule; -use libipld_core::ipld::Ipld; +use crate::{capsule::Capsule, did::Did}; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::{Codec, Encode}, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, +}; use serde::{Deserialize, Serialize}; +use signature::SignatureEncoding; use std::collections::BTreeMap; +// FIXME #[cfg(feature = "dag-cbor")] +use libipld_cbor::DagCborCodec; +use signature::Signer; + /// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Envelope { +pub struct Envelope { /// The signture of the `payload`. - pub signature: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, } -impl Envelope { - // FIXME need key material - pub fn sign(payload: T) -> Envelope { - // FIXME - todo!() +impl, S: SignatureEncoding> Envelope { + pub fn try_sign( + signer: &DID::Signer, + payload: T, + ) -> Result::Signature>, ()> { + Self::try_sign_generic::( + signer, + DagCborCodec, + Code::Sha2_256, + payload, + ) + } + + pub fn try_sign_generic, DID: Did>( + signer: &DID::Signer, + codec: C, + hasher: H, + payload: T, + ) -> Result, ()> + // FIXME err = () + where + Ipld: Encode, + { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; + + Ok(Envelope { + signature: Signature::One(sig), + payload, + }) } pub fn validate_signature(&self) -> Result<(), ()> { @@ -31,125 +78,44 @@ impl Envelope { // FIXME consider kicking Batch down the road for spec reasons? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Signature { - One(Vec), +pub enum Signature { + One(S), Batch { - signature: Vec, + signature: S, merkle_proof: Vec>, }, } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(transparent)] -// pub struct StaticVec { -// pub slice: Box<[T]>, -// } -// -// impl From> for StaticVec { -// fn from(vec: Vec) -> Self { -// Self { -// slice: vec.into_boxed_slice(), -// } -// } -// } -// -// impl From> for Vec { -// fn from(vec: StaticVec) -> Vec { -// vec.slice.into() -// } -// } -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(transparent)] -// pub struct StaticString { -// string: Box, -// } -// -// impl From for StaticString { -// fn from(string: String) -> Self { -// Self { -// string: string.into_boxed_str(), -// } -// } -// } -// -// impl<'a> From<&'a str> for StaticString { -// fn from(s: &'a str) -> Self { -// Self { string: s.into() } -// } -// } -// -// impl<'a> From<&'a StaticString> for &'a str { -// fn from(s: &'a StaticString) -> &'a str { -// &s.string -// } -// } -// -// impl From for String { -// fn from(s: StaticString) -> String { -// s.string.into() -// } -// } - -impl From<&Signature> for Ipld { - fn from(signature: &Signature) -> Self { - match signature { - Signature::One(sig) => sig.clone().into(), - Signature::Batch { - signature, - merkle_proof, - } => { - let mut map = BTreeMap::new(); - let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); - map.insert("sig".into(), signature.clone().into()); - map.insert("prf".into(), proof.into()); - map.into() - } - } - } -} - -impl From for Ipld { - fn from(signature: Signature) -> Self { +impl> From> for Ipld { + fn from(signature: Signature) -> Self { match signature { Signature::One(sig) => sig.into(), Signature::Batch { signature, merkle_proof, - } => { - let mut map = BTreeMap::new(); - let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); - map.insert("sig".into(), signature.into()); - map.insert("prf".into(), proof.into()); - map.into() - } + } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), } } } -// FIXME Store or BTreeMap? Also eliminate that Clone constraint -impl + Clone> From<&Envelope> for Ipld { - fn from(Envelope { signature, payload }: &Envelope) -> Self { - let mut inner = BTreeMap::new(); - inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link - - let mut map = BTreeMap::new(); - map.insert("sig".into(), signature.into()); - map.insert("pld".into(), Ipld::Map(inner)); +impl, S: SignatureEncoding + Into> From> for Ipld { + fn from(Envelope { signature, payload }: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - Ipld::Map(map) - } -} + let codec = DagCborCodec; // FIXME get this from the payload + let hasher = Code::Sha2_256; // FIXME get this from the payload -impl + Clone> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let mut inner = BTreeMap::new(); - inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - let mut map = BTreeMap::new(); - map.insert("sig".into(), signature.into()); - map.insert("pld".into(), Ipld::Map(inner)); + let cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); - Ipld::Map(map) + BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), cid.into())]).into() } } From cc5d305bd55a407e8267c131a0ce7430bf6908f9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 23:37:14 -0800 Subject: [PATCH 139/234] VERY WIP invocation agent --- src/delegation.rs | 2 +- src/delegation/agent.rs | 12 ++- src/delegation/payload.rs | 7 ++ src/delegation/store.rs | 20 ++++- src/invocation.rs | 3 +- src/invocation/agent.rs | 155 ++++++++++++++++++++++++++++++++++++++ src/invocation/payload.rs | 7 ++ src/proof/same.rs | 17 +---- src/receipt.rs | 2 +- src/receipt/payload.rs | 11 ++- src/signature.rs | 107 +++++++++++++++++--------- 11 files changed, 285 insertions(+), 58 deletions(-) create mode 100644 src/invocation/agent.rs diff --git a/src/delegation.rs b/src/delegation.rs index 972f2506..f248ef26 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope, DID::Signature>; +pub type Delegation = signature::Envelope, DID>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 5e337e4a..277246fa 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -59,12 +59,18 @@ impl< conditions: new_conditions, }; - return Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")); + return Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")); } let to_delegate = &self .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .get_chain( + &self.did, + &subject, + &ability_builder, + vec![], + &SystemTime::now(), + ) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -86,7 +92,7 @@ impl< not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")) + Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) } pub fn recieve( diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 9e4dfc53..06569542 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -17,6 +17,7 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, + signature::Verifiable, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -28,6 +29,12 @@ use serde::{ use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// The payload portion of a [`Delegation`][super::Delegation]. /// /// This contains the semantic information about the delegation, including the diff --git a/src/delegation/store.rs b/src/delegation/store.rs index a736d191..875bc3e3 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,9 +1,10 @@ use super::{condition::Condition, Delegation}; use crate::{ + ability::arguments, did::Did, proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -21,8 +22,12 @@ pub trait Store { // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar + // FIXME rename put fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + // FIXME validate invocation + // sore invocation + // just... move to invocation fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( @@ -30,6 +35,7 @@ pub trait Store { aud: &DID, subject: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result)>>, Self::Error>; @@ -38,9 +44,10 @@ pub trait Store { iss: &DID, aud: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result { - self.get_chain(aud, iss, builder, now) + self.get_chain(aud, iss, builder, conditions, now) .map(|chain| chain.is_some()) } } @@ -111,6 +118,8 @@ pub struct MemoryStore { // FIXME check that UCAN is valid impl Store for MemoryStore +where + B::Hierarchy: Into>, { type Error = Infallible; @@ -145,6 +154,7 @@ impl Sto aud: &DID, subject: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { @@ -181,6 +191,12 @@ impl Sto return ControlFlow::Continue(()); } + for condition in &conditions { + if !condition.validate(&d.payload.ability_builder.into()) { + return ControlFlow::Continue(()); + } + } + chain.push((cid, d)); if &d.payload.issuer == subject { diff --git a/src/invocation.rs b/src/invocation.rs index 395c53e8..e14e4d9a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,6 +1,7 @@ mod payload; mod resolvable; +pub mod agent; pub mod promise; pub mod store; @@ -16,7 +17,7 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID::Signature>; +pub type Invocation = signature::Envelope, DID>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs new file mode 100644 index 00000000..2e284691 --- /dev/null +++ b/src/invocation/agent.rs @@ -0,0 +1,155 @@ +use super::{payload::Payload, store::Store, Invocation, Resolvable}; +use crate::{ + delegation, + delegation::{condition::Condition, Delegable, Delegation}, + did::Did, + nonce::Nonce, + signature::Verifiable, + time::JsTime, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent< + 'a, + T: Resolvable + Delegable, + C: Condition, + DID: Did, + S: Store, + P: Store, + D: delegation::store::Store, +> { + pub did: &'a DID, + + pub store: &'a mut S, + pub promised_store: &'a mut P, + pub delegation_store: &'a D, + + signer: &'a ::Signer, + marker: PhantomData<(T, C)>, +} + +impl< + 'a, + T: Resolvable + Delegable + Clone, + C: Condition, + DID: Did + ToString + Clone, + S: Store, + P: Store, + D: delegation::store::Store, + > Agent<'a, T, C, DID, S, P, D> +{ + pub fn new( + did: &'a DID, + signer: &'a ::Signer, + store: &'a mut S, + promised_store: &'a mut P, + delegation_store: &'a mut D, + ) -> Self { + Self { + did, + store, + promised_store, + delegation_store, + signer, + marker: PhantomData, + } + } + + pub fn invoke( + &mut self, + audience: Option<&DID>, + subject: &DID, + ability: T::Promised, + metadata: BTreeMap, + cause: Option, + expiration: JsTime, + not_before: Option, + // FIXME err type + ) -> Result, ()> { + let proofs = self + .delegation_store + .get_chain( + audience, + subject, + ability.into(), + vec![], + &SystemTime::now(), + ) + .map_err(|_| ())? + .map(|chain| chain.map(|(cid, _)| *cid).into()) + .unwrap_or(vec![]); + + let mut seed = vec![]; + + let payload = Payload { + issuer: self.did.clone(), + subject: subject.clone(), + audience: audience.cloned(), + ability, + proofs, + metadata, + nonce: Nonce::generate_16(&mut seed), + cause, + expiration, + not_before, + }; + + let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; + let cid: Cid = invocation.into(); + self.store.put(cid, invocation); + Ok(invocation) + } + + // FIXME err = () + pub fn revoke(&mut self, cid: &Cid) -> Result<(), ()> { + todo!(); // FIXME create a revoke invocation + self.store.revoke(&cid) + } + + pub fn receive( + &self, + invocation: Invocation, + proofs: BTreeMap>, + // FIXME return type + ) -> Result, ()> { + // FIXME needs varsig header + let cid = Cid::from(invocation); + + invocation + .verifier() + .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) + .map_err(|_| ())?; + + // FIXME pull delegations out of the store and check them + + match Resolvable::try_resolve(&invocation.payload) { + Ok(resolved) => { + // FIXME promised store + self.store.put(cid, resolved).map_err(|_| ())?; + } + Err(unresolved) => self.promised_store.put(cid, unresolved).map_err(|_| ())?, + } + + // FIXME + // FIXME promised store + self.store.put(cid, invocation).map_err(|_| ())?; + + for (cid, deleg) in proofs { + self.delegation_store.insert(cid, deleg).map_err(|_| ())?; + } + + if invocation.payload.audience != Some(*self.did) { + return Ok(Recipient::Other(invocation)); + } + + Ok(Recipient::You(invocation)) + } +} + +pub enum Recipient { + You(T), + Other(T), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b3767f4b..9aa7e61b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,6 +6,7 @@ use crate::{ did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, + signature::Verifiable, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -13,6 +14,12 @@ use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, diff --git a/src/proof/same.rs b/src/proof/same.rs index ed0bb4cb..b616a01c 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,7 +1,6 @@ //! Check the delegation proof against another instance of the same type -use super::error::{OptionalFieldError, Unequal}; -use crate::did::Did; +use super::error::OptionalFieldError; /// Trait for checking if a proof of the same type is equally or less restrictive. /// @@ -39,7 +38,7 @@ use crate::did::Did; /// } pub trait CheckSame { /// Error type describing why a proof was insufficient. - type Error; // FIXME Rename CheckSameError? + type Error; /// Check if the proof is equally or less restrictive than the instance. /// @@ -48,18 +47,6 @@ pub trait CheckSame { fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } -// impl CheckSame for Did { -// type Error = Unequal; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.eq(proof) { -// Ok(()) -// } else { -// Err(Unequal {}) -// } -// } -// } - impl CheckSame for Option { type Error = OptionalFieldError; diff --git a/src/receipt.rs b/src/receipt.rs index 9dc335c3..05544091 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -11,6 +11,6 @@ pub use responds::Responds; use crate::{ability, did, did::Did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID::Signature>; +pub type Receipt = signature::Envelope, DID>; pub type Preset = Receipt; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 54e8f829..b093f6c7 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -3,7 +3,10 @@ //! [`Invocation`]: crate::invocation::Invocation use super::responds::Responds; -use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use crate::{ + ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, signature::Verifiable, + time::Timestamp, +}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::{self, MapAccess, Visitor}, @@ -12,6 +15,12 @@ use serde::{ }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// The payload (non-signature) portion of a response from an [`Invocation`]. /// /// [`Invocation`]: crate::invocation::Invocation diff --git a/src/signature.rs b/src/signature.rs index 204e8ee4..e40bee94 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -7,7 +7,7 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use signature::SignatureEncoding; use std::collections::BTreeMap; @@ -15,35 +15,37 @@ use std::collections::BTreeMap; use libipld_cbor::DagCborCodec; use signature::Signer; +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} + +impl + Capsule, DID: Did> Verifiable for Envelope { + fn verifier(&self) -> &DID { + &self.payload.verifier() + } +} + /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Envelope { +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +pub struct Envelope + Capsule, DID: Did> { /// The signture of the `payload`. - pub signature: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, } -impl, S: SignatureEncoding> Envelope { - pub fn try_sign( - signer: &DID::Signer, - payload: T, - ) -> Result::Signature>, ()> { - Self::try_sign_generic::( - signer, - DagCborCodec, - Code::Sha2_256, - payload, - ) +impl + Clone + Into, DID: Did> Envelope { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { + Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } - pub fn try_sign_generic, DID: Did>( + pub fn try_sign_generic>( signer: &DID::Signer, codec: C, hasher: H, payload: T, - ) -> Result, ()> + ) -> Result, ()> // FIXME err = () where Ipld: Encode, @@ -64,42 +66,79 @@ impl, S: SignatureEncoding> Envelope { let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; Ok(Envelope { - signature: Signature::One(sig), + signature: Signature::Solo(sig), payload, }) } pub fn validate_signature(&self) -> Result<(), ()> { - // FIXME - todo!() + // FIXME need varsig + let codec = todo!(); + let hasher = todo!(); + + let mut buffer = vec![]; + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + match self.signature { + Signature::Solo(sig) => self + .verifier() + .verify(&cid.to_bytes(), &sig) + .map_err(|_| ()), + } } } // FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] + // #[serde(untagged)] pub enum Signature { - One(S), - Batch { - signature: S, - merkle_proof: Vec>, - }, + Solo(S), + // Batch { + // signature: S, + // root: Vec, + // merkle_proof: Vec, + // }, } +//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +//pub enum MerkleStep { +// Node(Vec, Vec, Direction), +// Turn(Direction), +// End, +//} +// +//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +//pub enum Direction { +// Left = 0, +// Right = 1, +//} + impl> From> for Ipld { fn from(signature: Signature) -> Self { match signature { - Signature::One(sig) => sig.into(), - Signature::Batch { - signature, - merkle_proof, - } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), + Signature::Solo(sig) => sig.into(), + // Signature::Batch { + // signature, + // merkle_proof, + // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), } } } -impl, S: SignatureEncoding + Into> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { +impl + Capsule + Into, DID: Did> From> for Ipld +where + DID::Signature: Into, +{ + fn from(Envelope { signature, payload }: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); let codec = DagCborCodec; // FIXME get this from the payload From 1b6065836eed7fc31ffa5e0b65064be16498064e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 15 Feb 2024 00:29:15 -0800 Subject: [PATCH 140/234] Working on invocaton agent --- src/ability/crud.rs | 47 ++++++++++++- src/ability/crud/create.rs | 14 ++++ src/ability/crud/destroy.rs | 13 ++++ src/ability/crud/parents.rs | 9 +-- src/ability/crud/read.rs | 14 ++++ src/ability/crud/update.rs | 14 ++++ src/ability/msg.rs | 11 ++- src/ability/msg/receive.rs | 15 ++++ src/ability/preset.rs | 1 + src/ability/ucan/revoke.rs | 8 +++ src/ability/wasm/run.rs | 10 +++ src/agent.rs | 4 +- src/delegation/:50 | 117 ------------------------------- src/delegation/agent.rs | 11 +-- src/delegation/payload.rs | 8 +-- src/delegation/store.rs | 33 ++++++--- src/invocation.rs | 11 +++ src/invocation/agent.rs | 129 ++++++++++++++++++++++++----------- src/invocation/payload.rs | 68 +++++++++--------- src/invocation/resolvable.rs | 4 +- src/invocation/store.rs | 69 +++++++++++++++++-- src/reader.rs | 47 +++++++------ src/store.rs | 6 +- src/time.rs | 4 +- src/ucan.rs | 2 +- 25 files changed, 410 insertions(+), 259 deletions(-) delete mode 100644 src/delegation/:50 diff --git a/src/ability/crud.rs b/src/ability/crud.rs index ccbd3512..aaf4eb54 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -49,9 +49,14 @@ pub mod update; pub use any::Any; pub use mutate::Mutate; -pub use parents::MutableParents; - -use crate::{ability::arguments, invocation::Resolvable, proof::same::CheckSame}; +pub use parents::*; + +use crate::{ + ability::arguments, + delegation::Delegable, + invocation::Resolvable, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, +}; use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] @@ -81,6 +86,42 @@ pub enum Promised { Destroy(destroy::Promised), } +impl Delegable for Ready { + type Builder = Builder; +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// match promised { +// Promised::Create(create) => create.into(), +// Promised::Read(read) => read.into(), +// Promised::Update(update) => update.into(), +// Promised::Destroy(destroy) => destroy.into(), +// } +// } +// } + +impl CheckParents for Builder { + type Parents = MutableParents; + type ParentError = (); // FIXME + + fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { + match self { + Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), + Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), + Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), + Builder::Read(read) => match parents { + MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), + _ => Err(()), + }, + } + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 51f04b8e..f5b39ca9 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -164,6 +165,10 @@ impl, A: TryFrom> TryFrom for Generic { } } +impl Delegable for Ready { + type Builder = Builder; +} + impl Checkable for Builder { type Hierarchy = Parentful; } @@ -264,6 +269,15 @@ impl From for Promised { } } +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index b1ad4189..d3da7bef 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -128,6 +129,18 @@ impl

Command for Generic

{ const COMMAND: &'static str = "crud/destroy"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// } +// } +// } + impl> From> for Ipld { fn from(destroy: Generic

) -> Self { destroy.into() diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 6c2f106f..4dd92d24 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -6,9 +6,13 @@ use super::error::ParentError; use crate::{ - ability::command::{ParseAbility, ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, proof::{parents::CheckParents, same::CheckSame}, }; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -72,9 +76,6 @@ impl ToCommand for MutableParents { } } -use crate::ability::{arguments, command::ParseAbilityError}; -use libipld_core::ipld::Ipld; - #[derive(Debug, Clone, Error)] pub enum ParseError { #[error("Invalid `crud/*` arguments: {0}")] diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 9edefa19..3d503962 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -3,6 +3,7 @@ use super::{any as crud, error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -125,6 +126,19 @@ impl Command for Generic { const COMMAND: &'static str = "crud/read"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { read.into() diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index ff3d153c..9308e314 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -132,6 +133,19 @@ impl Command for Generic { const COMMAND: &'static str = "crud/update"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl, A: Into> From> for Ipld { fn from(create: Generic) -> Self { create.into() diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 54a02095..cc8d6c35 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -10,8 +10,9 @@ pub use receive::Receive; use crate::{ ability::arguments, + delegation::Delegable, invocation::Resolvable, - proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -34,6 +35,10 @@ pub enum Promised { Receive(receive::Promised), // FIXME } +impl Delegable for Ready { + type Builder = Builder; +} + impl TryFrom for Ready { type Error = (); @@ -102,3 +107,7 @@ impl CheckParents for Builder { } } } + +impl Checkable for Builder { + type Hierarchy = Parentful; +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 37f4aa23..3897a65f 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,6 +2,7 @@ use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, @@ -45,12 +46,26 @@ pub struct Receive { pub from: Option, } +pub type Builder = Receive; + // FIXME needs promisory version impl Command for Receive { const COMMAND: &'static str = "msg/send"; } +impl Delegable for Receive { + type Builder = Receive; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// from: promised.from.map(Into::into), +// } +// } +// } + impl Checkable for Receive { type Hierarchy = Parentful; } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 4fc1428e..6a523f1b 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { + // FIXME UCAN Crud(crud::Ready), Msg(msg::Ready), Wasm(wasm::run::Ready), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index d3da8d3a..c2d28dc5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -31,6 +31,14 @@ impl Delegable for Ready { type Builder = Builder; } +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// ucan: promised.ucan.map(Into::into), +// } +// } +// } + impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index ab08db22..6ee7d6df 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -54,6 +54,16 @@ impl Resolvable for Ready { } } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + module: promised.module.try_resolve().ok(), + function: promised.function.try_resolve().ok(), + args: promised.args.try_resolve().ok(), + } + } +} + /// A variant meant for delegation, where fields may be omitted pub type Builder = Generic, Option, Option>>; diff --git a/src/agent.rs b/src/agent.rs index c26be6c1..e2a8a4cb 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -63,7 +63,7 @@ impl< } else { let mut conds = self .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .get_chain(&self.did, &subject, &ability_builder, SystemTime::now()) .map_err(|_| ())? // FIXME .first() .1 @@ -101,7 +101,7 @@ impl< Delegation { payload, signature } } - pub fn recieve_delegation() {} + pub fn receive_delegation() {} } // impl Agent { diff --git a/src/delegation/:50 b/src/delegation/:50 deleted file mode 100644 index c74e50a7..00000000 --- a/src/delegation/:50 +++ /dev/null @@ -1,117 +0,0 @@ -use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; -use libipld_core::{cid::Cid, ipld::Ipld}; -use std::{collections::BTreeMap, marker::PhantomData}; -use thiserror::Error; -use web_time::SystemTime; - -pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { - pub did: &'a Did, - pub store: &'a mut S, - _marker: PhantomData<(B, C)>, -} - -// FIXME show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this - -impl<'a, B: Checkable, C: Condition, S: Store> Agent<'a, B, C, S> { - pub fn new(did: &'a Did, store: &'a mut S) -> Self { - Self { - did, - store, - _marker: PhantomData, - } - } - - pub fn delegate( - &self, - cid: &Cid, // FIXME remove and generate from the capsule header? - audience: Did, - subject: Did, - ability_builder: B, - new_conditions: Vec, - metadata: BTreeMap, - expiration: JsTime, - not_before: Option, - ) -> Result, DelegateError> { - if !self - .store - .can_delegate(self.did, &audience, &ability_builder, &SystemTime::now()) - { - return Err(DelegateError::ProofsNotFound); - } - - let conditions = if subject == *self.did { - new_conditions - } else { - let mut conds = self - .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) - .map_err(|_| ())? // FIXME - .first() - .1 - .payload - .conditions; - - let mut new = new_conditions; - conds.append(&mut new); - conds - }; - - let mut salt = self.did.clone().to_string().into_bytes(); - - let payload = Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - conditions, - metadata, - nonce: Nonce::generate_16(&mut salt), - expiration: expiration.into(), - not_before: not_before.map(Into::into), - }; - - Ok(self.sign(payload)) - } - - pub fn recieve( - &self, - cid: &Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<'a, >::Error>> { - if self.store.get(cid).is_ok() { - return Ok(()); - } - - if delegation.audience() != *self.did { - return Err(ReceiveError::WrongAudience(delegation.audience())); - } - - delegation - .validate_signature() - .map_err(|_| ReceiveError::InvalidSignature(cid))?; - - self.store - .insert(self.store, delegation) - .map_err(Into::into) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum DelegateError { - #[error("The current agent does not have the necessary proofs to delegate.")] - ProofsNotFound, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum ReceiveError<'a, StoreErr> { - #[error("The current agent ({0}) is not the intended audience of the delegation.")] - WrongAudience(&'a Did), - - #[error("Signature for UCAN with CID {0} is invalid.")] - InvalidSignature(&'a Cid), - - #[error(transparent)] - StoreError(#[from] StoreErr), -} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 277246fa..913a295b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -42,6 +42,7 @@ impl< metadata: BTreeMap, expiration: JsTime, not_before: Option, + now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(&mut salt); @@ -64,13 +65,7 @@ impl< let to_delegate = &self .store - .get_chain( - &self.did, - &subject, - &ability_builder, - vec![], - &SystemTime::now(), - ) + .get_chain(&self.did, &subject, &ability_builder, vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -95,7 +90,7 @@ impl< Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) } - pub fn recieve( + pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 06569542..73d3368d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -369,7 +369,7 @@ impl Payload { pub fn check<'a>( &'a self, proofs: Vec>, - now: SystemTime, + now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, @@ -421,7 +421,7 @@ impl Acc { &self, proof: &Payload, args: &arguments::Named, - now: SystemTime, + now: &SystemTime, ) -> Result::Error>> where H: Prove + Clone + Into>, @@ -434,12 +434,12 @@ impl Acc { return Err(EnvelopeError::MisalignedIssAud.into()); } - if SystemTime::from(proof.expiration.clone()) > now { + if SystemTime::from(proof.expiration.clone()) > *now { return Err(EnvelopeError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > now { + if SystemTime::from(nbf) > *now { return Err(EnvelopeError::NotYetValid.into()); } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 875bc3e3..af4ceb3f 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -18,7 +18,7 @@ use web_time::SystemTime; pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result>, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -32,24 +32,35 @@ pub trait Store { fn get_chain( &self, - aud: &DID, + audience: &DID, subject: &DID, builder: &B, conditions: Vec, now: &SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; fn can_delegate( &self, - iss: &DID, - aud: &DID, + issuer: &DID, + audience: &DID, builder: &B, conditions: Vec, now: &SystemTime, ) -> Result { - self.get_chain(aud, iss, builder, conditions, now) + self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) } + + fn get_many( + &self, + cids: &[Cid], + ) -> Result>, Self::Error> { + cids.iter().try_fold(vec![], |mut acc, cid| { + let d: &Delegation = self.get(cid)?; + acc.push(d); + Ok(acc) + }) + } } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -121,10 +132,10 @@ impl Sto where B::Hierarchy: Into>, { - type Error = Infallible; + type Error = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result>, Self::Error> { - Ok(self.ucans.get(cid)) + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(()) } fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { @@ -156,7 +167,7 @@ where builder: &B, conditions: Vec, now: &SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { @@ -197,7 +208,7 @@ where } } - chain.push((cid, d)); + chain.push((*cid, d)); if &d.payload.issuer == subject { status = Status::Complete; diff --git a/src/invocation.rs b/src/invocation.rs index e14e4d9a..4d33d418 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -25,3 +25,14 @@ pub type PromisedInvocation = Invocation; // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; + +impl Invocation { + fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { + let mut payload = self.payload; + payload.ability = f(payload.ability); + Self { + payload, + signature: self.signature, + } + } +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 2e284691..f23fb185 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,7 +1,8 @@ use super::{payload::Payload, store::Store, Invocation, Resolvable}; use crate::{ + ability::ucan, delegation, - delegation::{condition::Condition, Delegable, Delegation}, + delegation::{condition::Condition, Delegable}, did::Did, nonce::Nonce, signature::Verifiable, @@ -23,9 +24,12 @@ pub struct Agent< > { pub did: &'a DID, - pub store: &'a mut S, - pub promised_store: &'a mut P, pub delegation_store: &'a D, + pub invocation_store: &'a mut S, + pub revocation_store: &'a mut S, // FIXME just a BTRee Set pointing into invocatuin store? + + pub unresolved_promise_store: &'a mut P, + pub resolved_promise_store: &'a mut P, signer: &'a ::Signer, marker: PhantomData<(T, C)>, @@ -35,7 +39,7 @@ impl< 'a, T: Resolvable + Delegable + Clone, C: Condition, - DID: Did + ToString + Clone, + DID: Did + Clone, S: Store, P: Store, D: delegation::store::Store, @@ -44,15 +48,19 @@ impl< pub fn new( did: &'a DID, signer: &'a ::Signer, - store: &'a mut S, - promised_store: &'a mut P, + invocation_store: &'a mut S, delegation_store: &'a mut D, + revocation_store: &'a mut D, + unresolved_promise_store: &'a mut P, + resolved_promise_store: &'a mut P, ) -> Self { Self { did, - store, - promised_store, + invocation_store, delegation_store, + revocation_store, + unresolved_promise_store, + resolved_promise_store, signer, marker: PhantomData, } @@ -62,24 +70,19 @@ impl< &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, + ability: T::Promised, // FIXME Resolved needs Into metadata: BTreeMap, cause: Option, - expiration: JsTime, + expiration: Option, not_before: Option, + now: &SystemTime, // FIXME err type ) -> Result, ()> { let proofs = self .delegation_store - .get_chain( - audience, - subject, - ability.into(), - vec![], - &SystemTime::now(), - ) + .get_chain(self.did, subject, &ability.into(), vec![], now) .map_err(|_| ())? - .map(|chain| chain.map(|(cid, _)| *cid).into()) + .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); let mut seed = vec![]; @@ -93,26 +96,63 @@ impl< metadata, nonce: Nonce::generate_16(&mut seed), cause, - expiration, - not_before, + expiration: expiration.map(Into::into), + not_before: not_before.map(Into::into), }; let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - let cid: Cid = invocation.into(); - self.store.put(cid, invocation); + let cid = Cid::from(invocation); Ok(invocation) } // FIXME err = () - pub fn revoke(&mut self, cid: &Cid) -> Result<(), ()> { - todo!(); // FIXME create a revoke invocation - self.store.revoke(&cid) - } + // FIXME move to revocation agent wit own traits? + // pub fn revoke( + // &mut self, + // subject: &DID, + // cause: Option, + // cid: Cid, + // now: JsTime, + // ) -> Result<(), ()> { + // let ability = ucan::revoke::Ready { ucan: cid }; + // let proofs = if subject == self.did { + // vec![] + // } else { + // self.delegation_store + // .get_chain( + // subject, + // self.did, + // &ability.into(), + // vec![], + // &SystemTime::now(), + // ) + // .map_err(|_| ())? + // .map(|chain| chain.map(|(cid, _)| *cid).into()) + // .unwrap_or(vec![]) + // }; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject: self.did.clone(), + // audience: Some(self.did.clone()), + // ability, + // proofs, + // cause: None, + // metadata: BTreeMap::new(), + // nonce: Nonce::generate_16(&mut vec![]), + // expiration: None, + // not_before: None, + // }; + + // let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; + + // self.invocation_store.revoke(&cid)?; + // } pub fn receive( &self, invocation: Invocation, - proofs: BTreeMap>, + now: SystemTime, // FIXME return type ) -> Result, ()> { // FIXME needs varsig header @@ -123,23 +163,30 @@ impl< .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) .map_err(|_| ())?; - // FIXME pull delegations out of the store and check them - - match Resolvable::try_resolve(&invocation.payload) { - Ok(resolved) => { - // FIXME promised store - self.store.put(cid, resolved).map_err(|_| ())?; + let payload: Payload = invocation.payload; + let resolved_payload = match payload.ability.try_resolve() { + Ok(resolved_payload) => { + // NOTE Already resolved when it came over the wire + let resolved_invocation = invocation.map_ability(|_| resolved_payload); + self.store.put(cid, resolved_invocation).map_err(|_| ())?; + resolved_payload } - Err(unresolved) => self.promised_store.put(cid, unresolved).map_err(|_| ())?, - } + Err(_) => { + // FIXME check if any of the unresolved promises are in the store + self.promised_store.put(cid, invocation).map_err(|_| ())?; + todo!() // return Ok(Recipient::Other(promised)); // FIXME + } + }; - // FIXME - // FIXME promised store - self.store.put(cid, invocation).map_err(|_| ())?; + let proof_payloads = self + .delegation_store + .get_many(&invocation.payload.proofs) + .map(|d| d.payload); - for (cid, deleg) in proofs { - self.delegation_store.insert(cid, deleg).map_err(|_| ())?; - } + resolved_payload + .into() + .check(&proof_payloads, now) + .map_err(|_| ())?; if invocation.payload.audience != Some(*self.did) { return Ok(Recipient::Other(invocation)); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9aa7e61b..ad17031b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -34,7 +34,7 @@ pub struct Payload { pub nonce: Nonce, pub not_before: Option, - pub expiration: Timestamp, + pub expiration: Option, } // FIXME cleanup traits @@ -47,7 +47,7 @@ impl Payload { pub fn check( self, proofs: Vec::Hierarchy, C, DID>>, - now: SystemTime, + now: &SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where T: Delegable, @@ -116,38 +116,38 @@ impl, DID: Did> From> for arguments::N /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl Resolvable for Payload -where - arguments::Named: From, - Ipld: From, - T::Promised: ToCommand, -{ - type Promised = Promised; - - fn try_resolve(promised: Promised) -> Result { - match ::try_resolve(promised.ability) { - Ok(resolved_ability) => Ok(Payload { - issuer: promised.issuer, - subject: promised.subject, - audience: promised.audience, - - ability: resolved_ability, - - proofs: promised.proofs, - cause: promised.cause, - metadata: promised.metadata, - nonce: promised.nonce, - - not_before: promised.not_before, - expiration: promised.expiration, - }), - Err(promised_ability) => Err(Payload { - ability: promised_ability, - ..promised - }), - } - } -} +// impl Resolvable for Payload +// where +// arguments::Named: From, +// Ipld: From, +// T::Promised: ToCommand, +// { +// type Promised = Promised; +// +// fn try_resolve(promised: Promised) -> Result { +// match ::try_resolve(promised.ability) { +// Ok(resolved_ability) => Ok(Payload { +// issuer: promised.issuer, +// subject: promised.subject, +// audience: promised.audience, +// +// ability: resolved_ability, +// +// proofs: promised.proofs, +// cause: promised.cause, +// metadata: promised.metadata, +// nonce: promised.nonce, +// +// not_before: promised.not_before, +// expiration: promised.expiration, +// }), +// Err(promised_ability) => Err(Payload { +// ability: promised_ability, +// ..promised +// }), +// } +// } +// } impl Serialize for Payload where diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index bc9103ea..8cac5758 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,7 +1,7 @@ -use crate::ability::arguments; +use crate::{ability::arguments, delegation::Delegable}; use libipld_core::ipld::Ipld; -pub trait Resolvable: Sized { +pub trait Resolvable: Delegable { type Promised: Into>; // FIXME indeed needed to get teh right err type diff --git a/src/invocation/store.rs b/src/invocation/store.rs index f15679cf..9196f041 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,17 +1,20 @@ use super::Invocation; -use crate::did::Did; -use libipld_core::cid::Cid; -use std::collections::BTreeMap; +use crate::{did::Did, invocation::Resolvable}; +use libipld_core::{cid::Cid, link::Link}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; use thiserror::Error; pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error>; fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; - fn has(&self, cid: &Cid) -> Result { + fn has(&self, cid: Cid) -> Result { Ok(self.get(cid).is_ok()) } } @@ -28,8 +31,8 @@ pub struct NotFound; impl Store for MemoryStore { type Error = NotFound; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { - self.store.get(cid).ok_or(NotFound) + fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error> { + self.store.get(&cid).ok_or(NotFound) } fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { @@ -37,3 +40,55 @@ impl Store for MemoryStore { Ok(()) } } + +//////// + +pub trait PromiseIndex { + type PromiseIndexError; + + fn put_waiting( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError>; + + fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError>; +} + +pub struct MemoryPromiseIndex { + pub index: BTreeMap>, +} + +impl PromiseIndex for MemoryPromiseIndex { + type PromiseIndexError = NotFound; + + fn put_waiting( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError> { + self.index + .insert(invocation, BTreeSet::from_iter(waiting_on)); + + Ok(()) + } + + fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError> { + Ok(match waiting_on.pop() { + None => BTreeSet::new(), + Some(first) => waiting_on + .iter() + .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { + let next = self.index.get(cid).ok_or(NotFound)?; + + let reduced = acc.intersection(*next.into()).collect(); + if reduced.is_empty() { + return Err(()); + } + + Ok(reduced) + }) + .unwrap_or_default(), + }) + } +} diff --git a/src/reader.rs b/src/reader.rs index c2614569..599673a9 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -245,22 +245,31 @@ impl From>> for Reader { } } -impl Resolvable for Reader -where - Reader: Into>, -{ - type Promised = Reader; - - fn try_resolve(promised: Self::Promised) -> Result { - match T::try_resolve(promised.val) { - Ok(val) => Ok(Reader { - env: promised.env, - val, - }), - Err(val) => Err(Reader { - env: promised.env, - val, - }), - } - } -} +// use crate::proof::{checkable::Checkable, same::CheckSame}; +// +// impl Delegable for Reader +// where +// Reader: Checkable + CheckSame, +// { +// type Builder = Reader; +// } +// +// impl Resolvable for Reader +// where +// Reader: Into>, +// { +// type Promised = Reader; +// +// fn try_resolve(promised: Self::Promised) -> Result { +// match T::try_resolve(promised.val) { +// Ok(val) => Ok(Reader { +// env: promised.env, +// val, +// }), +// Err(val) => Err(Reader { +// env: promised.env, +// val, +// }), +// } +// } +// } diff --git a/src/store.rs b/src/store.rs index c388aaa5..84826f56 100644 --- a/src/store.rs +++ b/src/store.rs @@ -21,7 +21,7 @@ where type Error; /// Read a token from the store - fn read(&self, cid: &Cid) -> Result, Self::Error> + fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode; @@ -42,7 +42,7 @@ where type Error; /// Read a token from the store - async fn read(&self, cid: &Cid) -> Result, Self::Error> + async fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode; @@ -66,7 +66,7 @@ pub struct InMemoryStore { impl Store for InMemoryStore { type Error = anyhow::Error; - fn read(&self, cid: &Cid) -> Result, Self::Error> + fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode, { diff --git a/src/time.rs b/src/time.rs index 03101e98..b47a09d9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -20,7 +20,7 @@ pub fn now() -> u64 { /// All timestamps that this library can handle. /// /// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Timestamp { /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. JsSafe(JsTime), @@ -112,7 +112,7 @@ impl TryFrom for Timestamp { /// and is thus sufficient for "nearly" all auth use cases. /// /// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct JsTime { time: SystemTime, diff --git a/src/ucan.rs b/src/ucan.rs index ba5e6584..c394710b 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -376,7 +376,7 @@ where } /// Return the `prf` field of the UCAN payload - pub fn proofs(&self) -> Option> { + pub fn proofs(&self) -> Option> { self.payload .prf .as_ref() From d99e16ae138bdea8abc709fbef0363192e38b4d9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 15 Feb 2024 22:53:48 -0800 Subject: [PATCH 141/234] Most of the way through invocation agent --- src/ability/crud.rs | 20 ++-- src/ability/crud/create.rs | 19 ++-- src/ability/crud/read.rs | 16 +-- src/ability/crud/update.rs | 104 ++++++++++++------ src/agent.rs | 2 +- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 10 ++ src/invocation/agent.rs | 200 +++++++++++++++++++++-------------- src/invocation/payload.rs | 73 ++++++++++++- src/invocation/resolvable.rs | 12 ++- src/ipld/enriched.rs | 99 +++++++++++++++++ src/ipld/newtype.rs | 11 ++ src/ipld/promised.rs | 72 ++++++++++--- src/signature.rs | 46 ++++---- 14 files changed, 507 insertions(+), 179 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index aaf4eb54..c080ae6d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -94,16 +94,16 @@ impl Checkable for Builder { type Hierarchy = Parentful; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// match promised { -// Promised::Create(create) => create.into(), -// Promised::Read(read) => read.into(), -// Promised::Update(update) => update.into(), -// Promised::Destroy(destroy) => destroy.into(), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Create(create) => Builder::Create(create.into()), + Promised::Read(read) => Builder::Read(read.into()), + Promised::Update(update) => Builder::Update(update.into()), + Promised::Destroy(destroy) => Builder::Destroy(destroy.into()), + } + } +} impl CheckParents for Builder { type Parents = MutableParents; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index f5b39ca9..b6ea934b 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -269,14 +269,17 @@ impl From for Promised { } } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +// FIXME may want to name this something other than a TryFrom +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(|x| x.try_resolve().ok()), + args: promised + .args + .and_then(|x| x.try_resolve().ok()?.try_into().ok()), + } + } +} impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 3d503962..7bd237a6 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -130,14 +130,14 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.map(Into::into), + args: promised.args.map(Into::into), + } + } +} impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 9308e314..64654b15 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -16,19 +16,6 @@ use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be updated. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments to be passed in the update. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -60,7 +47,17 @@ pub struct Generic { /// /// style updateready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable ability for updating existing agents. @@ -93,7 +90,17 @@ pub type Ready = Generic>; /// /// style update stroke:orange; /// ``` -pub type Builder = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/update` ability (but possibly awaiting another @@ -127,9 +134,19 @@ pub type Builder = Generic>; /// /// style updatepromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option>, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option, +} -impl Command for Generic { +impl Command for Ready { const COMMAND: &'static str = "crud/update"; } @@ -137,22 +154,43 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(r: Ready) -> Self { + Builder { + path: r.path, + args: r.args, + } + } +} + +impl From for Ready { + fn from(builder: Builder) -> Self { + Ready { + path: builder.path, + args: builder.args, + } + } +} + +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(Into::into), + args: promised.args.and_then(|res| match res.try_resolve() { + Ok(args) => Some(args.into()), + Err(unresolved) => None, + }), + } + } +} -impl, A: Into> From> for Ipld { - fn from(create: Generic) -> Self { +impl From for Ipld { + fn from(create: Ready) -> Self { create.into() } } -impl, A: TryFrom> TryFrom for Generic { +impl TryFrom for Ready { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -161,15 +199,15 @@ impl, A: TryFrom> TryFrom for Generic { return Err(()); // FIXME } - Ok(Generic { + Ok(Ready { path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .get("path") + .map(|ipld| (ipld::Newtype(*ipld)).try_into().map_err(|_| ())) .transpose()?, args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .get("args") + .map(|ipld| (*ipld).try_into().map_err(|_| ())) .transpose()?, }) } else { diff --git a/src/agent.rs b/src/agent.rs index e2a8a4cb..7192f3bd 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -84,7 +84,7 @@ impl< ability_builder, conditions, metadata, - nonce: Nonce::generate_16(&mut salt), + nonce: Nonce::generate_12(&mut salt), expiration: expiration.into(), not_before: not_before.map(Into::into), }; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 913a295b..87769369 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -45,7 +45,7 @@ impl< now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); - let nonce = Nonce::generate_16(&mut salt); + let nonce = Nonce::generate_12(&mut salt); if subject == *self.did { let payload: Payload = Payload { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 73d3368d..5e83b655 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -461,3 +461,13 @@ impl Acc { .map_err(DelegationError::SemanticError) } } + +// use crate::proof::{parentful::Parentful, parentless::Parentless}; +// +// impl>, C, DID: Did> Checkable for Payload { +// type Hierarchy = Parentless>; +// } +// +// impl>, C, DID: Did> Checkable for Payload { +// type Hierarchy = Parentful>; +// } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index f23fb185..a3ff1d88 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,14 +1,21 @@ use super::{payload::Payload, store::Store, Invocation, Resolvable}; use crate::{ - ability::ucan, + ability::{arguments, ucan}, delegation, delegation::{condition::Condition, Delegable}, did::Did, nonce::Nonce, - signature::Verifiable, + proof::{checkable::Checkable, prove::Prove}, + signature::{Signature, Verifiable}, time::JsTime, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, +}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -26,8 +33,6 @@ pub struct Agent< pub delegation_store: &'a D, pub invocation_store: &'a mut S, - pub revocation_store: &'a mut S, // FIXME just a BTRee Set pointing into invocatuin store? - pub unresolved_promise_store: &'a mut P, pub resolved_promise_store: &'a mut P, @@ -70,14 +75,14 @@ impl< &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, // FIXME Resolved needs Into + ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? metadata: BTreeMap, cause: Option, expiration: Option, not_before: Option, now: &SystemTime, // FIXME err type - ) -> Result, ()> { + ) -> Result, ()> { let proofs = self .delegation_store .get_chain(self.did, subject, &ability.into(), vec![], now) @@ -94,105 +99,135 @@ impl< ability, proofs, metadata, - nonce: Nonce::generate_16(&mut seed), + nonce: Nonce::generate_12(&mut seed), cause, expiration: expiration.map(Into::into), not_before: not_before.map(Into::into), }; - let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - let cid = Cid::from(invocation); - Ok(invocation) + Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) } - // FIXME err = () - // FIXME move to revocation agent wit own traits? - // pub fn revoke( - // &mut self, - // subject: &DID, - // cause: Option, - // cid: Cid, - // now: JsTime, - // ) -> Result<(), ()> { - // let ability = ucan::revoke::Ready { ucan: cid }; - // let proofs = if subject == self.did { - // vec![] - // } else { - // self.delegation_store - // .get_chain( - // subject, - // self.did, - // &ability.into(), - // vec![], - // &SystemTime::now(), - // ) - // .map_err(|_| ())? - // .map(|chain| chain.map(|(cid, _)| *cid).into()) - // .unwrap_or(vec![]) - // }; - - // let payload = Payload { - // issuer: self.did.clone(), - // subject: self.did.clone(), - // audience: Some(self.did.clone()), - // ability, - // proofs, - // cause: None, - // metadata: BTreeMap::new(), - // nonce: Nonce::generate_16(&mut vec![]), - // expiration: None, - // not_before: None, - // }; - - // let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - - // self.invocation_store.revoke(&cid)?; - // } - pub fn receive( &self, - invocation: Invocation, - now: SystemTime, + promised: Invocation, + now: &SystemTime, // FIXME return type - ) -> Result, ()> { + ) -> Result>, ()> + where + ::Hierarchy: Clone + Into>, + T::Builder: Clone + Checkable + Prove + Into>, + { // FIXME needs varsig header - let cid = Cid::from(invocation); - - invocation + let mut buffer = vec![]; + Ipld::from(promised) + .encode(DagCborCodec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + let mut encoded = vec![]; + promised + .payload + // FIXME use the varsig headre to get the codec + .encode(DagCborCodec, &mut encoded) + .expect("FIXME"); + + promised .verifier() - .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) + .verify( + &encoded, + &match promised.signature { + Signature::Solo(sig) => sig, + }, + ) .map_err(|_| ())?; - let payload: Payload = invocation.payload; - let resolved_payload = match payload.ability.try_resolve() { - Ok(resolved_payload) => { - // NOTE Already resolved when it came over the wire - let resolved_invocation = invocation.map_ability(|_| resolved_payload); - self.store.put(cid, resolved_invocation).map_err(|_| ())?; - resolved_payload - } + let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability) { + Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store - self.promised_store.put(cid, invocation).map_err(|_| ())?; - todo!() // return Ok(Recipient::Other(promised)); // FIXME + // FIXME check if it's actually unresolved + self.unresolved_promise_store + .put(cid, promised) + .map_err(|_| ())?; + + todo!() + // return Ok(Recipient::Other(promised)); // FIXME } }; let proof_payloads = self .delegation_store - .get_many(&invocation.payload.proofs) - .map(|d| d.payload); + .get_many(&promised.payload.proofs) + .map_err(|_| ())? + .into_iter() + .map(|d| d.payload) + .collect(); - resolved_payload - .into() - .check(&proof_payloads, now) + let resolved_payload = promised.payload.map_ability(|_| resolved_ability); + + delegation::Payload::::from(resolved_payload) + .check(proof_payloads, now) .map_err(|_| ())?; - if invocation.payload.audience != Some(*self.did) { - return Ok(Recipient::Other(invocation)); + if promised.payload.audience != Some(*self.did) { + return Ok(Recipient::Other(resolved_payload)); } - Ok(Recipient::You(invocation)) + Ok(Recipient::You(resolved_payload)) + } + + pub fn revoke( + &mut self, + subject: &DID, + cause: Option, + cid: Cid, + now: &JsTime, + // FIXME return type + ) -> Result, ()> + where + T: From, + { + let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); + let proofs = if subject == self.did { + vec![] + } else { + self.delegation_store + .get_chain( + subject, + self.did, + &ability.into(), + vec![], + &SystemTime::now(), + ) + .map_err(|_| ())? + .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + .unwrap_or(vec![]) + }; + + let payload = Payload { + issuer: self.did.clone(), + subject: self.did.clone(), + audience: Some(self.did.clone()), + ability, + proofs, + cause: None, + metadata: BTreeMap::new(), + nonce: Nonce::generate_12(&mut vec![]), + expiration: None, + not_before: None, + }; + + let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; + + self.delegation_store.revoke(cid).map_err(|_| ())?; + Ok(invocation) } } @@ -200,3 +235,8 @@ pub enum Recipient { You(T), Other(T), } + +// impl Agent { +// FIXME err = () +// FIXME move to revocation agent wit own traits? +// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ad17031b..cf5de2b9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,7 +9,15 @@ use crate::{ signature::Verifiable, time::Timestamp, }; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use anyhow; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::{Codec, Encode}, + error::SerdeError, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, + serde as ipld_serde, +}; use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -44,6 +52,24 @@ pub struct Payload { // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type impl Payload { + pub fn map_ability(self, f: F) -> Payload + where + F: FnOnce(T) -> U, + { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + ability: f(self.ability), + proofs: self.proofs, + cause: self.cause, + metadata: self.metadata, + nonce: self.nonce, + not_before: self.not_before, + expiration: self.expiration, + } + } + pub fn check( self, proofs: Vec::Hierarchy, C, DID>>, @@ -116,13 +142,46 @@ impl, DID: Did> From> for arguments::N /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; +// impl Delegable for Payload { +// type Builder = Payload; +// } + +// use crate::proof::parentful::Parentful; +// +// impl Checkable for Payload +// where +// T::Builder: Checkable>, +// { +// type Hierarchy = (); +// } + +// impl TryFrom> for Payload { +// fn from(payload: Payload) -> Self { +// Payload { +// issuer: payload.issuer, +// subject: payload.subject, +// audience: payload.audience, +// +// ability: T::from(payload.ability), +// +// proofs: payload.proofs, +// cause: payload.cause, +// metadata: payload.metadata, +// nonce: payload.nonce, +// +// not_before: payload.not_before, +// expiration: payload.expiration, +// } +// } +// } + // impl Resolvable for Payload // where // arguments::Named: From, // Ipld: From, // T::Promised: ToCommand, // { -// type Promised = Promised; +// type Promised = Promised; // // fn try_resolve(promised: Promised) -> Result { // match ::try_resolve(promised.ability) { @@ -315,3 +374,13 @@ impl>, DID: Did> From } } } + +impl Encode for Payload +where + Ipld: Encode, +{ + fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { + let ipld: Ipld = (*self).into(); + ipld.encode(codec, writer) + } +} diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 8cac5758..0f4d12dc 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -2,8 +2,18 @@ use crate::{ability::arguments, delegation::Delegable}; use libipld_core::ipld::Ipld; pub trait Resolvable: Delegable { - type Promised: Into>; + // FIXME rename "Unresolved" + type Promised: Into + Into>; // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; + + // FIXME better name + // NOTE this takes anything taht doesn't resolve and returns None on those fields + // FIXME no, jsut use Into and NOTE THIS IN THE DOCS + // fn resolve_to_builder(&self) -> Self::Builder; } + +// impl Delegable for Ipld { +// type Builder = Option; +// } diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 120feaa9..c0752c25 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,3 +1,4 @@ +use crate::invocation::Resolvable; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -32,6 +33,104 @@ pub enum Enriched { Link(Cid), } +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a, T> { + inbound: Vec>, + outbound: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Item<'a, T> { + Node(&'a Enriched), + Inner(&'a T), +} + +impl<'a, T> PostOrderIpldIter<'a, T> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(enriched: &'a Enriched) -> Self { + PostOrderIpldIter { + inbound: vec![Item::Node(enriched)], + outbound: vec![], + } + } +} + +impl<'a, T> IntoIterator for &'a Enriched { + type Item = Item<'a, T>; + type IntoIter = PostOrderIpldIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + PostOrderIpldIter::new(&self) + } +} + +impl<'a, T: Clone> FromIterator> for &'a Enriched { + fn from_iter>>(it: I) -> Self { + &it.into_iter().fold(Enriched::Null, |acc, x| match x { + Item::Node(Enriched::Null) => Enriched::Null, + Item::Node(Enriched::Bool(b)) => Enriched::Bool(*b), + Item::Node(Enriched::Integer(i)) => Enriched::Integer(*i), + Item::Node(Enriched::Float(f)) => Enriched::Float(*f), + Item::Node(Enriched::String(s)) => Enriched::String(s.clone()), + Item::Node(Enriched::Bytes(b)) => Enriched::Bytes(b.clone()), + Item::Node(Enriched::Link(c)) => Enriched::Link(c.clone()), + Item::Node(Enriched::List(vec)) => { + let mut list = vec![]; + for item in vec { + list.push(item); + } + Enriched::List(list.iter().map(|a| (*a).clone()).collect()) + } + Item::Node(Enriched::Map(btree)) => { + let mut map = BTreeMap::new(); + for (k, v) in btree { + map.insert(k.clone(), (*v).clone()); + } + Enriched::Map(map) + } + Item::Inner(_) => acc, + }) + } +} + +impl<'a, T> From<&'a Enriched> for PostOrderIpldIter<'a, T> { + fn from(enriched: &'a Enriched) -> Self { + PostOrderIpldIter::new(enriched) + } +} + +impl<'a, T> Iterator for PostOrderIpldIter<'a, T> { + type Item = Item<'a, T>; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(map @ Item::Node(Enriched::Map(btree))) => { + self.outbound.push(map); + + for node in btree.values() { + self.inbound.push(Item::Inner(node)); + } + } + + Some(list @ Item::Node(Enriched::List(vector))) => { + self.outbound.push(list); + + for node in vector { + self.inbound.push(Item::Inner(node)); + } + } + Some(node) => self.outbound.push(node), + } + } + } +} + impl> From for Enriched { fn from(ipld: Ipld) -> Self { match ipld { diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f2252979..25b0c5cc 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -55,6 +55,17 @@ impl From for Newtype { } } +impl TryFrom for PathBuf { + type Error = (); // FIXME + + fn try_from(wrapped: Newtype) -> Result { + match wrapped.0 { + Ipld::String(s) => Ok(PathBuf::from(s)), + _ => Err(()), + } + } +} + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8e0f62b1..83ae2029 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,5 +1,9 @@ -use super::enriched::Enriched; -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use super::enriched::{Enriched, Item}; +use crate::ability::arguments; +use crate::invocation::{ + promise::{self, Promise, PromiseAny, PromiseErr, PromiseOk}, + Resolvable, // FIXME this shoudl be under promise +}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -8,16 +12,6 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); -impl Promised { - // FIXME note that this is different from the failable version which is more like - // a try_reoslve... which has a note at the bottom on this module - pub fn serialize_as_ipld(&self) -> Ipld { - ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand - } -} - -// Promise variants into Promised - impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) @@ -51,6 +45,7 @@ impl From for Promised { } // FIXME THIS is a great example of a try_resolve +// FIXME is this recursive? Will this blow the stack? impl TryFrom for Ipld { type Error = Promised; @@ -84,3 +79,56 @@ impl TryFrom for Ipld { } } } + +// FIXME surely the other version in this module can't be right if this also works? +// FIXME this is more iterative right? +// impl TryFrom for Ipld { +// // impl Resolvable for Ipld { +// type Error = Self; +// // type Promised = Promised; +// +// fn try_from(promised: Promised) -> Result { +// fn handle(enriched: super::Enriched) -> Result { +// enriched +// .into_iter() +// .try_fold(vec![], |mut acc, next| { +// match next { +// Item::Inner(promised) => { +// let inner: Ipld = Resolvable::try_resolve(*promised).map_err(|_| ())?; +// +// acc.push(inner); +// } +// Item::Node(node) => { +// let _ = Ipld::try_from(*node).map_err(|_| ())?; +// } +// } +// Ok(acc) +// }) +// .map(|vec| vec.first().expect("FIXME").clone()) +// } +// +// match promised.0 { +// Promise::Ok(promise_ok) => match promise_ok { +// PromiseOk::Fulfilled(enriched) => { +// handle(enriched).map_err(|_| PromiseOk::Fulfilled(enriched).into()) +// } +// PromiseOk::Pending(_) => Err(promised), +// }, +// Promise::Err(promise_err) => match promise_err { +// PromiseErr::Rejected(enriched) => { +// handle(enriched).map_err(|_| PromiseErr::Rejected(enriched).into()) +// } +// PromiseErr::Pending(_) => Err(promised), +// }, +// Promise::Any(promise_any) => match promise_any { +// PromiseAny::Fulfilled(enriched) => { +// handle(enriched).map_err(|_| PromiseAny::Fulfilled(enriched).into()) +// } +// PromiseAny::Rejected(enriched) => { +// handle(enriched).map_err(|_| PromiseAny::Rejected(enriched).into()) +// } +// PromiseAny::Pending(_) => Err(promised), +// }, +// } +// } +// } diff --git a/src/signature.rs b/src/signature.rs index e40bee94..81a3b85e 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,6 +1,7 @@ //! Signatures and cryptographic envelopes. use crate::{capsule::Capsule, did::Did}; +use anyhow; use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, @@ -35,7 +36,7 @@ pub struct Envelope + Capsule, DID: Did> { pub payload: T, } -impl + Clone + Into, DID: Did> Envelope { +impl + Into, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } @@ -50,20 +51,13 @@ impl + Clone + Into, DID: Did> Envelope, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); let mut buffer = vec![]; ipld.encode(codec, &mut buffer) .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - let cid: Cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; + let sig = signer.try_sign(&buffer).map_err(|_| ())?; Ok(Envelope { signature: Signature::Solo(sig), @@ -122,22 +116,28 @@ pub enum Signature { // Right = 1, //} -impl> From> for Ipld { - fn from(signature: Signature) -> Self { - match signature { - Signature::Solo(sig) => sig.into(), - // Signature::Batch { - // signature, - // merkle_proof, - // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), - } +impl + Capsule + Into, DID: Did> Encode for Envelope +where + Ipld: Encode, +{ + fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { + Ipld::from(*self).encode(codec, writer) } } -impl + Capsule + Into, DID: Did> From> for Ipld -where - DID::Signature: Into, -{ +// impl> From> for Ipld { +// fn from(signature: Signature) -> Self { +// match signature { +// Signature::Solo(sig) => sig.into(), +// // Signature::Batch { +// // signature, +// // merkle_proof, +// // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), +// } +// } +// } + +impl + Capsule + Into, DID: Did> From> for Ipld { fn from(Envelope { signature, payload }: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); From acc564e2905f26c8f62c0b34bb06c6c39ca1c655 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 16:13:10 -0800 Subject: [PATCH 142/234] compiles again... back to cleanup --- src/ability/arguments.rs | 17 ++++++++++ src/ability/crud/create.rs | 33 +++++++++++++------- src/ability/crud/destroy.rs | 32 +++++++------------ src/ability/crud/read.rs | 36 ++++++++++++--------- src/ability/crud/update.rs | 50 +++++++++++------------------- src/ability/msg.rs | 10 +++++- src/ability/msg/receive.rs | 46 ++++++++++++++++----------- src/ability/preset.rs | 10 ++++++ src/ability/ucan/revoke.rs | 14 ++++----- src/delegation/store.rs | 4 +-- src/invocation.rs | 2 +- src/invocation/agent.rs | 28 +++++++++-------- src/invocation/payload.rs | 21 ++++++++----- src/invocation/promise/resolves.rs | 7 +++++ src/invocation/store.rs | 14 ++++++--- src/ipld/enriched.rs | 19 ++++++------ src/signature.rs | 39 ++++++++++++----------- 17 files changed, 220 insertions(+), 162 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 305feadb..0e96c6ef 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -5,6 +5,23 @@ mod named; pub use named::{Named, NamedError}; use crate::{invocation::promise::Resolves, ipld}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; // FIXME move under invoc::promise? pub type Promised = Resolves>; + +impl Promised { + pub fn try_resolve_option(self) -> Option> { + match self.try_resolve() { + Err(_) => None, + Ok(named_promises) => named_promises + .iter() + .try_fold(BTreeMap::new(), |mut map, (k, v)| { + map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); + Some(map) + }) + .map(Named), + } + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index b6ea934b..dff88b40 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -226,18 +226,29 @@ impl From for arguments::Named { ); } + // FIXME gross code if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() - }) - .into(), - ); + match args_res.try_resolve() { + Ok(named_promises) => { + let value = named_promises.iter().try_fold( + arguments::Named::::new(), + |mut acc, (k, v)| { + // FIXME extract + acc.insert(k.into(), v.clone().try_into().ok()?); + Some(acc) + }, + ); + + match value { + Some(v) => { + named.insert("args".to_string(), v.into()); + } + None => {} + } + } + + Err(_unresolved) => {} + } } named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index d3da7bef..68fd7b39 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -133,14 +133,6 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// } -// } -// } - impl> From> for Ipld { fn from(destroy: Generic

>::PromiseStoreError: fmt::Debug, { - // FIXME needs varsig header let mut buffer = vec![]; Ipld::from(promised.clone()) - .encode(DagCborCodec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + .encode(*promised.varsig_header().codec(), &mut buffer) + .map_err(ReceiveError::EncodingError)?; let cid: Cid = CidGeneric::new_v1( DagCborCodec.into(), MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), + .map_err(ReceiveError::MultihashError)?, ); let mut encoded = vec![]; Ipld::from(promised.payload().clone()) - // FIXME use the varsig headre to get the codec - .encode(DagCborCodec, &mut encoded) - .expect("FIXME"); + .encode(*promised.0.varsig_header.codec(), &mut encoded) + .map_err(ReceiveError::EncodingError)?; promised .verifier() - .verify( - &encoded, - &match promised.signature() { - Witness::Signature(ref sig) => sig.clone(), - }, - ) - .map_err(|_| ())?; + .verify(&encoded, &promised.signature()) + .map_err(ReceiveError::SigVerifyError)?; let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store // FIXME check if it's actually unresolved + + // self.invocation_store + // .put(cid.clone(), promised.clone()) + // .map_err(ReceiveError::PromiseStoreError)?; + self.unresolved_promise_store - .put(cid, promised) - .map_err(|_| ())?; + .put(cid, todo!()) // cid for promised) + .map_err(ReceiveError::PromiseStoreError)?; todo!() // return Ok(Recipient::Other(promised)); // FIXME @@ -165,7 +172,7 @@ where let proof_payloads = self .delegation_store .get_many(&promised.proofs()) - .map_err(|_| ())? + .map_err(ReceiveError::DelegationStoreError)? .into_iter() .map(|d| d.payload()) .collect(); @@ -174,7 +181,7 @@ where delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) - .map_err(|_| ())?; + .map_err(ReceiveError::DelegationValidationError)?; if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); @@ -189,8 +196,9 @@ where cause: Option, cid: Cid, now: Timestamp, + varsig_header: V, // FIXME return type - ) -> Result, ()> + ) -> Result, ()> where T: From, { @@ -224,7 +232,8 @@ where issued_at: None, }; - let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; + let invocation = + Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; self.delegation_store.revoke(cid).map_err(|_| ())?; Ok(invocation) @@ -236,3 +245,37 @@ pub enum Recipient { You(T), Other(T), } + +#[derive(Debug, Error)] +pub enum ReceiveError, DID: Did, C: fmt::Debug, D> +where + delegation::ValidationError< + <<::Builder as Checkable>::Hierarchy as Prove>::Error, + C, + >: fmt::Debug, +

>::PromiseStoreError: fmt::Debug, +{ + #[error("encoding error: {0}")] + EncodingError(#[from] libipld_core::error::Error), + + #[error("multihash error: {0}")] + MultihashError(#[from] libipld_core::multihash::Error), + + #[error("signature verification error: {0}")] + SigVerifyError(#[from] signature::Error), + + #[error("promise store error: {0}")] + PromiseStoreError(#[source]

>::PromiseStoreError), + + #[error("delegation store error: {0}")] + DelegationStoreError(#[source] D), + + #[error("delegation validation error: {0}")] + DelegationValidationError( + #[source] + delegation::ValidationError< + <<::Builder as Checkable>::Hierarchy as Prove>::Error, + C, + >, + ), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 7044e40d..ed3c2a04 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -20,6 +20,15 @@ use serde::{ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { /// The subject of the [`Invocation`]. @@ -400,3 +409,60 @@ impl From> for Ipld { payload.into() } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Payload +where + T::Strategy: 'static, + DID::Parameters: Clone, +{ + type Parameters = (T::Parameters, DID::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, did_args): Self::Parameters) -> Self::Strategy { + ( + T::arbitrary_with(t_args), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args.clone()), + Option::::arbitrary_with((0.5.into(), did_args)), + Nonce::arbitrary(), + prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..25), + Option::::arbitrary().prop_map(|opt_nt| opt_nt.map(|nt| nt.cid)), + Option::::arbitrary(), + Option::::arbitrary(), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.0)) + .collect::>() + }), + ) + .prop_map( + |( + ability, + issuer, + subject, + audience, + nonce, + proofs, + cause, + expiration, + issued_at, + metadata, + )| { + Payload { + issuer, + subject, + audience, + ability, + proofs, + cause, + nonce, + metadata, + issued_at, + expiration, + } + }, + ) + .boxed() + } +} diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index a8ecbd78..1825df10 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -7,6 +7,9 @@ use serde::{ }; use std::fmt; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. /// /// Unlike [`Resolves`][super::Resolves]: @@ -210,3 +213,25 @@ impl TryFrom> for PromiseErr { } } } + +#[cfg(feature = "test_utils")] +impl Arbitrary + for PromiseAny +where + T::Strategy: 'static, + T::Parameters: 'static, + E::Strategy: 'static, + E::Parameters: 'static, +{ + type Parameters = (T::Parameters, E::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, e_args): Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseAny::Fulfilled), + E::arbitrary_with(e_args).prop_map(PromiseAny::Rejected), + cid::Newtype::arbitrary().prop_map(|nt| PromiseAny::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index e30ce53e..01c47403 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that only selects the `{"err": error}` branch of a result. /// /// On resolution, the value is unwrapped from the `{"err": error}`, @@ -90,3 +93,21 @@ impl> TryFrom> for PromiseErr { E::try_from(Ipld::from(args)).map(PromiseErr::Rejected) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for PromiseErr +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseErr::Rejected), + cid::Newtype::arbitrary().prop_map(|nt| PromiseErr::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 56f50e0a..4cc406a9 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that only selects the `{"ok": value}` branch of a result. /// /// On resolution, the value is unwrapped from the `{"ok": value}`, @@ -91,3 +94,21 @@ impl> TryFrom> for PromiseOk { T::try_from(Ipld::from(args)).map(PromiseOk::Fulfilled) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for PromiseOk +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseOk::Fulfilled), + cid::Newtype::arbitrary().prop_map(|nt| PromiseOk::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index d998485e..dcfc5651 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -3,6 +3,9 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. /// /// Unlike [`PromiseAny`][super::PromiseAny]: @@ -310,3 +313,21 @@ impl From> for PromiseAny { } } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Resolves +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = (T::Parameters, T::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((ok_args, err_args): Self::Parameters) -> Self::Strategy { + prop_oneof![ + PromiseOk::::arbitrary_with(ok_args).prop_map(Resolves::Ok), + PromiseErr::::arbitrary_with(err_args).prop_map(Resolves::Err), + ] + .boxed() + } +} diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index ae960dfc..43ea8ee4 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -12,20 +12,20 @@ pub struct MemoryStore { } impl Store for MemoryStore { - type PromiseIndexError = Infallible; + type PromiseStoreError = Infallible; fn put( &mut self, - waiting_on: Vec, invocation: Cid, - ) -> Result<(), Self::PromiseIndexError> { + waiting_on: Vec, + ) -> Result<(), Self::PromiseStoreError> { self.index .insert(invocation, BTreeSet::from_iter(waiting_on)); Ok(()) } - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError> { + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 200ac656..0bee796f 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -3,12 +3,12 @@ use libipld_core::cid::Cid; use std::collections::BTreeSet; pub trait Store { - type PromiseIndexError; + type PromiseStoreError; // NOTE put_waiting - fn put(&mut self, waiting_on: Vec, invocation: Cid) - -> Result<(), Self::PromiseIndexError>; + fn put(&mut self, invocation: Cid, waiting_on: Vec) + -> Result<(), Self::PromiseStoreError>; // NOTE get waiting - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError>; + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError>; } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index a270db1d..d9ef104d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,35 +1,51 @@ //! Storage for [`Invocation`]s. use super::Invocation; -use crate::did::Did; -use libipld_core::cid::Cid; +use crate::{crypto::varsig, did::Did}; +use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store { - type Error; +pub trait Store, Enc: Codec + Into + TryFrom> { + type InvocationStoreError; - fn get(&self, cid: Cid) -> Result>, Self::Error>; + fn get( + &self, + cid: Cid, + ) -> Result>, Self::InvocationStoreError>; - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError>; - fn has(&self, cid: Cid) -> Result { + fn has(&self, cid: Cid) -> Result { Ok(self.get(cid).is_ok()) } } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - store: BTreeMap>, +pub struct MemoryStore, Enc: Codec + Into + TryFrom> { + store: BTreeMap>, } -impl Store for MemoryStore { - type Error = Infallible; +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore +{ + type InvocationStoreError = Infallible; - fn get(&self, cid: Cid) -> Result>, Self::Error> { + fn get( + &self, + cid: Cid, + ) -> Result>, Self::InvocationStoreError> { Ok(self.store.get(&cid)) } - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError> { self.store.insert(cid, invocation); Ok(()) } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 11a859ab..e68a208f 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -43,17 +43,21 @@ pub enum ParentfulError { /// The `cmd` field was more powerful than the proof. /// /// i.e. it behaves like moving "down" the delegation chain not "up" + #[error("The `cmd` field was more powerful than the proof")] CommandEscelation, /// The `args` field was more powerful than the proof. + #[error("The `args` field was more powerful than the proof: {0}")] ArgumentEscelation(ArgErr), /// The parents do not prove the ability. + #[error("The parents do not prove the ability: {0}")] InvalidProofChain(PrfErr), /// Comparing parents in a delegation chain failed. /// /// The specific comparison error is captured in the `ParErr`. + #[error("Comparing parents in a delegation chain failed: {0}")] InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } diff --git a/src/receipt.rs b/src/receipt.rs index e7b70ebb..cb43ce6b 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,24 +14,41 @@ pub use payload::Payload; pub use responds::Responds; pub use store::Store; -use crate::{ability, crypto::signature, did}; +use crate::{ + ability, + crypto::{signature, varsig}, + did, +}; +use libipld_core::codec::Codec; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt(pub signature::Envelope, DID>); +pub struct Receipt< + T: Responds, + DID: did::Did, + V: varsig::Header, + C: Codec + Into + TryFrom, +>(pub signature::Envelope, DID, V, C>); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt; - -impl Receipt { +pub type Preset = Receipt< + ability::preset::Ready, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +impl, C: Codec + Into + TryFrom> + Receipt +{ /// Returns the [`Payload`] of the [`Receipt`]. pub fn payload(&self) -> &Payload { &self.0.payload } /// Returns the [`signature::Envelope`] of the [`Receipt`]. - pub fn signature(&self) -> &signature::Witness { + pub fn signature(&self) -> &DID::Signature { &self.0.signature } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 41ccea13..ddf6582d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -244,9 +244,11 @@ where } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where T::Success: Arbitrary + 'static, + DID::Parameters: Clone, + DID::Strategy: 'static, { type Parameters = (::Parameters, DID::Parameters); type Strategy = BoxedStrategy; @@ -261,7 +263,7 @@ where ], prop::collection::vec(cid::Newtype::arbitrary(), 0..25), prop::collection::vec(cid::Newtype::arbitrary(), 0..25), - prop::collection::hash_map(".*", ipld::Newtype::arbitrary(), 0..50), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50), Nonce::arbitrary(), prop::option::of(Timestamp::arbitrary()), ) diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs index c3bd3ad4..11e84731 100644 --- a/src/receipt/store/memory.rs +++ b/src/receipt/store/memory.rs @@ -1,32 +1,38 @@ use super::Store; use crate::{ + crypto::varsig, did::Did, receipt::{Receipt, Responds}, task, }; -use libipld_core::ipld::Ipld; +use libipld_core::{codec::Codec, ipld::Ipld}; use std::{collections::BTreeMap, convert::Infallible, fmt}; /// An in-memory [`receipt::Store`][crate::receipt::Store]. #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore -where +pub struct MemoryStore< + T: Responds, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, +> where T::Success: fmt::Debug + Clone + PartialEq, { - store: BTreeMap>, + store: BTreeMap>, } -impl Store for MemoryStore +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, { type Error = Infallible; - fn get(&self, id: &task::Id) -> Result>, Self::Error> { + fn get(&self, id: &task::Id) -> Result>, Self::Error> { Ok(self.store.get(id)) } - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { self.store.insert(id, receipt); Ok(()) } diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs index c2bed62b..40a141d4 100644 --- a/src/receipt/store/traits.rs +++ b/src/receipt/store/traits.rs @@ -1,12 +1,13 @@ use crate::{ + crypto::varsig, did::Did, receipt::{Receipt, Responds}, task, }; -use libipld_core::ipld::Ipld; +use libipld_core::{codec::Codec, ipld::Ipld}; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { +pub trait Store, C: Codec + Into + TryFrom> { /// The error type representing all the ways a store operation can fail. type Error; @@ -14,12 +15,12 @@ pub trait Store { /// /// If the store itself did not experience an error, but the value /// was not found, the result will be `Ok(None)`. - fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } From 76f0fc9baf159f7de969b5baf905c6cfa8f199c5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 20 Feb 2024 16:31:43 -0800 Subject: [PATCH 159/234] Expose CID helpers --- src/ability/crud.rs | 11 ++++++ src/ability/crud/any.rs | 18 +++++++++- src/ability/crud/create.rs | 22 ++++++++++++ src/ability/crud/destroy.rs | 18 ++++++++++ src/ability/crud/mutate.rs | 21 ++++++++++- src/ability/crud/parents.rs | 9 +++++ src/ability/crud/read.rs | 22 ++++++++++++ src/ability/crud/update.rs | 22 ++++++++++++ src/ability/msg.rs | 12 +++++-- src/ability/msg/any.rs | 14 +++++++- src/ability/msg/receive.rs | 14 +++++++- src/ability/preset.rs | 19 ++++++++++ src/crypto/signature/envelope.rs | 39 +++++++++++++++++++++ src/delegation.rs | 14 ++++++++ src/invocation.rs | 12 +++++++ src/invocation/agent.rs | 52 ++++++++++++++-------------- src/invocation/promise/resolvable.rs | 3 +- src/proof/checkable.rs | 4 ++- src/proof/parentful.rs | 14 ++++++++ src/proof/parentless.rs | 12 ++++++- 20 files changed, 317 insertions(+), 35 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 98c8804e..1c4a03f9 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -122,6 +122,17 @@ impl CheckParents for Builder { } } +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Create(create) => create.into(), + Builder::Read(read) => read.into(), + Builder::Update(update) => update.into(), + Builder::Destroy(destroy) => destroy.into(), + } + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 077d1d70..775a58f7 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,7 +1,7 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -94,3 +94,19 @@ impl From for Ipld { builder.into() } } + +impl From for arguments::Named { + fn from(any: Any) -> arguments::Named { + let mut named = arguments::Named::new(); + if let Some(path) = any.path { + named.insert( + "path".into(), + path.into_os_string() + .into_string() + .expect("PathBuf should generate a valid path") + .into(), + ); + } + named + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index e62494ac..85a77250 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -312,3 +312,25 @@ impl promise::Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f47200e0..95f29319 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -256,3 +256,21 @@ impl From for Ready { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + named + } +} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index ab8c85a3..241fa1f2 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,7 +1,7 @@ //! The delegation superclass for all mutable CRUD actions. use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{ checkable::Checkable, error::OptionalFieldError, parentful::Parentful, parents::CheckParents, same::CheckSame, @@ -113,3 +113,22 @@ impl CheckParents for Mutate { Ok(()) } } + +impl From for arguments::Named { + fn from(mutate: Mutate) -> Self { + let mut args = arguments::Named::new(); + + if let Some(path) = mutate.path { + args.insert( + "path".into(), + Ipld::String( + path.into_os_string() + .into_string() + .expect("PathBuf should generate a valid path"), + ), + ); + } + + args + } +} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 4dd92d24..8d3be766 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -127,3 +127,12 @@ impl CheckSame for MutableParents { } } } + +impl From for arguments::Named { + fn from(parents: MutableParents) -> Self { + match parents { + MutableParents::Any(any) => any.into(), + MutableParents::Mutate(mutate) => mutate.into(), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 99ee9022..c1be22fd 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -285,3 +285,25 @@ impl promise::Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf should make a valid path") + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 0dc4f992..0ed14da3 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -335,3 +335,25 @@ impl From for Builder { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 1f56e40b..d3099de8 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,12 +1,11 @@ //! Message abilities mod any; -mod receive; +pub mod receive; pub mod send; pub use any::Any; -pub use receive::Receive; use crate::{ ability::arguments, @@ -119,3 +118,12 @@ impl CheckParents for Builder { impl Checkable for Builder { type Hierarchy = Parentful; } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Send(send) => send.into(), + Builder::Receive(receive) => receive.into(), + } + } +} diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 48f8eeab..8290420b 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,7 +1,7 @@ //! "Any" message ability (superclass of all message abilities) use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -73,3 +73,15 @@ impl CheckSame for Any { Ok(()) } } + +impl From for arguments::Named { + fn from(any: Any) -> arguments::Named { + let mut args = arguments::Named::new(); + + if let Some(from) = any.from { + args.insert("from".into(), from.to_string().into()); + } + + args + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 36381e04..840a5329 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -46,7 +46,7 @@ pub struct Receive { pub from: Option, } -pub(super) type Builder = Receive; +pub type Builder = Receive; // FIXME needs promisory version @@ -149,3 +149,15 @@ impl promise::Resolvable for Receive { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut args = arguments::Named::new(); + + if let Some(from) = builder.from { + args.insert("from".into(), from.into()); + } + + args + } +} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 50822bb7..d9ccbc4e 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -155,3 +155,22 @@ impl From for Builder { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Crud(builder) => builder.into(), + Builder::Msg(builder) => builder.into(), + Builder::Wasm(builder) => builder.into(), + } + } +} + +impl From for arguments::Named { + fn from(parents: Parents) -> Self { + match parents { + Parents::Crud(parents) => parents.into(), + Parents::Msg(parents) => parents.into(), + } + } +} diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 2b1237f5..a7b27c87 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -4,9 +4,11 @@ use crate::{ did::{Did, Verifiable}, }; use libipld_core::{ + cid::{Cid, CidGeneric}, codec::{Codec, Encode}, error::Result, ipld::Ipld, + multihash::{Code, MultihashDigest}, }; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; @@ -155,6 +157,23 @@ impl< .verify(&encoded, &self.signature) .map_err(ValidateError::VerifyError) } + + pub fn cid(&self) -> Result + where + Self: Clone, + Ipld: Encode, + T: Into, + { + let codec = self.varsig_header.codec().clone(); + let mut ipld_buffer = vec![]; + self.encode(codec, &mut ipld_buffer)?; + + let multihash = Code::Sha2_256.digest(&ipld_buffer); + Ok(CidGeneric::new_v1( + self.varsig_header.codec().clone().into(), + multihash, + )) + } } impl< @@ -175,6 +194,26 @@ impl< } } +impl< + T: Verifiable + Capsule + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > Encode for Envelope +where + Self: Clone, + Ipld: Encode, +{ + fn encode( + &self, + codec: Enc, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + let ipld: Ipld = self.clone().into(); + ipld.encode(codec, w) + } +} + /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] pub enum SignError { diff --git a/src/delegation.rs b/src/delegation.rs index 05c5033d..91d8790d 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,8 +32,10 @@ use crate::{ }; use condition::Condition; use libipld_core::{ + cid::{Cid, CidGeneric}, codec::{Codec, Encode}, ipld::Ipld, + multihash::{Code, MultihashDigest}, }; use std::collections::BTreeMap; use web_time::SystemTime; @@ -139,6 +141,18 @@ impl, Enc: Codec + Into + &self.0.signature } + pub fn codec(&self) -> &Enc { + self.varsig_header().codec() + } + + pub fn cid(&self) -> Result + where + signature::Envelope, DID, V, Enc>: Clone + Encode, + Ipld: Encode, + { + self.0.cid() + } + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, diff --git a/src/invocation.rs b/src/invocation.rs index 4b6a4c60..1e75b4a2 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -133,6 +133,18 @@ where self.payload().check_time(now) } + pub fn codec(&self) -> &Enc { + self.varsig_header().codec() + } + + pub fn cid(&self) -> Result + where + signature::Envelope, DID, V, Enc>: Clone + Encode, + Ipld: Encode, + { + self.0.cid() + } + pub fn try_sign( signer: &DID::Signer, varsig_header: V, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index b7dd7a79..1a285e34 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,7 +1,7 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, - crypto::{varsig, Nonce}, + crypto::{signature as ucan_signature, varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, @@ -14,8 +14,9 @@ use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashGeneric}, + multihash::{Code, MultihashDigest}, }; +use signature; use std::{collections::BTreeMap, fmt, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -26,7 +27,7 @@ pub struct Agent< T: Resolvable + Delegable, C: Condition, DID: Did, - S: Store, + S: Store, P: promise::Store, D: delegation::store::Store, V: varsig::Header, @@ -36,8 +37,7 @@ pub struct Agent< pub delegation_store: &'a mut D, pub invocation_store: &'a mut S, - pub unresolved_promise_store: &'a mut P, - pub resolved_promise_store: &'a mut P, + pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, marker: PhantomData<(T, C, V, Enc)>, @@ -48,7 +48,7 @@ impl< T: Resolvable + Delegable + Clone, C: Condition, DID: Did + Clone, - S: Store, + S: Store, P: promise::Store, D: delegation::store::Store, V: varsig::Header, @@ -64,15 +64,13 @@ where signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - unresolved_promise_store: &'a mut P, - resolved_promise_store: &'a mut P, + unresolved_promise_index: &'a mut P, ) -> Self { Self { did, invocation_store, delegation_store, - unresolved_promise_store, - resolved_promise_store, + unresolved_promise_index, signer, marker: PhantomData, } @@ -122,23 +120,29 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where + T::Builder: Into> + Clone, + Ipld: Encode, C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, - T::Builder: Clone + Checkable + Prove + Into>, Invocation: Clone, <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, + ucan_signature::Envelope, DID, V, Enc>: Clone, + ucan_signature::Envelope, DID, V, Enc>: Clone, { + // FIXME You know... store it + // also: Envelops hsould have a cid() method + // self.invocation_store + // .put(promised.cid().clone(), promised.clone()) + // .map_err(ReceiveError::PromiseStoreError)?; + let mut buffer = vec![]; Ipld::from(promised.clone()) .encode(*promised.varsig_header().codec(), &mut buffer) .map_err(ReceiveError::EncodingError)?; - let cid: Cid = CidGeneric::new_v1( - DagCborCodec.into(), - MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) - .map_err(ReceiveError::MultihashError)?, - ); + let multihash = Code::Sha2_256.digest(buffer.as_slice()); + let cid: Cid = CidGeneric::new_v1(DagCborCodec.into(), multihash); let mut encoded = vec![]; Ipld::from(promised.payload().clone()) @@ -153,19 +157,13 @@ where let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { - // FIXME check if any of the unresolved promises are in the store - // FIXME check if it's actually unresolved - - // self.invocation_store - // .put(cid.clone(), promised.clone()) - // .map_err(ReceiveError::PromiseStoreError)?; + let waiting_on_cid = todo!(); - self.unresolved_promise_store - .put(cid, todo!()) // cid for promised) + self.unresolved_promise_index + .put(promised.cid()?, vec![waiting_on_cid]) .map_err(ReceiveError::PromiseStoreError)?; - todo!() - // return Ok(Recipient::Other(promised)); // FIXME + return Ok(Recipient::Unresolved(cid)); } }; @@ -242,8 +240,10 @@ where #[derive(Debug)] pub enum Recipient { + // FIXME change to status You(T), Other(T), + Unresolved(Cid), } #[derive(Debug, Error)] diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index d039585e..ee0d111b 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,5 +1,5 @@ use crate::{ability::arguments, delegation::Delegable}; -use libipld_core::ipld::Ipld; +use libipld_core::{cid::Cid, ipld::Ipld}; // FIXME rename "Unresolved" // FIXME better name @@ -18,5 +18,6 @@ pub trait Resolvable: Delegable { type Promised: Into + Into>; /// Attempt to resolve the [`Self::Promised`]. + // FIXME bubble up what we're waiting on fn try_resolve(promised: Self::Promised) -> Result; fn try_resolve(promised: Self::Promised) -> Result; } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 5098c314..3e6d2031 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,6 +1,8 @@ //! Define the hierarchy of an ability (or mark as not having one) use super::{prove::Prove, same::CheckSame}; +use crate::ability::arguments; +use libipld_core::ipld::Ipld; // FIXME move to Delegatbel? @@ -11,5 +13,5 @@ pub trait Checkable: CheckSame + Sized { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From + PartialEq; + type Hierarchy: CheckSame + Prove + From + PartialEq + Into>; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index e68a208f..a6515cc8 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -6,6 +6,7 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; @@ -61,6 +62,19 @@ pub enum ParentfulError { InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } +impl From> for arguments::Named +where + arguments::Named: From + From, +{ + fn from(parentful: Parentful) -> Self { + match parentful { + Parentful::Any => arguments::Named::new(), + Parentful::Parents(parents) => parents.into(), + Parentful::This(this) => this.into(), + } + } +} + impl From> for Ipld where Ipld: From, diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 645246db..5dafb16a 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -6,6 +6,7 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; +use crate::ability::arguments; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -30,6 +31,15 @@ impl From for Parentless { } } +impl>> From> for arguments::Named { + fn from(parentless: Parentless) -> Self { + match parentless { + Parentless::Any => todo!(), + Parentless::This(this) => this.into(), + } + } +} + // FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? /// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] @@ -49,7 +59,7 @@ pub enum ParentlessError { /// This behaves as an alias for `Checkable::>`. pub trait NoParents {} -impl Checkable for T { +impl>> Checkable for T { type Hierarchy = Parentless; } From 66efbdaa05b82653875559983f7c15bab57d5832 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 21 Feb 2024 13:47:15 -0800 Subject: [PATCH 160/234] Massively simplify instanced for abilities --- src/ability/arguments/named.rs | 10 + src/ability/command.rs | 32 +-- src/ability/crud.rs | 131 ++++++++--- src/ability/crud/any.rs | 47 +++- src/ability/crud/create.rs | 275 ++++++++++------------ src/ability/crud/destroy.rs | 127 ++++------ src/ability/crud/mutate.rs | 16 ++ src/ability/crud/parents.rs | 43 ++-- src/ability/crud/read.rs | 294 +++++++++++------------ src/ability/crud/update.rs | 100 +++++--- src/ability/dynamic.rs | 11 +- src/ability/msg.rs | 108 +++++++-- src/ability/msg/receive.rs | 66 ++++-- src/ability/msg/send.rs | 158 ++++++++----- src/ability/preset.rs | 105 +++++---- src/ability/ucan/revoke.rs | 51 +++- src/ability/wasm/module.rs | 7 + src/ability/wasm/run.rs | 173 +++++++++----- src/delegation/delegable.rs | 43 +++- src/delegation/payload.rs | 2 +- src/invocation/agent.rs | 8 +- src/invocation/payload.rs | 2 +- src/invocation/promise.rs | 7 +- src/invocation/promise/pending.rs | 9 + src/invocation/promise/resolvable.rs | 96 +++++++- src/invocation/promise/resolves.rs | 11 +- src/ipld.rs | 6 +- src/ipld/promised.rs | 338 +++++++++++++++++++++++---- src/proof/error.rs | 2 +- src/proof/util.rs | 26 +-- src/reader/builder.rs | 9 +- src/reader/generic.rs | 10 +- 32 files changed, 1540 insertions(+), 783 deletions(-) create mode 100644 src/invocation/promise/pending.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 5ccf97c1..6ea813ef 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -81,6 +81,10 @@ impl Named { self.0.is_empty() } + pub fn values(&self) -> impl Iterator { + self.0.values() + } + pub fn contains(&self, other: &Named) -> Result<(), NamedError> where T: PartialEq, @@ -146,6 +150,12 @@ impl> TryFrom for Named { } } +// impl From> for Named { +// fn from(map: BTreeMap) -> Self { +// Named(map) +// } +// } + impl> From> for Ipld { fn from(arguments: Named) -> Self { Ipld::Map( diff --git a/src/ability/command.rs b/src/ability/command.rs index 1ab0d979..b9c5d0ab 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -56,42 +56,46 @@ pub trait Command { } // FIXME definitely needs a better name +// pub trait ParseAbility: TryFrom> { pub trait ParseAbility: Sized { - type Error: fmt::Debug; + type ArgsErr: fmt::Debug; - // FIXME rename this trait to Ability? - fn try_parse(cmd: &str, args: &arguments::Named) -> Result; + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>; } #[derive(Debug, Clone, Error)] pub enum ParseAbilityError { - #[error("Unknown command")] - UnknownCommand, + #[error("Unknown command: {0}")] + UnknownCommand(String), #[error(transparent)] InvalidArgs(#[from] E), } -impl> ParseAbility for T +impl>> ParseAbility for T where - >::Error: fmt::Debug, + >>::Error: fmt::Debug, { - type Error = ParseAbilityError<>::Error>; + type ArgsErr = >>::Error; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>>::Error>> { if cmd != T::COMMAND { - return Err(ParseAbilityError::UnknownCommand); + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); } - Ipld::Map(args.0.clone()) - .try_into() - .map_err(ParseAbilityError::InvalidArgs) + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) } } // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] -// FIXME ^^^^ NOT ANYMORE +// FIXME ^^^^ NOT ANYMORE? // Either that needs to be re-locked down, or (because it's all abstract anyways) // just note that you probably don;t want this one. pub trait ToCommand { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 1c4a03f9..bddccbca 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -52,9 +52,13 @@ pub use mutate::Mutate; pub use parents::*; use crate::{ - ability::arguments, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -72,10 +76,10 @@ pub enum Ready { #[derive(Debug, Clone, PartialEq)] pub enum Builder { - Create(create::Builder), - Read(read::Builder), + Create(create::Ready), + Read(read::Ready), Update(update::Builder), - Destroy(destroy::Builder), + Destroy(destroy::Ready), } #[derive(Debug, Clone, PartialEq)] @@ -90,17 +94,82 @@ impl Delegable for Ready { type Builder = Builder; } +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match create::Ready::try_parse(cmd, args.clone()) { + Ok(create) => return Ok(Builder::Create(create)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match read::Ready::try_parse(cmd, args.clone()) { + Ok(read) => return Ok(Builder::Read(read)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match update::Builder::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Builder::Update(update)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match destroy::Ready::try_parse(cmd, args) { + Ok(destroy) => return Ok(Builder::Destroy(destroy)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.into())) + } +} + impl Checkable for Builder { type Hierarchy = Parentful; } -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Create(create) => Builder::Create(create.into()), - Promised::Read(read) => Builder::Read(read.into()), - Promised::Update(update) => Builder::Update(update.into()), - Promised::Destroy(destroy) => Builder::Destroy(destroy.into()), +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Create(create) => create.to_command(), + Ready::Read(read) => read.to_command(), + Ready::Update(update) => update.to_command(), + Ready::Destroy(destroy) => destroy.to_command(), + } + } +} + +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Create(create) => create.to_command(), + Promised::Read(read) => read.to_command(), + Promised::Update(update) => update.to_command(), + Promised::Destroy(destroy) => destroy.to_command(), + } + } +} + +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Create(create) => create.to_command(), + Builder::Read(read) => read.to_command(), + Builder::Update(update) => update.to_command(), + Builder::Destroy(destroy) => destroy.to_command(), } } } @@ -133,16 +202,16 @@ impl From for arguments::Named { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Create(create) => create.into(), - Promised::Read(read) => read.into(), - Promised::Update(update) => update.into(), - Promised::Destroy(destroy) => destroy.into(), - } - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// match promised { +// Promised::Create(create) => create.into(), +// Promised::Read(read) => read.into(), +// Promised::Update(update) => update.into(), +// Promised::Destroy(destroy) => destroy.into(), +// } +// } +// } impl From for Builder { fn from(ready: Ready) -> Self { @@ -184,21 +253,15 @@ impl CheckSame for Builder { impl Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Promised) -> Result { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { match promised { - Promised::Create(create) => Resolvable::try_resolve(create) - .map(Ready::Create) - .map_err(Promised::Create), - Promised::Read(read) => Resolvable::try_resolve(read) - .map(Ready::Read) - .map_err(Promised::Read), - Promised::Update(update) => Resolvable::try_resolve(update) - .map(Ready::Update) - .map_err(Promised::Update), - Promised::Destroy(destroy) => Resolvable::try_resolve(destroy) - .map(Ready::Destroy) - .map_err(Promised::Destroy), + Promised::Create(create) => create.into(), + Promised::Read(read) => read.into(), + Promised::Update(update) => update.into(), + Promised::Destroy(destroy) => destroy.into(), } } } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 775a58f7..f5bb2aab 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,7 +1,10 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility, ParseAbilityError}, + }, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -64,14 +67,56 @@ impl Command for Any { const COMMAND: &'static str = "crud/*"; } +impl TryFrom> for Any { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + + for (key, value) in arguments.iter() { + match key.as_str() { + "path" => { + let some_path = match value { + Ipld::String(s) => Ok(PathBuf::from(s)), + _ => Err(()), + }?; + + path = Some(some_path); + } + _ => return Err(()), + } + } + + Ok(Any { path }) + } +} + +// FIXME pipe example + impl NoParents for Any {} +// impl ParseAbility for Any { +// type ArgsErr = (); +// +// fn try_parse( +// cmd: &str, +// args: arguments::Named, +// ) -> Result> { +// if cmd != Self::COMMAND { +// return Err(ParseAbilityError::CommandMismatch); +// } +// +// Self::try_from(args).map_err(|_| ParseAbilityError::Args(Self::COMMAND)) +// } +// } + impl CheckSame for Any { type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 85a77250..1247cacd 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,9 +7,9 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::ipld::Ipld; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::Serialize; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -57,40 +57,17 @@ pub struct Generic { /// /// style createready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for creating other agents. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/create` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// create("crud/create") -/// end -/// end -/// end -/// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") -/// -/// top --> any --> mutate --> create -/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} -/// -/// style create stroke:orange; -/// ``` -pub type Builder = Generic>; + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/create` ability (but possibly awaiting another @@ -124,53 +101,90 @@ pub type Builder = Generic>; /// /// style createpromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, -impl Command for Generic { - const COMMAND: &'static str = "crud/create"; + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>>, } -impl, A: Into> From> for Ipld { - fn from(create: Generic) -> Self { - create.into() - } +const COMMAND: &str = "crud/create"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; } -impl, A: TryFrom> TryFrom for Generic { - type Error = (); // FIXME +impl Command for Promised { + const COMMAND: &'static str = COMMAND; +} - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 2 { - return Err(()); // FIXME +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(mut map) = ipld { +// if map.len() > 2 { +// return Err(()); // FIXME +// } +// +// Ok(Generic { +// path: map +// .remove("path") +// .map(|ipld| P::try_from(ipld).map_err(|_| ())) +// .transpose()?, +// +// args: map +// .remove("args") +// .map(|ipld| A::try_from(ipld).map_err(|_| ())) +// .transpose()?, +// }) +// } else { +// Err(()) // FIXME +// } +// } +// } + +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, ipld) in arguments { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + args = Some(ipld.try_into().map_err(|_| ())?); + } + _ => return Err(()), } - - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - - args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME } + + Ok(Ready { path, args }) } } impl Delegable for Ready { - type Builder = Builder; + type Builder = Ready; } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -182,7 +196,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = MutableParents; type ParentError = (); // FIXME @@ -212,45 +226,21 @@ impl CheckParents for Builder { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - // FIXME gross code - if let Some(args_res) = promised.args { - match args_res.try_resolve() { - Ok(named_promises) => { - let value = named_promises.iter().try_fold( - arguments::Named::::new(), - |mut acc, (k, v)| { - // FIXME extract - acc.insert(k.into(), v.clone().try_into().ok()?); - Some(acc) - }, - ); - - match value { - Some(v) => { - named.insert("args".to_string(), v.into()); - } - None => {} - } - } - - Err(_unresolved) => {} - } - } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path) = promised.path { +// named.insert("path".to_string(), Ipld::String(path.to_string())); +// } +// +// if let Some(args) = promised.args { +// named.insert("args".to_string(), Ipld::from(args)); +// } +// +// named +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { @@ -265,56 +255,23 @@ impl From for Promised { } // FIXME may want to name this something other than a TryFrom -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|x| x.try_resolve().ok()), - args: promised - .args - .and_then(|x| x.try_resolve().ok()?.try_into().ok()), - } - } -} +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.and_then(|x| x.try_resolve().ok()), +// args: promised +// .args +// .and_then(|x| x.try_resolve().ok()?.try_into().ok()), +// } +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(builder: Ready) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { @@ -334,3 +291,19 @@ impl From for arguments::Named { named } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); + } + + if let Some(args) = promised.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 95f29319..83a6fb4a 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -9,7 +9,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::Serialize; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -53,40 +53,13 @@ pub struct Generic { /// /// style destroyready stroke:orange; /// ``` -pub type Ready = Generic; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for destroying resources. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/destroy` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// destroy("crud/destroy") -/// end -/// end -/// end -/// -/// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") -/// -/// top --> any --> mutate --> destroy -/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} -/// -/// style destroy stroke:orange; -/// ``` -pub type Builder = Generic; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be destroyed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/destroy` ability (but possibly awaiting another @@ -120,48 +93,54 @@ pub type Builder = Generic; /// /// style destroypromise stroke:orange; /// ``` -pub type Promised = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be destroyed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, +} -impl

Command for Generic

{ - const COMMAND: &'static str = "crud/destroy"; +const COMMAND: &'static str = "crud/destroy"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Builder; +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } -impl> From> for Ipld { - fn from(destroy: Generic

) -> Self { - destroy.into() - } +impl Delegable for Ready { + type Builder = Ready; } -impl> TryFrom for Generic

{ +impl TryFrom> for Ready { type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 1 { - return Err(()); // FIXME - } + fn try_from(args: arguments::Named) -> Result { + let mut path = None; - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME + for (k, ipld) in args { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } + } + _ => return Err(()), + } } + + Ok(Ready { path }) } } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -173,7 +152,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = MutableParents; type ParentError = (); // FIXME @@ -228,21 +207,17 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - })), - }, - None => None, +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); } - .transpose()?; - Ok(Ready { path }) + named } } @@ -257,8 +232,8 @@ impl From for Ready { } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(builder: Ready) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 241fa1f2..4900e00c 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -79,6 +79,20 @@ impl TryFrom for Mutate { } } +impl TryFrom> for Mutate { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + if let Some(Ipld::String(s)) = args.get("path") { + return Ok(Mutate { + path: Some(PathBuf::from(s)), + }); + }; + + Ok(Mutate { path: None }) + } +} + impl Checkable for Mutate { type Hierarchy = Parentful; } @@ -89,6 +103,7 @@ impl CheckSame for Mutate { fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } @@ -105,6 +120,7 @@ impl CheckParents for Mutate { fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 8d3be766..ac4f130d 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -78,28 +78,41 @@ impl ToCommand for MutableParents { #[derive(Debug, Clone, Error)] pub enum ParseError { - #[error("Invalid `crud/*` arguments: {0}")] - InvalidAnyArgs(#[source] ::Error), + #[error("Invalid `crud/*` arguments: {0:?}")] + InvalidAnyArgs(>>::Error), - #[error("Invalid `crud/mutate` arguments: {0}")] - InvalidMutateArgs(#[source] ::Error), + #[error("Invalid `crud/mutate` arguments: {0:?}")] + InvalidMutateArgs(>>::Error), } impl ParseAbility for MutableParents { - type Error = ParseAbilityError; + type ArgsErr = ParseError; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - super::Any::try_parse(cmd, args) - .map(MutableParents::Any) - .map_err(ParseError::InvalidAnyArgs) - .map_err(ParseAbilityError::InvalidArgs)?; + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match super::Any::try_parse(cmd, args.clone()) { + Ok(any) => return Ok(MutableParents::Any(any)), + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(ParseError::InvalidAnyArgs( + e, + ))) + } + Err(ParseAbilityError::UnknownCommand(_)) => {} + } - super::Mutate::try_parse(cmd, args) - .map(MutableParents::Mutate) - .map_err(ParseError::InvalidMutateArgs) - .map_err(ParseAbilityError::InvalidArgs)?; + match super::Any::try_parse(cmd, args.clone()) { + Ok(any) => return Ok(MutableParents::Any(any)), + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs( + ParseError::InvalidMutateArgs(e), + )) + } + Err(ParseAbilityError::UnknownCommand(_)) => {} + } - Err(ParseAbilityError::UnknownCommand) + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index c1be22fd..76ee8019 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -2,31 +2,21 @@ use super::any as crud; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility, ParseAbilityError}, + }, delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::ipld::Ipld; -use serde::Serialize; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; // FIXME deserialize instance -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be read. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments to modify the read request. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. /// @@ -54,38 +44,17 @@ pub struct Generic { /// /// style readready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for reading resources. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/read` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// subgraph Invokable -/// read("crud/read") -/// end -/// end -/// end -/// -/// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") -/// -/// top --> any --> read -/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} -/// -/// style read stroke:orange; -/// ``` -pub type Builder = Generic>; + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/read` ability (but possibly awaiting another @@ -117,64 +86,96 @@ pub type Builder = Generic>; /// /// style readpromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, -impl Command for Generic { + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>>, +} + +const COMMAND: &'static str = "crud/read"; + +impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl Delegable for Ready { - type Builder = Builder; +impl Command for Promised { + const COMMAND: &'static str = "crud/read"; } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|p| p.try_resolve().ok()), - args: promised.args.and_then(|p| p.try_resolve_option()), // FIXME this needs to read better - } - } +impl Delegable for Ready { + type Builder = Ready; } // FIXME resolves vs resolvable is confusing -impl, A: Into> From> for Ipld { - fn from(read: Generic) -> Self { - read.into() +impl TryFrom for Ready { + type Error = SerdeError; // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } -impl, A: TryFrom> TryFrom for Generic { - type Error = (); // FIXME +impl From for arguments::Named { + fn from(ready: Ready) -> Self { + let mut named = arguments::Named::new(); - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 2 { - return Err(()); // FIXME - } + if let Some(path) = ready.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf should make a valid path") + .into(), + ); + } - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - - args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME + if let Some(args) = ready.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} + +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, v) in arguments.into_iter() { + match k.as_str() { + "path" => { + if let Ipld::String(string) = v { + path = Some(PathBuf::from(string)); + } else { + return Err(()); + } + } + "args" => { + args = Some(arguments::Named::try_from(v).map_err(|_| ())?); + } + _ => return Err(()), + } } + + Ok(Ready { path, args }) } } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -186,7 +187,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = crud::Any; type ParentError = (); // FIXME @@ -204,38 +205,21 @@ impl CheckParents for Builder { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - // FIXME - // if let Some(args_res) = promised.args { - // let v = args_res.map(|a| { - // // FIXME extract - // a.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { - // acc.insert(*k, (*v).try_into().ok()?); - // Some(acc) - // }) - // }); - - // // match v { - // // - // // } - // // named.insert( - // // "args".to_string(), - // // ); - // } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path_res) = promised.path { +// named.insert("path".into(), path_res.into()); +// } +// +// if let Some(args_res) = promised.args { +// named.insert("args".into(), args_res.into()); +// } +// +// named +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { @@ -251,59 +235,45 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf should make a valid path") - .into(), - ); + if let Some(path_res) = promised.path { + named.insert("path".to_string(), path_res.into()); } - if let Some(args) = builder.args { - named.insert("args".to_string(), args.into()); + if let Some(args_res) = promised.args { + named.insert("args".to_string(), args_res.into()); } named } } + +// impl TryFrom> for Promised { +// type Error = (); +// +// fn try_from(arguments: arguments::Named) -> Result { +// let mut path = None; +// let mut args = None; +// +// for (k, v) in arguments.into_iter() { +// match k.as_str() { +// "path" => { +// let path = promise::Resolves::try_from(v)?; +// path.map(|path| Promised { path, args }); +// } +// "args" => { +// let args = promise::Resolves::try_from(v)?; +// args.map(|args| Promised { path, args }); +// } +// _ => return Err(()), +// } +// } +// +// Ok(Promised { path, args }) +// } +// } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 0ed14da3..dd2225da 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -143,14 +143,63 @@ pub struct Promised { args: Option, } +const COMMAND: &'static str = "crud/update"; + impl Command for Ready { - const COMMAND: &'static str = "crud/update"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { type Builder = Builder; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + Self::try_from_named(named).map_err(|_| ()) + } +} + +impl TryFrom> for Builder { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::Map(map) = ipld { + args = Some(arguments::Named(map)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Builder { path, args }) + } +} + impl From for Builder { fn from(r: Ready) -> Self { Builder { @@ -292,39 +341,6 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } impl From for Builder { @@ -357,3 +373,19 @@ impl From for arguments::Named { named } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); + } + + if let Some(args) = promised.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index d28b4610..d95be19f 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -2,7 +2,7 @@ use super::{ arguments, - command::{ParseAbility, ToCommand}, + command::{ParseAbility, ParseAbilityError, ToCommand}, }; use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -45,12 +45,15 @@ pub struct Dynamic { } impl ParseAbility for Dynamic { - type Error = Infallible; + type ArgsErr = (); - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { Ok(Dynamic { cmd: cmd.to_string(), - args: args.clone(), + args, }) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index d3099de8..3968c0cf 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -8,9 +8,13 @@ pub mod send; pub use any::Any; use crate::{ - ability::arguments, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -38,6 +42,79 @@ impl Delegable for Ready { type Builder = Builder; } +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Send(send) => send.to_command(), + Ready::Receive(receive) => receive.to_command(), + } + } +} + +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Send(send) => send.to_command(), + Builder::Receive(receive) => receive.to_command(), + } + } +} + +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Send(send) => send.to_command(), + Promised::Receive(receive) => receive.to_command(), + } + } +} + +// impl ParseAbility for Ready { +// type ArgsErr = (); +// +// fn try_parse( +// cmd: &str, +// args: arguments::Named, +// ) -> Result> { +// match send::Ready::try_parse(cmd, args.clone()) { +// Ok(send) => return Ok(Ready::Send(send)), +// Err(ParseAbilityError::InvalidArgs(args)) => { +// return Err(ParseAbilityError::InvalidArgs(())) +// } +// Err(ParseAbilityError::UnknownCommand(_)) => {} +// } +// +// match receive::Receive::try_parse(cmd, args) { +// Ok(receive) => return Ok(Ready::Receive(receive)), +// Err(ParseAbilityError::InvalidArgs(args)) => { +// return Err(ParseAbilityError::InvalidArgs(())) +// } +// Err(ParseAbilityError::UnknownCommand(cmd)) => {} +// } +// +// Err(ParseAbilityError::UnknownCommand(cmd.to_string())) +// } +// } + +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Builder::try_parse(cmd, args.clone()) { + return Ok(Builder::Send(send)); + } + + if let Ok(receive) = receive::Receive::try_parse(cmd, args) { + return Ok(Builder::Receive(receive)); + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + impl TryFrom for Ready { type Error = (); @@ -69,26 +146,6 @@ impl From for arguments::Named { impl Resolvable for Ready { type Promised = Promised; - - fn try_resolve(promised: Promised) -> Result { - match promised { - Promised::Send(send) => Resolvable::try_resolve(send) - .map(Ready::Send) - .map_err(Promised::Send), - Promised::Receive(receive) => Resolvable::try_resolve(receive) - .map(Ready::Receive) - .map_err(Promised::Receive), - } - } -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Send(send) => Builder::Send(send.into()), - Promised::Receive(receive) => Builder::Receive(receive.into()), - } - } } impl CheckSame for Builder { @@ -127,3 +184,12 @@ impl From for arguments::Named { } } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), + } + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 840a5329..b0304b0b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; @@ -46,23 +47,50 @@ pub struct Receive { pub from: Option, } -pub type Builder = Receive; - // FIXME needs promisory version +const COMMAND: &'static str = "msg/send"; + impl Command for Receive { - const COMMAND: &'static str = "msg/send"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Receive { type Builder = Receive; } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - from: promised.from.and_then(|x| x.try_resolve_option()), +impl TryFrom> for Receive { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut from = None; + + for (key, ipld) in arguments { + match key.as_str() { + "from" => { + from = Some(url::Newtype::try_from(ipld).map_err(|_| ())?); + } + _ => return Err(()), + } + } + + Ok(Receive { from }) + } +} + +impl From for arguments::Named { + fn from(receive: Receive) -> Self { + let mut args = arguments::Named::new(); + + if let Some(from) = receive.from { + args.insert("from".into(), from.into()); } + + args } } @@ -134,28 +162,16 @@ impl From for arguments::Named { impl promise::Resolvable for Receive { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - match &p.from { - None => Ok(Receive { from: None }), - Some(promise::Resolves::Ok(promise_ok)) => match promise_ok.clone().try_resolve() { - Ok(from) => Ok(Receive { from }), - Err(_from) => Err(Promised { from: p.from }), - }, - Some(promise::Resolves::Err(promise_err)) => match promise_err.clone().try_resolve() { - Ok(from) => Ok(Receive { from }), - Err(_from) => Err(Promised { from: p.from }), - }, - } - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); - if let Some(from) = builder.from { - args.insert("from".into(), from.into()); + if let Some(from) = promised.from { + let _ = ipld::Promised::from(from).with_resolved(|ipld| { + args.insert("from".into(), ipld.into()); + }); } args diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 96d3299b..fd1ec63b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, }; @@ -12,28 +13,6 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -/// Helper for creating instances of `msg/send` with the correct shape. -/// -/// This is not generally used directly, unless you want to abstract -/// over all of the `msg/send` variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// The recipient of the message - pub to: To, - - /// FIXME Builder needs to omit option fields from Serde - - /// The sender address of the message - /// - /// This *may* be a URL (such as an email address). - /// If provided, the `subject` must have the right to send from this address. - pub from: From, - - /// The main body of the message - pub message: Message, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. /// @@ -61,7 +40,21 @@ pub struct Generic { /// /// style sendrun stroke:orange; /// ``` -pub type Ready = Generic; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// The recipient of the message + pub to: Url, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: Url, + + /// The main body of the message + pub message: String, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable variant of the `msg/send` ability. @@ -89,7 +82,21 @@ pub type Ready = Generic; /// /// style send stroke:orange; /// ``` -pub type Builder = Generic, Option, Option>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + /// The recipient of the message + pub to: Option, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: Option, + + /// The main body of the message + pub message: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability @@ -120,8 +127,21 @@ pub type Builder = Generic, Option, Option>; /// /// style sendpromise stroke:orange; /// ``` -pub type Promised = - Generic, promise::Resolves, promise::Resolves>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// The recipient of the message + pub to: promise::Resolves, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: promise::Resolves, + + /// The main body of the message + pub message: promise::Resolves, +} impl Delegable for Ready { type Builder = Builder; @@ -129,13 +149,6 @@ impl Delegable for Ready { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Ready { to, from, message }), - Err((to, from, message)) => Err(Promised { to, from, message }), - } - } } impl From for arguments::Named { @@ -171,8 +184,18 @@ impl From for Builder { } } -impl Command for Generic { - const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "msg/send"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Checkable for Builder { @@ -198,9 +221,49 @@ impl CheckParents for Builder { } } +impl TryFrom> for Builder { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, ipld) in args.0 { + match key.as_str() { + "to" => { + // FIXME extract this common pattern + if let Ipld::String(s) = ipld { + to = Some(Url::parse(s.as_str()).map_err(|_| ())?); + } else { + return Err(()); + } + } + "from" => { + if let Ipld::String(s) = ipld { + from = Some(Url::parse(s.as_str()).map_err(|_| ())?); + } else { + return Err(()); + } + } + "message" => { + if let Ipld::String(s) = ipld { + message = Some(s); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Builder { to, from, message }) + } +} + impl From for Builder { fn from(resolved: Ready) -> Self { - Generic { + Builder { to: resolved.to.into(), from: resolved.from.into(), message: resolved.message.into(), @@ -247,21 +310,12 @@ impl TryFrom for Ready { } } -impl From> for Ipld -where - Ipld: From + From + From, -{ - fn from(send: Generic) -> Self { - send.into() - } -} - -impl TryFrom - for Generic -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) +impl From for arguments::Named { + fn from(p: Promised) -> Self { + arguments::Named::from_iter([ + ("to".into(), p.to.into()), + ("from".into(), p.from.into()), + ("message".into(), p.message.into()), + ]) } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index d9ccbc4e..c9efe787 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,8 +1,12 @@ use super::{crud, msg, wasm}; use crate::{ - ability::{arguments, command::ParseAbility}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -42,24 +46,28 @@ impl CheckSame for Parents { } } -impl ParseAbility for Parents { - type Error = String; // FIXME - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - if let Ok(crud) = crud::MutableParents::try_parse(cmd, args) { - return Ok(Parents::Crud(crud)); - } +impl Delegable for Ready { + type Builder = Builder; +} - if let Ok(msg) = msg::Any::try_parse(cmd, args) { - return Ok(Parents::Msg(msg)); +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Crud(ready) => ready.to_command(), + Ready::Msg(ready) => ready.to_command(), + Ready::Wasm(ready) => ready.to_command(), } - - Err("Nope".into()) } } -impl Delegable for Ready { - type Builder = Builder; +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Crud(builder) => builder.to_command(), + Builder::Msg(builder) => builder.to_command(), + Builder::Wasm(builder) => builder.to_command(), + } + } } impl CheckSame for Builder { @@ -118,41 +126,46 @@ pub enum Promised { Wasm(wasm::run::Promised), } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Crud(promised) => promised.into(), - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), - } - } -} - impl Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Self::Promised) -> Result { - match promised { - Promised::Crud(promised) => Resolvable::try_resolve(promised) - .map(Ready::Crud) - .map_err(Promised::Crud), - Promised::Msg(promised) => Resolvable::try_resolve(promised) - .map(Ready::Msg) - .map_err(Promised::Msg), - Promised::Wasm(promised) => Resolvable::try_resolve(promised) - .map(Ready::Wasm) - .map_err(Promised::Wasm), +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Crud(promised) => promised.to_command(), + Promised::Msg(promised) => promised.to_command(), + Promised::Wasm(promised) => promised.to_command(), } } } -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Crud(promised) => Builder::Crud(promised.into()), - Promised::Msg(promised) => Builder::Msg(promised.into()), - Promised::Wasm(promised) => Builder::Wasm(promised.into()), +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match msg::Builder::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Builder::Msg(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), } + + match crud::Builder::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Builder::Crud(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match wasm::run::Builder::try_parse(cmd, args) { + Ok(builder) => return Ok(Builder::Wasm(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } @@ -174,3 +187,13 @@ impl From for arguments::Named { } } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Crud(promised) => promised.into(), + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index a0d61502..1b078cea 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -6,11 +6,15 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, +}; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -19,14 +23,48 @@ pub struct Ready { pub ucan: Cid, } +const COMMAND: &'static str = "ucan/revoke"; + impl Command for Ready { - const COMMAND: &'static str = "ucan/revoke"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { type Builder = Builder; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let ipld: Ipld = arguments.get("ucan").ok_or(())?.clone(); + let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; + + Ok(Ready { ucan: nt.cid }) + } +} + +impl TryFrom> for Builder { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + if let Some(ipld) = arguments.get("ucan") { + let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; + Ok(Builder { ucan: Some(nt.cid) }) + } else { + Ok(Builder { ucan: None }) + } + } +} + impl From for Builder { fn from(promised: Promised) -> Self { Builder { @@ -37,12 +75,11 @@ impl From for Builder { impl promise::Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Self::Promised) -> Result { - match promised.ucan.try_resolve() { - Ok(ucan) => Ok(Ready { ucan }), - Err(ucan) => Err(Promised { ucan }), - } +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) } } diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index 7b319b69..fc4b2ff7 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -1,5 +1,6 @@ //! Wasm module representations +use crate::{ability::arguments, ipld}; use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -73,3 +74,9 @@ impl<'de> Deserialize<'de> for Module { } } } + +impl From for ipld::Promised { + fn from(module: Module) -> Self { + module.into() + } +} diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index ecf4be6a..64acf9f4 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -2,70 +2,119 @@ use super::module::Module; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility}, + }, delegation::Delegable, invocation::promise, + ipld, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +const COMMAND: &'static str = "wasm/run"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; +} + /// The ability to run a Wasm module on the subject's machine #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { +pub struct Ready { /// The Wasm module to run - pub module: Mod, + pub module: Module, /// The function from the module to run - pub function: Fun, + pub function: String, /// Arguments to pass to the function - pub args: Args, + pub args: Vec, } -impl Command for Generic { - const COMMAND: &'static str = "wasm/run"; -} - -/// A variant with all of the required fields filled in -pub type Ready = Generic>; - impl Delegable for Ready { type Builder = Builder; } -impl promise::Resolvable for Ready { - type Promised = Promised; - - fn try_resolve(promised: Self::Promised) -> Result { - match promise::Resolves::try_resolve_3(promised.module, promised.function, promised.args) { - Ok((module, function, args)) => Ok(Ready { - module, - function, - args, - }), - Err((module, function, args)) => Err(Promised { - module, - function, - args, - }), +impl TryFrom> for Builder { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut module = None; + let mut function = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "mod" => { + module = Some(ipld.try_into().map_err(|_| ())?); + } + "fun" => { + if let Ipld::String(s) = ipld { + function = Some(s); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::List(list) = ipld { + args = Some(list); + } else { + return Err(()); + } + } + _ => return Err(()), + } } + + Ok(Builder { + module, + function, + args, + }) } } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - module: promised.module.try_resolve().ok(), - function: promised.function.try_resolve().ok(), - args: promised.args.try_resolve().ok(), - } - } +// impl TryFrom> for Builder { +// type Error = (); +// +// fn try_from(args: arguments::Named) -> Result { +// let ready = Ready::try_from(args)?; +// +// Ok(Builder { +// module: Some(ready.module), +// function: Some(ready.function), +// args: Some(ready.args), +// }) +// } +// } + +impl promise::Resolvable for Ready { + type Promised = Promised; } /// A variant meant for delegation, where fields may be omitted -pub type Builder = Generic, Option, Option>>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Builder { + /// The Wasm module to run + pub module: Option, + + /// The function from the module to run + pub function: Option, + + /// Arguments to pass to the function + pub args: Option>, +} impl NoParents for Builder {} @@ -137,32 +186,36 @@ impl CheckSame for Builder { } /// A variant meant for linking together invocations with promises -pub type Promised = - Generic, promise::Resolves, promise::Resolves>>; - -impl From for Promised { - fn from(ready: Ready) -> Self { - Promised { - module: promise::Resolves::from(Ok(ready.module)), - function: promise::Resolves::from(Ok(ready.function)), - args: promise::Resolves::from(Ok(ready.args)), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(promised: Promised) -> Result { - Ok(Ready { - module: promised.module.try_resolve().map_err(|_| ())?, - function: promised.function.try_resolve().map_err(|_| ())?, - args: promised.args.try_resolve().map_err(|_| ())?, - }) - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Promised { + pub module: promise::Resolves, + pub function: promise::Resolves, + pub args: promise::Resolves>, } -impl From for arguments::Named { +// impl From for Promised { +// fn from(ready: Ready) -> Self { +// Promised { +// module: promise::Resolves::from(Ok(ready.module)), +// function: promise::Resolves::from(Ok(ready.function)), +// args: promise::Resolves::from(Ok(ready.args)), +// } +// } +// } + +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(promised: Promised) -> Result { +// Ok(Ready { +// module: promised.module.try_from().map_err(|_| ())?, +// function: promised.function.try_from().map_err(|_| ())?, +// args: promised.args.try_from().map_err(|_| ())?, +// }) +// } +// } + +impl From for arguments::Named { fn from(promised: Promised) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 19806dd5..78ea8853 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,4 +1,11 @@ -use crate::proof::checkable::Checkable; +use crate::{ + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, + proof::checkable::Checkable, +}; +use libipld_core::ipld::Ipld; /// A trait for types that can be delegated. /// @@ -7,7 +14,39 @@ use crate::proof::checkable::Checkable; /// /// [`Delegation`]: crate::delegation::Delegation /// [`Invocation`]: crate::invocation::Invocation +// FIXME NOTE: don't need parse ability, because parse -> builder -> self +// FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. pub trait Delegable: Sized { /// A delegation with some arguments filled. - type Builder: TryInto + From + Checkable; + type Builder: TryInto + + From + + Checkable + + ParseAbility + + ToCommand + + Into>; + + fn into_command(self) -> String { + Self::Builder::from(self).to_command() + } + + fn into_named_args(self) -> arguments::Named { + Self::Builder::from(self).into() + } + + fn try_parse_to_ready( + command: &str, + named: arguments::Named, + ) -> Result::ArgsErr>> { + let builder = Self::Builder::try_parse(command, named)?; + builder.try_into().map_err(|err| todo!()) + } + + fn try_from_named( + named: arguments::Named, + ) -> Result::ArgsErr>> { + let builder = Self::Builder::try_parse("", named)?; + builder.try_into().map_err(|err| todo!()) + } } + +// FIXME ParseAbility + ToCommand + Checkable = Ability? diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 3ab8d1f2..4361382b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -323,7 +323,7 @@ impl< let args = arguments.ok_or(de::Error::missing_field("args"))?; let ability_builder = - ::try_parse(cmd.as_str(), &args).map_err(|e| { + ::try_parse(cmd.as_str(), args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {:?} because {:?}", cmd, e diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1a285e34..f4192fe9 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -91,7 +91,13 @@ where ) -> Result, ()> { let proofs = self .delegation_store - .get_chain(self.did, subject, &ability.clone().into(), vec![], now) + .get_chain( + self.did, + subject, + &::try_to_builder(ability.clone()).map_err(|_| ())?, + vec![], + now, + ) .map_err(|_| ())? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed3c2a04..3eca6d1a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -363,7 +363,7 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; let args = arguments.ok_or(de::Error::missing_field("args"))?; - let ability = ::try_parse(cmd.as_str(), &args).map_err(|e| { + let ability = ::try_parse(cmd.as_str(), args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {:?} becuase {:?}", cmd, e diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 592aac94..71a8b287 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,10 +1,9 @@ //! [UCAN Promise](https://github.com/ucan-wg/promise)s: selectors, wrappers, and traits. -// FIXME put entire module behind feature flag - mod any; mod err; mod ok; +mod pending; mod resolvable; mod resolves; @@ -14,14 +13,16 @@ pub mod store; pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; +pub use pending::Pending; pub use resolvable::Resolvable; pub use resolves::Resolves; pub use store::Store; +use enum_as_inner::EnumAsInner; use serde::{Deserialize, Serialize}; /// Top-level union of all UCAN Promise options -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] #[serde(untagged)] pub enum Promise { /// The `await/ok` promise diff --git a/src/invocation/promise/pending.rs b/src/invocation/promise/pending.rs new file mode 100644 index 00000000..8ac83db2 --- /dev/null +++ b/src/invocation/promise/pending.rs @@ -0,0 +1,9 @@ +use libipld_core::cid::Cid; + +// AKA Selector +#[derive(Debug, Clone, PartialEq)] +pub enum Pending { + Ok(Cid), + Err(Cid), + Any(Cid), +} diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index ee0d111b..32e1be35 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,5 +1,15 @@ -use crate::{ability::arguments, delegation::Delegable}; +use crate::{ + ability::{ + arguments, + command::{ParseAbility, ToCommand}, + }, + delegation::Delegable, + invocation::promise::Pending, + ipld, +}; use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeSet, fmt}; +use thiserror::Error; // FIXME rename "Unresolved" // FIXME better name @@ -15,9 +25,87 @@ pub trait Resolvable: Delegable { /// be a promise. /// /// [PromiseIpld]: crate::ipld::Promised - type Promised: Into + Into>; + // type Promised: Into + Into>; + type Promised: Into> + ToCommand; /// Attempt to resolve the [`Self::Promised`]. - // FIXME bubble up what we're waiting on fn try_resolve(promised: Self::Promised) -> Result; - fn try_resolve(promised: Self::Promised) -> Result; + fn try_resolve(promised: Self::Promised) -> Result> + where + Self::Promised: Clone, + { + let ipld_promise: arguments::Named = promised.clone().into(); + match arguments::Named::::try_from(ipld_promise) { + Err(_) => Err(CantResolve { + promised, + reason: todo!(), // ParseAbility::ArgsErr::ExpectedMap, + }), + Ok(named) => { + let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) + .map_err(|reason| CantResolve { + promised: promised.clone(), + reason: todo!(), + })?; + + builder.try_into().map_err(|_reason| CantResolve { + promised, + reason: todo!(), + }) + } + } + } + + fn get_all_pending(promised: Self::Promised) -> BTreeSet { + let promise_map: arguments::Named = promised.into(); + + promise_map + .values() + .fold(BTreeSet::new(), |mut set, promised| { + if let ipld::Promised::Link(cid) = promised { + set.insert(*cid); + } + + set + }) + } + + fn try_to_builder(promised: Self::Promised) -> Result { + let cmd = promised.to_command(); + let ipld_promise: arguments::Named = promised.into(); + + let named: arguments::Named = + ipld_promise + .into_iter() + .fold(arguments::Named::new(), |mut acc, (k, v)| { + match v.try_into() { + Err(_) => (), + Ok(ipld) => { + acc.insert(k, ipld); // i.e. forget any promises + } + } + + acc + }); + + Self::Builder::try_parse(&cmd, named).map_err(|_| ()) + } +} + +#[derive(Error)] +pub struct CantResolve { + pub promised: S::Promised, + pub reason: <::Builder as ParseAbility>::ArgsErr, +} + +impl fmt::Debug for CantResolve +where + S::Promised: fmt::Debug, + <::Builder as ParseAbility>::ArgsErr: fmt::Debug, + Pending: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CantResolve") + .field("promised", &self.promised) + .field("reason", &self.reason) + .finish() + } } diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index dcfc5651..3ee866d4 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -1,4 +1,4 @@ -use super::{PromiseAny, PromiseErr, PromiseOk}; +use super::{Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -277,6 +277,15 @@ impl From> for Resolves { } } +impl From> for Promise { + fn from(resolves: Resolves) -> Promise { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + impl TryFrom> for PromiseOk { type Error = PromiseErr; diff --git a/src/ipld.rs b/src/ipld.rs index 95aeca60..2c3a79db 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,14 +6,14 @@ //! //! [`Ipld`]: libipld_core::ipld::Ipld -mod enriched; +// mod enriched; mod newtype; mod number; mod promised; pub mod cid; -pub use enriched::Enriched; +// pub use enriched::Enriched; pub use newtype::Newtype; pub use number::Number; -pub use promised::Promised; +pub use promised::*; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index fce3cdac..8071181d 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,75 +1,325 @@ -use super::enriched::Enriched; -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::ipld::Ipld; +// use super::enriched::Enriched; +use crate::{ + ability::arguments, + invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, + url, +}; +use enum_as_inner::EnumAsInner; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; /// A recursive data structure whose leaves may be [`Ipld`] or promises. /// /// [`Promised`] resolves to regular [`Ipld`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Promised(pub Promise, Enriched>); +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +pub enum Promised { + // Resolved Leaves + Null, + Bool(bool), + Integer(i128), + Float(f64), + String(String), + Bytes(Vec), + Link(Cid), -impl From, Enriched>> for Promised { - fn from(promise: Promise, Enriched>) -> Self { - Promised(promise) + // Pending Leaves + WaitOk(Cid), + WaitErr(Cid), + WaitAny(Cid), + + // Recursive + List(Vec), + Map(BTreeMap), +} + +impl Promised { + pub fn with_resolved(self, f: F) -> Result + where + F: FnOnce(Ipld) -> T, + { + match self.try_into() { + Ok(ipld) => Ok(f(ipld)), + Err(pending) => Err(pending), + } + } + + pub fn with_pending(self, f: F) -> Result + where + F: FnOnce(Pending) -> E, + { + match self.try_into() { + Ok(ipld) => Err(ipld), + Err(promised) => Ok(f(promised)), + } } } -impl From, Enriched>> for Promised { - fn from(p_any: PromiseAny, Enriched>) -> Self { - Promised(p_any.into()) +impl From for Promised { + fn from(ipld: Ipld) -> Promised { + match ipld { + Ipld::Null => Promised::Null, + Ipld::Bool(b) => Promised::Bool(b), + Ipld::Integer(i) => Promised::Integer(i), + Ipld::Float(f) => Promised::Float(f), + Ipld::String(s) => Promised::String(s), + Ipld::Bytes(b) => Promised::Bytes(b), + Ipld::Link(cid) => Promised::Link(cid), + Ipld::List(list) => Promised::List(list.into_iter().map(Into::into).collect()), + Ipld::Map(map) => { + let mut promised_map = BTreeMap::new(); + for (k, v) in map { + promised_map.insert(k, v.into()); + } + Promised::Map(promised_map) + } + } } } -impl From>> for Promised { - fn from(p_ok: PromiseOk>) -> Self { - Promised(p_ok.into()) +impl TryFrom for Ipld { + type Error = Pending; + + fn try_from(promised: Promised) -> Result { + match promised { + Promised::Null => Ok(Ipld::Null), + Promised::Bool(b) => Ok(Ipld::Bool(b)), + Promised::Integer(i) => Ok(Ipld::Integer(i)), + Promised::Float(f) => Ok(Ipld::Float(f)), + Promised::String(s) => Ok(Ipld::String(s)), + Promised::Bytes(b) => Ok(Ipld::Bytes(b)), + Promised::Link(cid) => Ok(Ipld::Link(cid)), + Promised::List(list) => list + .into_iter() + .try_fold(Vec::new(), |mut acc, promised| { + acc.push(promised.try_into()?); + Ok(acc) + }) + .map(Ipld::List), + Promised::Map(map) => map + .into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, v.try_into()?); + Ok(acc) + }) + .map(Ipld::Map), + Promised::WaitOk(cid) => Err(Pending::Ok(cid).into()), + Promised::WaitErr(cid) => Err(Pending::Err(cid).into()), + Promised::WaitAny(cid) => Err(Pending::Any(cid).into()), + } } } -impl From>> for Promised { - fn from(p_err: PromiseErr>) -> Self { - Promised(p_err.into()) +impl From> for Promised { + fn from(p_ok: PromiseOk) -> Promised { + match p_ok { + PromiseOk::Fulfilled(ipld) => ipld.into(), + PromiseOk::Pending(cid) => Promised::WaitOk(cid), + } } } -impl From for Promised { - fn from(ipld: Ipld) -> Self { - Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) +impl From> for Promised { + fn from(p_err: PromiseErr) -> Promised { + match p_err { + PromiseErr::Rejected(ipld) => ipld.into(), + PromiseErr::Pending(cid) => Promised::WaitErr(cid), + } } } -impl TryFrom for Ipld { - type Error = Promised; +impl From> for Promised { + fn from(p_any: PromiseAny) -> Promised { + match p_any { + PromiseAny::Fulfilled(ipld) => ipld.into(), + PromiseAny::Rejected(ipld) => ipld.into(), + PromiseAny::Pending(cid) => Promised::WaitAny(cid), + } + } +} - fn try_from(p: Promised) -> Result { - match p.0 { - Promise::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(inner) => { - inner.try_into().map_err(|e| PromiseOk::Fulfilled(e).into()) - } +impl From> for Promised { + fn from(promise: Promise) -> Promised { + match promise { + Promise::Ok(p_ok) => p_ok.into(), + Promise::Err(p_err) => p_err.into(), + Promise::Any(p_any) => p_any.into(), + } + } +} - PromiseOk::Pending(inner) => Err(PromiseOk::Pending(inner).into()), +impl From> for Promised +where + Promised: From, +{ + fn from(r: Resolves) -> Promised { + match r { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(val) => val.into(), + PromiseOk::Pending(cid) => Promised::WaitOk(cid), }, - Promise::Err(p_err) => match p_err { - PromiseErr::Rejected(inner) => { - inner.try_into().map_err(|e| PromiseErr::Rejected(e).into()) - } - - PromiseErr::Pending(inner) => Err(PromiseErr::Pending(inner).into()), + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(val) => val.into(), + PromiseErr::Pending(cid) => Promised::WaitErr(cid), }, - Promise::Any(p_any) => match p_any { - PromiseAny::Fulfilled(inner) => inner - .try_into() - .map_err(|e| Promise::Any(PromiseAny::Fulfilled(e)).into()), + } + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(args: arguments::Named) -> Promised { + Promised::Map( + args.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} + +impl From for Promised { + fn from(path: PathBuf) -> Promised { + Promised::String(path.to_string_lossy().to_string()) + } +} + +impl From for Promised { + fn from(cid: Cid) -> Promised { + Promised::Link(cid) + } +} + +impl From<::url::Url> for Promised { + fn from(url: ::url::Url) -> Promised { + Promised::String(url.to_string()) + } +} - PromiseAny::Rejected(inner) => { - inner.try_into().map_err(|e| PromiseAny::Rejected(e).into()) +impl From for Promised { + fn from(nt: url::Newtype) -> Promised { + nt.0.into() + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(opt: Option) -> Promised { + match opt { + Some(val) => val.into(), + None => Promised::Null, + } + } +} + +impl From for Promised { + fn from(s: String) -> Promised { + Promised::String(s) + } +} + +impl From for Promised { + fn from(f: f64) -> Promised { + Promised::Float(f) + } +} + +impl From for Promised { + fn from(i: i128) -> Promised { + Promised::Integer(i) + } +} + +impl From for Promised { + fn from(b: bool) -> Promised { + Promised::Bool(b) + } +} + +impl From> for Promised { + fn from(b: Vec) -> Promised { + Promised::Bytes(b) + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(map: BTreeMap) -> Promised { + Promised::Map( + map.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} +impl From> for Promised +where + Promised: From, +{ + fn from(list: Vec) -> Promised { + Promised::List(list.into_iter().map(Into::into).collect()) + } +} + +/*************************** +| POST ORDER IPLD ITERATOR | +***************************/ + +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a> { + inbound: Vec<&'a Promised>, + outbound: Vec<&'a Promised>, +} + +// #[derive(Clone, Debug, PartialEq)] +// pub enum Item<'a> { +// Node(&'a Promised), +// Inner(&'a Cid), +// } + +impl<'a> PostOrderIpldIter<'a> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(promised: &'a Promised) -> Self { + PostOrderIpldIter { + inbound: vec![promised], + outbound: vec![], + } + } +} + +impl<'a> Iterator for PostOrderIpldIter<'a> { + type Item = &'a Promised; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(ref map @ Promised::Map(ref btree)) => { + self.outbound.push(map.clone()); + + for node in btree.values() { + self.inbound.push(node); + } } - PromiseAny::Pending(inner) => Err(PromiseAny::Pending(inner).into()), - }, + Some(ref list @ Promised::List(ref vector)) => { + self.outbound.push(list.clone()); + + for node in vector { + self.inbound.push(node); + } + } + Some(node) => self.outbound.push(node), + } } } } diff --git a/src/proof/error.rs b/src/proof/error.rs index c9bf339d..f2911c00 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -13,7 +13,7 @@ use wasm_bindgen::prelude::*; pub struct Unequal(); /// A generic error for when two fields are unequal. -#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub enum OptionalFieldError { /// A required field is missing. diff --git a/src/proof/util.rs b/src/proof/util.rs index 05926acd..e1046e34 100644 --- a/src/proof/util.rs +++ b/src/proof/util.rs @@ -1,15 +1,15 @@ use super::error::OptionalFieldError; -pub fn check_optional( - target: Option, - proof: Option, -) -> Result<(), OptionalFieldError> { - if let Some(target_value) = target { - let proof_value = proof.ok_or(OptionalFieldError::Missing)?; - if target_value != proof_value { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) -} +// pub fn check_optional( +// target: Option, +// proof: Option, +// ) -> Result<(), OptionalFieldError> { +// if let Some(target_value) = target { +// let proof_value = proof.ok_or(OptionalFieldError::Missing)?; +// if target_value != proof_value { +// return Err(OptionalFieldError::Unequal); +// } +// } +// +// Ok(()) +// } diff --git a/src/reader/builder.rs b/src/reader/builder.rs index d7298de7..e772e239 100644 --- a/src/reader/builder.rs +++ b/src/reader/builder.rs @@ -1,5 +1,5 @@ use super::Reader; -use crate::{ability::arguments, delegation::Delegable, proof::checkable::Checkable}; +use crate::ability::arguments; use serde::{Deserialize, Serialize}; /// A helper newtype that marks a value as being a [`Delegable::Builder`]. @@ -18,13 +18,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Builder(pub T); -impl Delegable for Reader -where - Reader>: Checkable, -{ - type Builder = Reader>; -} - impl>> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() diff --git a/src/reader/generic.rs b/src/reader/generic.rs index ae713d82..10cfa815 100644 --- a/src/reader/generic.rs +++ b/src/reader/generic.rs @@ -164,13 +164,15 @@ impl ToCommand for Reader { } } -impl ParseAbility for Reader { - type Error = ParseAbilityError<::Error>; +impl>> TryFrom> + for Reader +{ + type Error = ParseAbilityError<>>::Error>; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_from(args: arguments::Named) -> Result { Ok(Reader { env: Default::default(), - val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + val: T::try_from(args).map_err(ParseAbilityError::InvalidArgs)?, }) } } From 322b2c6cdf5dd32b93790a919bfaa7fd9a22b7ff Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 21 Feb 2024 21:58:32 -0800 Subject: [PATCH 161/234] Cleanup after simplifying promises --- src/ability.rs | 9 +- src/ability/arguments/named.rs | 20 +-- src/ability/command.rs | 43 ------- src/ability/crud.rs | 46 ++++++- src/ability/crud/any.rs | 20 +-- src/ability/crud/create.rs | 92 ++++++++------ src/ability/crud/destroy.rs | 38 +++++- src/ability/crud/parents.rs | 3 +- src/ability/crud/read.rs | 116 ++++++++--------- src/ability/crud/update.rs | 45 ++++++- src/ability/dynamic.rs | 5 +- src/ability/msg.rs | 22 +++- src/ability/msg/any.rs | 5 +- src/ability/msg/receive.rs | 29 ++++- src/ability/msg/send.rs | 73 +++++++++-- src/ability/parse.rs | 70 ++++++++++ src/ability/preset.rs | 32 ++++- src/ability/ucan/revoke.rs | 30 ++++- src/ability/wasm/module.rs | 10 +- src/ability/wasm/run.rs | 84 ++++++------ src/crypto/signature/envelope.rs | 8 +- src/delegation.rs | 5 +- src/delegation/agent.rs | 3 - src/delegation/condition/traits.rs | 3 +- src/delegation/delegable.rs | 5 +- src/delegation/payload.rs | 7 +- src/did/key/verifier.rs | 2 +- src/invocation.rs | 9 +- src/invocation/agent.rs | 183 ++++++++++++++++++--------- src/invocation/payload.rs | 5 +- src/invocation/promise.rs | 2 +- src/invocation/promise/pending.rs | 2 +- src/invocation/promise/resolvable.rs | 53 ++++++-- src/invocation/promise/resolves.rs | 12 +- src/ipld/newtype.rs | 11 ++ src/ipld/promised.rs | 101 ++++++++++++++- src/lib.rs | 12 +- src/proof.rs | 1 - src/proof/parentless.rs | 2 +- src/proof/util.rs | 15 --- src/reader/generic.rs | 5 +- src/url.rs | 26 ++++ 42 files changed, 878 insertions(+), 386 deletions(-) create mode 100644 src/ability/parse.rs delete mode 100644 src/proof/util.rs diff --git a/src/ability.rs b/src/ability.rs index 2c3cebee..1cc28455 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -33,10 +33,6 @@ //! field may include a promise pointing at another invocation. Once fully //! resolved ("ready"), they must be validatable against the delegation chain. -// FIXME feature flag each? -// FIXME ability implementers guide (e.g. serde deny fields) -// - pub mod ucan; #[cfg(feature = "ability-crud")] @@ -53,12 +49,9 @@ pub mod preset; pub mod arguments; pub mod command; +pub mod parse; #[cfg(target_arch = "wasm32")] pub mod js; -// // TODO move to crate::wasm? or hide behind "dynamic" feature flag? pub mod dynamic; - -// FIXME macro to derive promise versions & delagted builder versions -// ... also maybe Ipld diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 6ea813ef..0120da7b 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,4 +1,4 @@ -use crate::ipld; +use crate::{invocation::promise::Pending, ipld}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -293,20 +293,14 @@ impl From> for Named { } impl TryFrom> for Named { - type Error = Named; + type Error = Pending; fn try_from(named: Named) -> Result { - // FIXME lots of clone - // FIXME idea: what if they implemet a is_resoled, and then the try_from? - // This lets us check by ref, and then do the conversion and unwrap - named - .iter() - .try_fold(Named::new(), |mut acc, (ref k, v)| { - let ipld = v.clone().try_into().map_err(|_| ())?; - acc.insert(k.to_string(), ipld); - Ok(acc) - }) - .map_err(|()| named.clone()) + named.iter().try_fold(Named::new(), |mut acc, (ref k, v)| { + let ipld = v.clone().try_into()?; + acc.insert(k.to_string(), ipld); + Ok(acc) + }) } } diff --git a/src/ability/command.rs b/src/ability/command.rs index b9c5d0ab..5cfa1975 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -16,11 +16,6 @@ //! } //! ``` -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::fmt; -use thiserror::Error; - /// Attach a `cmd` field to a type /// /// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. @@ -55,44 +50,6 @@ pub trait Command { const COMMAND: &'static str; } -// FIXME definitely needs a better name -// pub trait ParseAbility: TryFrom> { -pub trait ParseAbility: Sized { - type ArgsErr: fmt::Debug; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result>; -} - -#[derive(Debug, Clone, Error)] -pub enum ParseAbilityError { - #[error("Unknown command: {0}")] - UnknownCommand(String), - - #[error(transparent)] - InvalidArgs(#[from] E), -} - -impl>> ParseAbility for T -where - >>::Error: fmt::Debug, -{ - type ArgsErr = >>::Error; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result>>::Error>> { - if cmd != T::COMMAND { - return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); - } - - Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) - } -} - // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] // FIXME ^^^^ NOT ANYMORE? diff --git a/src/ability/crud.rs b/src/ability/crud.rs index bddccbca..1e1ab5df 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -54,7 +54,8 @@ pub use parents::*; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -90,6 +91,49 @@ pub enum Promised { Destroy(destroy::Promised), } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match create::Promised::try_parse_promised(cmd, args.clone()) { + Ok(create) => return Ok(Promised::Create(create)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match read::Promised::try_parse_promised(cmd, args.clone()) { + Ok(read) => return Ok(Promised::Read(read)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match update::Promised::try_parse_promised(cmd, args.clone()) { + Ok(update) => return Ok(Promised::Update(update)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match destroy::Promised::try_parse_promised(cmd, args) { + Ok(destroy) => return Ok(Promised::Destroy(destroy)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.into())) + } +} + impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index f5bb2aab..6fcdd310 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,10 +1,7 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility, ParseAbilityError}, - }, + ability::{arguments, command::Command}, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -95,21 +92,6 @@ impl TryFrom> for Any { impl NoParents for Any {} -// impl ParseAbility for Any { -// type ArgsErr = (); -// -// fn try_parse( -// cmd: &str, -// args: arguments::Named, -// ) -> Result> { -// if cmd != Self::COMMAND { -// return Err(ParseAbilityError::CommandMismatch); -// } -// -// Self::try_from(args).map_err(|_| ParseAbilityError::Args(Self::COMMAND)) -// } -// } - impl CheckSame for Any { type Error = OptionalFieldError; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 1247cacd..ae8a5b64 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,9 +7,9 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use libipld_core::ipld::Ipld; use serde::Serialize; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; // FIXME deserialize instance @@ -123,31 +123,57 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(mut map) = ipld { -// if map.len() > 2 { -// return Err(()); // FIXME -// } -// -// Ok(Generic { -// path: map -// .remove("path") -// .map(|ipld| P::try_from(ipld).map_err(|_| ())) -// .transpose()?, -// -// args: map -// .remove("args") -// .map(|ipld| A::try_from(ipld).map_err(|_| ())) -// .transpose()?, -// }) -// } else { -// Err(()) // FIXME -// } -// } -// } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + + "args" => { + args = match prom { + ipld::Promised::Map(map) => { + Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) + } + ipld::Promised::WaitOk(cid) => { + Some(promise::PromiseOk::Pending(cid).into()) + } + ipld::Promised::WaitErr(cid) => { + Some(promise::PromiseErr::Pending(cid).into()) + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + } + _ => return Err(()), + } + } + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} impl TryFrom> for Ready { type Error = (); @@ -254,18 +280,6 @@ impl From for Promised { } } -// FIXME may want to name this something other than a TryFrom -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.and_then(|x| x.try_resolve().ok()), -// args: promised -// .args -// .and_then(|x| x.try_resolve().ok()?.try_into().ok()), -// } -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 83a6fb4a..a5d48571 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,4 +1,5 @@ //! Destroy a resource. + use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, @@ -9,9 +10,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::Serialize; -use std::{collections::BTreeMap, path::PathBuf}; - -// FIXME deserialize instance +use std::path::PathBuf; /// A helper for creating lifecycle instances of `crud/create` with the correct shape. #[derive(Debug, Clone, PartialEq, Serialize)] @@ -101,6 +100,39 @@ pub struct Promised { pub path: Option>, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { path }) + } +} + const COMMAND: &'static str = "crud/destroy"; impl Command for Ready { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index ac4f130d..18b7cceb 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -8,7 +8,8 @@ use super::error::ParentError; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }, proof::{parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 76ee8019..57001d8c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -2,12 +2,9 @@ use super::any as crud; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility, ParseAbilityError}, - }, + ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -98,14 +95,66 @@ pub struct Promised { pub args: Option>>, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + + "args" => { + args = match prom { + ipld::Promised::Map(map) => { + Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) + } + ipld::Promised::WaitOk(cid) => { + Some(promise::PromiseOk::Pending(cid).into()) + } + ipld::Promised::WaitErr(cid) => { + Some(promise::PromiseErr::Pending(cid).into()) + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + } + _ => return Err(()), + } + } + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} + const COMMAND: &'static str = "crud/read"; impl Command for Ready { - const COMMAND: &'static str = "crud/read"; + const COMMAND: &'static str = COMMAND; } impl Command for Promised { - const COMMAND: &'static str = "crud/read"; + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { @@ -205,34 +254,6 @@ impl CheckParents for Ready { } } -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// let mut named = arguments::Named::new(); -// -// if let Some(path_res) = promised.path { -// named.insert("path".into(), path_res.into()); -// } -// -// if let Some(args_res) = promised.args { -// named.insert("args".into(), args_res.into()); -// } -// -// named -// } -// } - -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), - - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), - } - } -} - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -252,28 +273,3 @@ impl From for arguments::Named { named } } - -// impl TryFrom> for Promised { -// type Error = (); -// -// fn try_from(arguments: arguments::Named) -> Result { -// let mut path = None; -// let mut args = None; -// -// for (k, v) in arguments.into_iter() { -// match k.as_str() { -// "path" => { -// let path = promise::Resolves::try_from(v)?; -// path.map(|path| Promised { path, args }); -// } -// "args" => { -// let args = promise::Resolves::try_from(v)?; -// args.map(|args| Promised { path, args }); -// } -// _ => return Err(()), -// } -// } -// -// Ok(Promised { path, args }) -// } -// } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dd2225da..305855e6 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -140,7 +140,7 @@ pub struct Promised { /// Optional arguments to be passed in the update. #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option, + args: Option>>, } const COMMAND: &'static str = "crud/update"; @@ -157,6 +157,49 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (key, prom) in named { + match key.as_str() { + "path" => match Ipld::try_from(prom) { + Err(pending) => { + path = Some(pending.into()); + } + Ok(ipld) => match ipld { + Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), + _ => return Err(()), + }, + }, + + "args" => match prom { + ipld::Promised::Map(map) => { + args = Some(promise::Resolves::new(arguments::Named(map))) + } + ipld::Promised::WaitOk(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + ipld::Promised::WaitErr(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + ipld::Promised::WaitAny(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + _ => return Err(()), + }, + + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} + impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index d95be19f..a665a7aa 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -2,12 +2,13 @@ use super::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }; use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{convert::Infallible, fmt::Debug}; +use std::fmt::Debug; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 3968c0cf..4fa01bed 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -10,7 +10,8 @@ pub use any::Any; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -69,6 +70,25 @@ impl ToCommand for Promised { } } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Promised::try_parse_promised(cmd, args.clone()) { + return Ok(Promised::Send(send)); + } + + if let Ok(receive) = receive::Promised::try_parse_promised(cmd, args) { + return Ok(Promised::Receive(receive)); + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + // impl ParseAbility for Ready { // type ArgsErr = (); // diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 8290420b..a20e3db9 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -3,10 +3,11 @@ use crate::{ ability::{arguments, command::Command}, proof::{parentless::NoParents, same::CheckSame}, + url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +// use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of @@ -44,7 +45,7 @@ use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { - pub from: Option, + pub from: Option, } impl Command for Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index b0304b0b..19373830 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -112,7 +112,7 @@ impl CheckParents for Receive { fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(from) = &self.from { if let Some(proof_from) = &proof.from { - if from != &url::Newtype(proof_from.clone()) { + if &from != &proof_from { return Err(()); } } @@ -138,7 +138,7 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq)] pub struct Promised { - pub from: Option>>, + pub from: Option>, } impl From for arguments::Named { @@ -164,6 +164,31 @@ impl promise::Resolvable for Receive { type Promised = Promised; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut from = None; + + for (key, prom) in arguments { + match key.as_str() { + "from" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + from = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => from = Some(pending.into()), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { from }) + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fd1ec63b..fc8a2f2c 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -6,12 +6,12 @@ use crate::{ invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, - url as url_newtype, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use url::Url; +// use url::Url; +use crate::url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -44,13 +44,13 @@ use url::Url; #[serde(deny_unknown_fields)] pub struct Ready { /// The recipient of the message - pub to: Url, + pub to: url::Newtype, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: Url, + pub from: url::Newtype, /// The main body of the message pub message: String, @@ -86,13 +86,13 @@ pub struct Ready { #[serde(deny_unknown_fields)] pub struct Builder { /// The recipient of the message - pub to: Option, + pub to: Option, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: Option, + pub from: Option, /// The main body of the message pub message: Option, @@ -131,13 +131,13 @@ pub struct Builder { #[serde(deny_unknown_fields)] pub struct Promised { /// The recipient of the message - pub to: promise::Resolves, + pub to: promise::Resolves, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: promise::Resolves, + pub from: promise::Resolves, /// The main body of the message pub message: promise::Resolves, @@ -151,6 +151,51 @@ impl promise::Resolvable for Ready { type Promised = Promised; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, prom) in args.0 { + match key.as_str() { + "to" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + to = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => to = Some(pending.into()), + _ => return Err(()), + }, + "from" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + from = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => from = Some(pending.into()), + _ => return Err(()), + }, + "message" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => message = Some(promise::Resolves::from(Ok(s))), + Err(pending) => to = Some(pending.into()), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { + to: to.ok_or(())?, + from: from.ok_or(())?, + message: message.ok_or(())?, + }) + } +} + impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); @@ -167,8 +212,8 @@ impl From for arguments::Named { impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ - ("to".into(), p.to.map(url_newtype::Newtype).into()), - ("from".into(), p.from.map(String::from).into()), + ("to".into(), p.to.into()), + ("from".into(), p.from.into()), ("message".into(), p.message.into()), ]) } @@ -234,14 +279,14 @@ impl TryFrom> for Builder { "to" => { // FIXME extract this common pattern if let Ipld::String(s) = ipld { - to = Some(Url::parse(s.as_str()).map_err(|_| ())?); + to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); } else { return Err(()); } } "from" => { if let Ipld::String(s) = ipld { - from = Some(Url::parse(s.as_str()).map_err(|_| ())?); + from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); } else { return Err(()); } diff --git a/src/ability/parse.rs b/src/ability/parse.rs new file mode 100644 index 00000000..c5212977 --- /dev/null +++ b/src/ability/parse.rs @@ -0,0 +1,70 @@ +use super::command::Command; +use crate::{ability::arguments, ipld}; +use libipld_core::ipld::Ipld; +use std::fmt; +use thiserror::Error; + +// FIXME definitely needs a better name +// pub trait ParseAbility: TryFrom> { +pub trait ParseAbility: Sized { + type ArgsErr: fmt::Debug; + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>; +} + +#[derive(Debug, Clone, Error)] +pub enum ParseAbilityError { + #[error("Unknown command: {0}")] + UnknownCommand(String), + + #[error(transparent)] + InvalidArgs(#[from] E), +} + +impl>> ParseAbility for T +where + >>::Error: fmt::Debug, +{ + type ArgsErr = >>::Error; + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>>::Error>> { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); + } + + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) + } +} + +pub trait ParsePromised: Sized { + type PromisedArgsError; + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result>; +} + +impl>> ParsePromised for T +where + >>::Error: fmt::Debug, +{ + type PromisedArgsError = >>::Error; + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); + } + + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) + } +} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index c9efe787..2443e26a 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -2,7 +2,8 @@ use super::{crud, msg, wasm}; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -140,6 +141,35 @@ impl ToCommand for Promised { } } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match crud::Promised::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(Promised::Crud(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match msg::Promised::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(Promised::Msg(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match wasm::run::Promised::try_parse_promised(cmd, args) { + Ok(promised) => return Ok(Promised::Wasm(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + impl ParseAbility for Builder { type ArgsErr = (); diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 1b078cea..fd2b29cf 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -11,10 +11,7 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, -}; +use std::{collections::BTreeMap, fmt::Debug}; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -133,6 +130,31 @@ pub struct Promised { pub ucan: promise::Resolves, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut ucan = None; + + for (k, prom) in arguments { + match k.as_str() { + "ucan" => match Ipld::try_from(prom) { + Ok(Ipld::Link(cid)) => { + ucan = Some(promise::Resolves::new(cid)); + } + Err(pending) => ucan = Some(pending.into()), + _ => return Err(()), + }, + _ => (), + } + } + + Ok(Promised { + ucan: ucan.ok_or(())?, + }) + } +} + impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index fc4b2ff7..18ed34db 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -1,6 +1,6 @@ //! Wasm module representations -use crate::{ability::arguments, ipld}; +use crate::ipld; use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -27,6 +27,14 @@ impl From for Ipld { } } +impl TryFrom for Module { + type Error = SerdeError; + + fn try_from(nt: ipld::Newtype) -> Result { + ipld_serde::from_ipld(nt.0) + } +} + impl TryFrom for Module { type Error = SerdeError; diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 64acf9f4..e9301a91 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -2,10 +2,7 @@ use super::module::Module; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, + ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, ipld, @@ -85,20 +82,6 @@ impl TryFrom> for Builder { } } -// impl TryFrom> for Builder { -// type Error = (); -// -// fn try_from(args: arguments::Named) -> Result { -// let ready = Ready::try_from(args)?; -// -// Ok(Builder { -// module: Some(ready.module), -// function: Some(ready.function), -// args: Some(ready.args), -// }) -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -193,27 +176,50 @@ pub struct Promised { pub args: promise::Resolves>, } -// impl From for Promised { -// fn from(ready: Ready) -> Self { -// Promised { -// module: promise::Resolves::from(Ok(ready.module)), -// function: promise::Resolves::from(Ok(ready.function)), -// args: promise::Resolves::from(Ok(ready.args)), -// } -// } -// } - -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(promised: Promised) -> Result { -// Ok(Ready { -// module: promised.module.try_from().map_err(|_| ())?, -// function: promised.function.try_from().map_err(|_| ())?, -// args: promised.args.try_from().map_err(|_| ())?, -// }) -// } -// } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut module = None; + let mut function = None; + let mut args = None; + + for (key, prom) in named { + match key.as_str() { + "module" => module = Some(prom.try_into().map_err(|_| ())?), + "function" => function = Some(prom.try_into().map_err(|_| ())?), + "args" => { + if let ipld::Promised::List(list) = prom.into() { + args = Some(promise::Resolves::new(list)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Promised { + module: module.ok_or(())?, + function: function.ok_or(())?, + args: args.ok_or(())?, + }) + } +} + +impl From for Promised { + fn from(ready: Ready) -> Self { + Promised { + module: promise::Resolves::from(Ok(ready.module)), + function: promise::Resolves::from(Ok(ready.function)), + args: promise::Resolves::from(Ok(ready + .args + .iter() + .map(|ipld| ipld.clone().into()) + .collect())), + } + } +} impl From for arguments::Named { fn from(promised: Promised) -> Self { diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index a7b27c87..22576fc0 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -4,7 +4,7 @@ use crate::{ did::{Did, Verifiable}, }; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, error::Result, ipld::Ipld, @@ -143,14 +143,14 @@ impl< /// # Exmaples /// /// FIXME - pub fn validate_signature(&self, varsig_header: &V) -> Result<(), ValidateError> + pub fn validate_signature(&self) -> Result<(), ValidateError> where T: Clone, Ipld: Encode, { let mut encoded = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(*varsig_header.codec(), &mut encoded) + ipld.encode(*self.varsig_header.codec(), &mut encoded) .map_err(ValidateError::PayloadEncodingError)?; self.verifier() @@ -169,7 +169,7 @@ impl< self.encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); - Ok(CidGeneric::new_v1( + Ok(Cid::new_v1( self.varsig_header.codec().clone().into(), multihash, )) diff --git a/src/delegation.rs b/src/delegation.rs index 91d8790d..5d81718e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,10 +32,9 @@ use crate::{ }; use condition::Condition; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashDigest}, }; use std::collections::BTreeMap; use web_time::SystemTime; @@ -158,7 +157,7 @@ impl, Enc: Codec + Into + Payload: Clone, Ipld: Encode, { - self.0.validate_signature(self.varsig_header()) + self.0.validate_signature() } pub fn try_sign( diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 160bc1bb..389de622 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -37,9 +37,6 @@ pub struct Agent< _marker: PhantomData<(B, C, V, Enc)>, } -// FIXME show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this - impl< 'a, B: Checkable + Clone, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 632d5615..1c7b8f90 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -2,9 +2,10 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; +use std::fmt; /// A trait for conditions that can be run on named IPLD arguments. -pub trait Condition { +pub trait Condition: fmt::Debug { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 78ea8853..a8929ab1 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,7 +1,8 @@ use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }, proof::checkable::Checkable, }; @@ -48,5 +49,3 @@ pub trait Delegable: Sized { builder.try_into().map_err(|err| todo!()) } } - -// FIXME ParseAbility + ToCommand + Checkable = Ability? diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4361382b..bd557979 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -2,7 +2,8 @@ use super::condition::Condition; use crate::{ ability::{ arguments, - command::{Command, ParseAbility, ToCommand}, + command::{Command, ToCommand}, + parse::ParseAbility, }, capsule::Capsule, crypto::Nonce, @@ -379,7 +380,7 @@ impl>, C: Condition, DID: Did> Payloa ) -> Result<(), ValidationError<::Error, C>> where T: Clone, - C: fmt::Debug + Clone, + C: Clone, DID: Clone, T::Hierarchy: Clone + Into>, { @@ -432,7 +433,7 @@ impl Acc { now: &SystemTime, ) -> Result::Error, C>> where - C: fmt::Debug + Clone, + C: Clone, H: Prove + Clone + Into>, { if self.issuer != proof.audience { diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 743cff94..d645c559 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -227,7 +227,7 @@ impl FromStr for Verifier { return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); } - _ => todo!(), + _ => return Err("invalid did:key".to_string()), }, ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { 48 => { diff --git a/src/invocation.rs b/src/invocation.rs index 1e75b4a2..2aa36b8e 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -139,7 +139,7 @@ where pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone, Ipld: Encode, { self.0.cid() @@ -156,6 +156,13 @@ where let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; Ok(Invocation(envelope)) } + + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> + where + Payload: Clone, + { + self.0.validate_signature() + } } impl, Enc: Codec + TryFrom + Into> diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index f4192fe9..9bc24aab 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,23 +1,27 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ - ability::{arguments, ucan}, - crypto::{signature as ucan_signature, varsig, Nonce}, + ability::{ + parse::{ParseAbility, ParseAbilityError, ParsePromised}, + ucan, + }, + crypto::{signature, varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, - did::{Did, Verifiable}, + did::Did, invocation::promise, proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; -use libipld_cbor::DagCborCodec; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashDigest}, }; -use signature; -use std::{collections::BTreeMap, fmt, marker::PhantomData}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, + marker::PhantomData, +}; use thiserror::Error; use web_time::SystemTime; @@ -43,21 +47,19 @@ pub struct Agent< marker: PhantomData<(T, C, V, Enc)>, } -impl< - 'a, - T: Resolvable + Delegable + Clone, - C: Condition, - DID: Did + Clone, - S: Store, - P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > Agent<'a, T, C, DID, S, P, D, V, Enc> +impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, delegation::Payload<::Hierarchy, C, DID>: Clone, + T: Resolvable + Delegable + Clone, + C: Condition, + DID: Did + Clone, + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -80,25 +82,65 @@ where &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? + ability: T, + metadata: BTreeMap, + cause: Option, + expiration: Option, + issued_at: Option, + now: SystemTime, + varsig_header: V, + ) -> Result< + Invocation, + InvokeError< + D::DelegationStoreError, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + >, + > + where + <::Promised as ParsePromised>::PromisedArgsError: fmt::Debug, + { + self.invoke_promise( + audience, + subject, + Resolvable::into_promised(ability), + metadata, + cause, + expiration, + issued_at, + now, + varsig_header, + ) + } + + pub fn invoke_promise( + &mut self, + audience: Option<&DID>, + subject: &DID, + ability: T::Promised, metadata: BTreeMap, cause: Option, expiration: Option, issued_at: Option, now: SystemTime, varsig_header: V, - // FIXME err type - ) -> Result, ()> { + ) -> Result< + Invocation, + InvokeError< + D::DelegationStoreError, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + >, + > { let proofs = self .delegation_store .get_chain( self.did, subject, - &::try_to_builder(ability.clone()).map_err(|_| ())?, + &::try_to_builder(ability.clone()) + .map_err(InvokeError::PromiseResolveError)?, vec![], now, ) - .map_err(|_| ())? + .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -117,56 +159,48 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?) + Ok(Invocation::try_sign(self.signer, varsig_header, payload) + .map_err(InvokeError::SignError)?) } pub fn receive( &mut self, promised: Invocation, now: &SystemTime, - ) -> Result>, ReceiveError> + ) -> Result< + Recipient>, + ReceiveError, + > where - T::Builder: Into> + Clone, - Ipld: Encode, - C: fmt::Debug + Clone, - ::Hierarchy: Clone + Into>, + Enc: From + Into, + T::Builder: Clone + Encode, + C: Clone, + ::Hierarchy: Clone, Invocation: Clone, <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, - ucan_signature::Envelope, DID, V, Enc>: Clone, - ucan_signature::Envelope, DID, V, Enc>: Clone, + signature::Envelope, DID, V, Enc>: Clone, + ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { - // FIXME You know... store it - // also: Envelops hsould have a cid() method - // self.invocation_store - // .put(promised.cid().clone(), promised.clone()) - // .map_err(ReceiveError::PromiseStoreError)?; - - let mut buffer = vec![]; - Ipld::from(promised.clone()) - .encode(*promised.varsig_header().codec(), &mut buffer) - .map_err(ReceiveError::EncodingError)?; - - let multihash = Code::Sha2_256.digest(buffer.as_slice()); - let cid: Cid = CidGeneric::new_v1(DagCborCodec.into(), multihash); - - let mut encoded = vec![]; - Ipld::from(promised.payload().clone()) - .encode(*promised.0.varsig_header.codec(), &mut encoded) - .map_err(ReceiveError::EncodingError)?; - - promised - .verifier() - .verify(&encoded, &promised.signature()) + let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; + let _ = promised + .validate_signature() .map_err(ReceiveError::SigVerifyError)?; + self.invocation_store + .put(cid.clone(), promised.clone()) + .map_err(ReceiveError::InvocationStoreError)?; + let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, - Err(_) => { - let waiting_on_cid = todo!(); + Err(cant_resolve) => { + let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); self.unresolved_promise_index - .put(promised.cid()?, vec![waiting_on_cid]) + .put( + promised.cid()?, + waiting_on.into_iter().collect::>(), + ) .map_err(ReceiveError::PromiseStoreError)?; return Ok(Recipient::Unresolved(cid)); @@ -253,22 +287,33 @@ pub enum Recipient { } #[derive(Debug, Error)] -pub enum ReceiveError, DID: Did, C: fmt::Debug, D> -where +pub enum ReceiveError< + T: Resolvable, + P: promise::Store, + DID: Did, + C: fmt::Debug, + D, + S: Store, + V: varsig::Header, + Enc: Codec + From + Into, +> where delegation::ValidationError< <<::Builder as Checkable>::Hierarchy as Prove>::Error, C, >: fmt::Debug,

>::PromiseStoreError: fmt::Debug, + ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), - #[error("multihash error: {0}")] - MultihashError(#[from] libipld_core::multihash::Error), - #[error("signature verification error: {0}")] - SigVerifyError(#[from] signature::Error), + SigVerifyError(#[from] signature::ValidateError), + + #[error("invocation store error: {0}")] + InvocationStoreError( + #[source] ::Promised, DID, V, Enc>>::InvocationStoreError, + ), #[error("promise store error: {0}")] PromiseStoreError(#[source]

>::PromiseStoreError), @@ -285,3 +330,15 @@ where >, ), } + +#[derive(Debug, Error)] +pub enum InvokeError { + #[error("delegation store error: {0}")] + DelegationStoreError(#[source] D), + + #[error("promise store error: {0}")] + SignError(#[source] signature::SignError), + + #[error("promise store error: {0}")] + PromiseResolveError(#[source] ArgsErr), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 3eca6d1a..fa0e9aea 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,9 +1,6 @@ use super::promise::Resolvable; use crate::{ - ability::{ - arguments, - command::{ParseAbility, ToCommand}, - }, + ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, delegation::{self, condition::Condition, Delegable, ValidationError}, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 71a8b287..4bc943a2 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -14,7 +14,7 @@ pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; pub use pending::Pending; -pub use resolvable::Resolvable; +pub use resolvable::*; pub use resolves::Resolves; pub use store::Store; diff --git a/src/invocation/promise/pending.rs b/src/invocation/promise/pending.rs index 8ac83db2..1c56e48c 100644 --- a/src/invocation/promise/pending.rs +++ b/src/invocation/promise/pending.rs @@ -1,7 +1,7 @@ use libipld_core::cid::Cid; // AKA Selector -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Pending { Ok(Cid), Err(Cid), diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index 32e1be35..17c9cd0d 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,7 +1,8 @@ use crate::{ ability::{ arguments, - command::{ParseAbility, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Pending, @@ -25,8 +26,22 @@ pub trait Resolvable: Delegable { /// be a promise. /// /// [PromiseIpld]: crate::ipld::Promised - // type Promised: Into + Into>; - type Promised: Into> + ToCommand; + type Promised: ToCommand + + ParsePromised // TryFrom> + + Into>; + + fn into_promised(self) -> Self::Promised + where + ::PromisedArgsError: fmt::Debug, + { + // FIXME In no way efficient... override where possible, or just cut the impl + let builder = Self::Builder::from(self); + let cmd = &builder.to_command(); + let named_ipld: arguments::Named = builder.into(); + let promised_ipld: arguments::Named = named_ipld.into(); + ::Promised::try_parse_promised(cmd, promised_ipld) + .expect("promise to always be possible from a ready ability") + } /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> @@ -35,20 +50,20 @@ pub trait Resolvable: Delegable { { let ipld_promise: arguments::Named = promised.clone().into(); match arguments::Named::::try_from(ipld_promise) { - Err(_) => Err(CantResolve { + Err(pending) => Err(CantResolve { promised, - reason: todo!(), // ParseAbility::ArgsErr::ExpectedMap, + reason: ResolveError::StillWaiting(pending), }), Ok(named) => { let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) - .map_err(|reason| CantResolve { + .map_err(|_reason| CantResolve { promised: promised.clone(), - reason: todo!(), + reason: ResolveError::ConversionError, })?; builder.try_into().map_err(|_reason| CantResolve { promised, - reason: todo!(), + reason: ResolveError::ConversionError, }) } } @@ -68,7 +83,12 @@ pub trait Resolvable: Delegable { }) } - fn try_to_builder(promised: Self::Promised) -> Result { + fn try_to_builder( + promised: Self::Promised, + ) -> Result< + Self::Builder, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + > { let cmd = promised.to_command(); let ipld_promise: arguments::Named = promised.into(); @@ -86,14 +106,14 @@ pub trait Resolvable: Delegable { acc }); - Self::Builder::try_parse(&cmd, named).map_err(|_| ()) + Self::Builder::try_parse(&cmd, named) } } -#[derive(Error)] +#[derive(Error, Clone)] pub struct CantResolve { pub promised: S::Promised, - pub reason: <::Builder as ParseAbility>::ArgsErr, + pub reason: ResolveError, } impl fmt::Debug for CantResolve @@ -109,3 +129,12 @@ where .finish() } } + +#[derive(Error, PartialEq, Eq, Clone, Debug)] +pub enum ResolveError { + #[error("The promise is still has arguments waiting to be resolved")] + StillWaiting(Pending), + + #[error("The resolved promise was unable to reify a Builder")] + ConversionError, +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 3ee866d4..1dbec8ca 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -1,4 +1,4 @@ -use super::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use super::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -228,6 +228,16 @@ impl Resolves { } } +impl From for Resolves { + fn from(pending: Pending) -> Self { + match pending { + Pending::Ok(cid) => Resolves::Ok(PromiseOk::Pending(cid)), + Pending::Err(cid) => Resolves::Err(PromiseErr::Pending(cid)), + Pending::Any(cid) => Resolves::Ok(PromiseOk::Pending(cid)), // FIXME + } + } +} + impl> TryFrom for Resolves { type Error = Ipld; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index fea821f4..88132b2b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -49,6 +49,17 @@ impl From for Newtype { } } +impl TryFrom for String { + type Error = (); + + fn try_from(nt: Newtype) -> Result { + match nt.0 { + Ipld::String(s) => Ok(s), + _ => Err(()), + } + } +} + impl From for Ipld { fn from(wrapped: Newtype) -> Self { wrapped.0 diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8071181d..589fe934 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -2,7 +2,7 @@ use crate::{ ability::arguments, invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, - url, + ipld, url, }; use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; @@ -67,11 +67,26 @@ impl From for Promised { Ipld::Link(cid) => Promised::Link(cid), Ipld::List(list) => Promised::List(list.into_iter().map(Into::into).collect()), Ipld::Map(map) => { - let mut promised_map = BTreeMap::new(); - for (k, v) in map { - promised_map.insert(k, v.into()); + if map.len() == 1 { + if let Some((k, Ipld::Link(cid))) = map.first_key_value() { + return match k.as_str() { + "await/ok" => Promised::WaitOk(*cid), + "await/err" => Promised::WaitErr(*cid), + "await/*" => Promised::WaitAny(*cid), + _ => Promised::Map(BTreeMap::from_iter([( + k.to_string(), + Promised::Link(*cid), + )])), + }; + } } - Promised::Map(promised_map) + + let map = map.into_iter().fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, v.into()); + acc + }); + + Promised::Map(map) } } } @@ -148,6 +163,66 @@ impl From> for Promised { } } +impl> TryFrom for Resolves { + type Error = (); + + fn try_from(promised: Promised) -> Result, Self::Error> { + match promised { + Promised::WaitOk(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), + Promised::WaitErr(cid) => Ok(Resolves::Err(PromiseErr::Pending(cid))), + Promised::WaitAny(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), // FIXME + + Promised::Null => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Null.into()).map_err(|_| ())?, + ))), + Promised::Bool(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Bool(b).into()).map_err(|_| ())?, + ))), + Promised::Integer(i) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Integer(i).into()).map_err(|_| ())?, + ))), + Promised::Float(f) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Float(f).into()).map_err(|_| ())?, + ))), + Promised::String(s) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::String(s).into()).map_err(|_| ())?, + ))), + Promised::Bytes(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Bytes(b).into()).map_err(|_| ())?, + ))), + Promised::Link(cid) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Link(cid).into()).map_err(|_| ())?, + ))), + + Promised::List(list) => { + let vec: Vec = list.into_iter().try_fold(vec![], |mut acc, promised| { + let ipld: Ipld = promised.try_into().map_err(|_| ())?; + acc.push(ipld); + Ok(acc) + })?; + + Ok(Resolves::Ok(PromiseOk::Fulfilled( + ipld::Newtype(Ipld::List(vec)).try_into().map_err(|_| ())?, + ))) + } + + Promised::Map(map) => { + let btree: BTreeMap = + map.into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + let ipld: Ipld = v.try_into().map_err(|_| ())?; + acc.insert(k, ipld); + Ok(acc) + })?; + + Ok(Resolves::Ok(PromiseOk::Fulfilled( + ipld::Newtype(Ipld::Map(btree)).try_into().map_err(|_| ())?, + ))) + } + } + } +} + impl From> for Promised where Promised: From, @@ -197,6 +272,18 @@ impl From<::url::Url> for Promised { } } +impl TryFrom for url::Newtype { + type Error = (); + + fn try_from(promised: Promised) -> Result { + match promised { + Promised::String(s) => Ok(url::Newtype(::url::Url::parse(&s).map_err(|_| ())?)), + // FIXME Promised::Link(cid) => Ok(url::Newtype::from(cid)), + _ => Err(()), + } + } +} + impl From for Promised { fn from(nt: url::Newtype) -> Promised { nt.0.into() @@ -304,7 +391,7 @@ impl<'a> Iterator for PostOrderIpldIter<'a> { match self.inbound.pop() { None => return self.outbound.pop(), Some(ref map @ Promised::Map(ref btree)) => { - self.outbound.push(map.clone()); + self.outbound.push(map); for node in btree.values() { self.inbound.push(node); @@ -312,7 +399,7 @@ impl<'a> Iterator for PostOrderIpldIter<'a> { } Some(ref list @ Promised::List(ref vector)) => { - self.outbound.push(list.clone()); + self.outbound.push(list); for node in vector { self.inbound.push(node); diff --git a/src/lib.rs b/src/lib.rs index bb7edaf4..694c91aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,10 @@ missing_debug_implementations, future_incompatible, let_underscore, - // FIXME missing_docs, + missing_docs, rust_2021_compatibility, - nonstandard_style, + nonstandard_style )] -// FIXME consider removing for Prove #![deny(unreachable_pub)] //! ucan @@ -35,3 +34,10 @@ pub mod test_utils; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; + +///////////// +// FIXME s // +///////////// + +// show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this diff --git a/src/proof.rs b/src/proof.rs index a80cd781..82092cb5 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -7,7 +7,6 @@ pub mod parentless; pub mod parents; pub mod prove; pub mod same; -pub mod util; // NOTE must remain *un*exported! pub(super) mod internal; diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 5dafb16a..c9b13264 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -34,7 +34,7 @@ impl From for Parentless { impl>> From> for arguments::Named { fn from(parentless: Parentless) -> Self { match parentless { - Parentless::Any => todo!(), + Parentless::Any => arguments::Named::new(), Parentless::This(this) => this.into(), } } diff --git a/src/proof/util.rs b/src/proof/util.rs deleted file mode 100644 index e1046e34..00000000 --- a/src/proof/util.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::error::OptionalFieldError; - -// pub fn check_optional( -// target: Option, -// proof: Option, -// ) -> Result<(), OptionalFieldError> { -// if let Some(target_value) = target { -// let proof_value = proof.ok_or(OptionalFieldError::Missing)?; -// if target_value != proof_value { -// return Err(OptionalFieldError::Unequal); -// } -// } -// -// Ok(()) -// } diff --git a/src/reader/generic.rs b/src/reader/generic.rs index 10cfa815..680ead6c 100644 --- a/src/reader/generic.rs +++ b/src/reader/generic.rs @@ -1,7 +1,4 @@ -use crate::ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, -}; +use crate::ability::{arguments, command::ToCommand, parse::ParseAbilityError}; use libipld_core::ipld::Ipld; /// A struct that attaches an ambient environment to a value. diff --git a/src/url.rs b/src/url.rs index 22c6915b..efa2f68f 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,7 +1,9 @@ //! URL utilities. +use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::fmt; use thiserror::Error; use url::Url; @@ -36,6 +38,30 @@ use proptest::prelude::*; #[serde(transparent)] pub struct Newtype(pub Url); +impl Newtype { + pub fn parse(s: &str) -> Result { + Ok(Newtype(Url::parse(s)?)) + } +} + +impl fmt::Display for Newtype { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl CheckSame for Newtype { + type Error = (); + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + if self == other { + Ok(()) + } else { + Err(()) + } + } +} + impl From for Ipld { fn from(newtype: Newtype) -> Self { newtype.into() From 8522b55a0fd1e95528593796f57a9d1c16ee483f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 23 Feb 2024 22:59:56 -0800 Subject: [PATCH 162/234] In theory, that's fully updated --- src/ability/arguments.rs | 32 +- src/ability/crud.rs | 198 ++++++------- src/ability/crud/create.rs | 102 +++---- src/ability/crud/destroy.rs | 102 +++---- src/ability/crud/read.rs | 78 ++--- src/ability/crud/update.rs | 129 ++++---- src/ability/dynamic.rs | 44 +-- src/ability/msg.rs | 141 +-------- src/ability/msg/receive.rs | 64 ++-- src/ability/msg/send.rs | 213 +++----------- src/ability/preset.rs | 143 ++------- src/ability/ucan/revoke.rs | 144 ++++----- src/ability/wasm/run.rs | 105 +------ src/delegation.rs | 106 +++---- src/delegation/agent.rs | 55 ++-- src/delegation/delegable.rs | 102 +++---- src/delegation/payload.rs | 421 ++------------------------- src/delegation/store/memory.rs | 64 ++-- src/delegation/store/traits.rs | 27 +- src/invocation.rs | 2 +- src/invocation/agent.rs | 110 ++++--- src/invocation/payload.rs | 117 +++++--- src/invocation/promise/resolvable.rs | 65 +---- src/lib.rs | 2 +- src/proof.rs | 16 +- src/url.rs | 24 +- 26 files changed, 883 insertions(+), 1723 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 0e96c6ef..6137a358 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -9,19 +9,19 @@ use libipld_core::ipld::Ipld; use std::collections::BTreeMap; // FIXME move under invoc::promise? -pub type Promised = Resolves>; - -impl Promised { - pub fn try_resolve_option(self) -> Option> { - match self.try_resolve() { - Err(_) => None, - Ok(named_promises) => named_promises - .iter() - .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); - Some(map) - }) - .map(Named), - } - } -} +// pub type Promised = Resolves>; +// +// impl Promised { +// pub fn try_resolve_option(self) -> Option> { +// match self.try_resolve() { +// Err(_) => None, +// Ok(named_promises) => named_promises +// .iter() +// .try_fold(BTreeMap::new(), |mut map, (k, v)| { +// map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); +// Some(map) +// }) +// .map(Named), +// } +// } +// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 1e1ab5df..57df163c 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -37,19 +37,19 @@ //! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete //! [`Did`]: crate::did::Did -mod any; -mod mutate; -mod parents; +// mod any; +// mod mutate; +// mod parents; pub mod create; pub mod destroy; -pub mod error; +// pub mod error; pub mod read; pub mod update; -pub use any::Any; -pub use mutate::Mutate; -pub use parents::*; +// pub use any::Any; +// pub use mutate::Mutate; +// pub use parents::*; use crate::{ ability::{ @@ -57,10 +57,8 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -75,14 +73,6 @@ pub enum Ready { Destroy(destroy::Ready), } -#[derive(Debug, Clone, PartialEq)] -pub enum Builder { - Create(create::Ready), - Read(read::Ready), - Update(update::Builder), - Destroy(destroy::Ready), -} - #[derive(Debug, Clone, PartialEq)] pub enum Promised { Create(create::Promised), @@ -134,11 +124,7 @@ impl ParsePromised for Promised { } } -impl Delegable for Ready { - type Builder = Builder; -} - -impl ParseAbility for Builder { +impl ParseAbility for Ready { type ArgsErr = (); fn try_parse( @@ -146,7 +132,7 @@ impl ParseAbility for Builder { args: arguments::Named, ) -> Result> { match create::Ready::try_parse(cmd, args.clone()) { - Ok(create) => return Ok(Builder::Create(create)), + Ok(create) => return Ok(Ready::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -154,15 +140,15 @@ impl ParseAbility for Builder { } match read::Ready::try_parse(cmd, args.clone()) { - Ok(read) => return Ok(Builder::Read(read)), + Ok(read) => return Ok(Ready::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Builder::try_parse(cmd, args.clone()) { - Ok(update) => return Ok(Builder::Update(update)), + match update::Ready::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Ready::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -170,7 +156,7 @@ impl ParseAbility for Builder { } match destroy::Ready::try_parse(cmd, args) { - Ok(destroy) => return Ok(Builder::Destroy(destroy)), + Ok(destroy) => return Ok(Ready::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -180,10 +166,10 @@ impl ParseAbility for Builder { Err(ParseAbilityError::UnknownCommand(cmd.into())) } } - -impl Checkable for Builder { - type Hierarchy = Parentful; -} +// +// impl Checkable for Builder { +// type Hierarchy = Parentful; +// } impl ToCommand for Ready { fn to_command(&self) -> String { @@ -207,44 +193,44 @@ impl ToCommand for Promised { } } -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Create(create) => create.to_command(), - Builder::Read(read) => read.to_command(), - Builder::Update(update) => update.to_command(), - Builder::Destroy(destroy) => destroy.to_command(), - } - } -} - -impl CheckParents for Builder { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { - match self { - Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), - Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), - Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), - Builder::Read(read) => match parents { - MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), - _ => Err(()), - }, - } - } -} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Create(create) => create.into(), - Builder::Read(read) => read.into(), - Builder::Update(update) => update.into(), - Builder::Destroy(destroy) => destroy.into(), - } - } -} +// impl ToCommand for Builder { +// fn to_command(&self) -> String { +// match self { +// Builder::Create(create) => create.to_command(), +// Builder::Read(read) => read.to_command(), +// Builder::Update(update) => update.to_command(), +// Builder::Destroy(destroy) => destroy.to_command(), +// } +// } +// } +// +// impl CheckParents for Builder { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { +// match self { +// Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), +// Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), +// Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), +// Builder::Read(read) => match parents { +// MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), +// _ => Err(()), +// }, +// } +// } +// } +// +// impl From for arguments::Named { +// fn from(builder: Builder) -> Self { +// match builder { +// Builder::Create(create) => create.into(), +// Builder::Read(read) => read.into(), +// Builder::Update(update) => update.into(), +// Builder::Destroy(destroy) => destroy.into(), +// } +// } +// } // impl From for arguments::Named { // fn from(promised: Promised) -> Self { @@ -257,43 +243,43 @@ impl From for arguments::Named { // } // } -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Create(create) => Builder::Create(create.into()), - Ready::Read(read) => Builder::Read(read.into()), - Ready::Update(update) => Builder::Update(update.into()), - Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), - Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), - Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), - Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), - } - } -} - -impl CheckSame for Builder { - type Error = (); - - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - match (self, other) { - (Builder::Create(a), Builder::Create(b)) => a.check_same(b), - (Builder::Read(a), Builder::Read(b)) => a.check_same(b), - (Builder::Update(a), Builder::Update(b)) => a.check_same(b), - (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), - _ => Err(()), - } - } -} +// impl From for Builder { +// fn from(ready: Ready) -> Self { +// match ready { +// Ready::Create(create) => Builder::Create(create.into()), +// Ready::Read(read) => Builder::Read(read.into()), +// Ready::Update(update) => Builder::Update(update.into()), +// Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), +// } +// } +// } +// +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(builder: Builder) -> Result { +// match builder { +// Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), +// Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), +// Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), +// Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), +// } +// } +// } +// +// impl CheckSame for Builder { +// type Error = (); +// +// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { +// match (self, other) { +// (Builder::Create(a), Builder::Create(b)) => a.check_same(b), +// (Builder::Read(a), Builder::Read(b)) => a.check_same(b), +// (Builder::Update(a), Builder::Update(b)) => a.check_same(b), +// (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), +// _ => Err(()), +// } +// } +// } impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index ae8a5b64..baa417cc 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,11 +1,11 @@ //! Create new resources. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -202,55 +202,55 @@ impl TryFrom> for Ready { } } -impl Delegable for Ready { - type Builder = Ready; -} - -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } +// impl Delegable for Ready { +// type Builder = Ready; +// } - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } // impl From for arguments::Named { // fn from(promised: Promised) -> Self { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index a5d48571..e43123cb 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,12 +1,12 @@ //! Destroy a resource. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -143,9 +143,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Ready; -} +// impl Delegable for Ready { +// type Builder = Ready; +// } impl TryFrom> for Ready { type Error = (); // FIXME @@ -168,49 +168,49 @@ impl TryFrom> for Ready { } } -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } - - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { @@ -265,10 +265,10 @@ impl From for Ready { } impl From for arguments::Named { - fn from(builder: Ready) -> Self { + fn from(ready: Ready) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { + if let Some(path) = ready.path { named.insert( "path".to_string(), path.into_os_string() diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 57001d8c..b9201b26 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,12 +1,12 @@ //! Read from a resource. -use super::any as crud; +// use super::any as crud; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -157,9 +157,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Ready; -} +// impl Delegable for Ready { +// type Builder = Ready; +// } // FIXME resolves vs resolvable is confusing @@ -220,39 +220,39 @@ impl TryFrom> for Ready { } } -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = crud::Any; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - // FIXME check the args, too! - if let Some(proof_path) = &other.path { - if self_path != proof_path { - return Err(()); - } - } - } - - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = crud::Any; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// // FIXME check the args, too! +// if let Some(proof_path) = &other.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// +// Ok(()) +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 305855e6..54863a76 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,11 +1,11 @@ //! Update existing resources. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -200,15 +200,38 @@ impl TryFrom> for Promised { } } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl TryFrom> for Ready { type Error = (); fn try_from(named: arguments::Named) -> Result { - Self::try_from_named(named).map_err(|_| ()) + let mut path = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::Map(map) = ipld { + args = Some(arguments::Named(map)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Ready { path, args }) } } @@ -293,51 +316,51 @@ impl TryFrom for Ready { } } -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl CheckSame for Builder { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Builder { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } - - Ok(()) - } -} +// impl Checkable for Builder { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Builder { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Builder { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { @@ -390,7 +413,7 @@ impl From for Builder { fn from(promised: Promised) -> Self { Builder { path: promised.path.and_then(|p| p.try_resolve().ok()), - args: promised.args.and_then(|a| a.try_resolve_option()), + args: todo!(), // promised.args.and_then(|a| a.try_resolve_option()), } } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a665a7aa..441c1565 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -5,7 +5,7 @@ use super::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError}, }; -use crate::proof::same::CheckSame; +// use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -118,27 +118,27 @@ impl TryFrom for Dynamic { } } -impl CheckSame for Dynamic { - type Error = String; // FIXME better err - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.cmd != proof.cmd { - return Err("Command mismatch".into()); - } - - self.args.0.iter().try_for_each(|(k, v)| { - if let Some(proof_v) = proof.args.get(k) { - if v != proof_v { - return Err("arguments::Named mismatch".into()); - } - } else { - return Err("arguments::Named mismatch".into()); - } - - Ok(()) - }) - } -} +// impl CheckSame for Dynamic { +// type Error = String; // FIXME better err +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.cmd != proof.cmd { +// return Err("Command mismatch".into()); +// } +// +// self.args.0.iter().try_for_each(|(k, v)| { +// if let Some(proof_v) = proof.args.get(k) { +// if v != proof_v { +// return Err("arguments::Named mismatch".into()); +// } +// } else { +// return Err("arguments::Named mismatch".into()); +// } +// +// Ok(()) +// }) +// } +// } impl From for Ipld { fn from(dynamic: Dynamic) -> Self { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 4fa01bed..e1d90481 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,22 +1,16 @@ //! Message abilities -mod any; - pub mod receive; pub mod send; -pub use any::Any; - use crate::{ ability::{ arguments, command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -27,22 +21,12 @@ pub enum Ready { Receive(receive::Receive), } -#[derive(Debug, Clone, PartialEq)] -pub enum Builder { - Send(send::Builder), - Receive(receive::Receive), -} - #[derive(Debug, Clone, PartialEq)] pub enum Promised { Send(send::Promised), Receive(receive::Promised), } -impl Delegable for Ready { - type Builder = Builder; -} - impl ToCommand for Ready { fn to_command(&self) -> String { match self { @@ -52,15 +36,6 @@ impl ToCommand for Ready { } } -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Send(send) => send.to_command(), - Builder::Receive(receive) => receive.to_command(), - } - } -} - impl ToCommand for Promised { fn to_command(&self) -> String { match self { @@ -89,72 +64,6 @@ impl ParsePromised for Promised { } } -// impl ParseAbility for Ready { -// type ArgsErr = (); -// -// fn try_parse( -// cmd: &str, -// args: arguments::Named, -// ) -> Result> { -// match send::Ready::try_parse(cmd, args.clone()) { -// Ok(send) => return Ok(Ready::Send(send)), -// Err(ParseAbilityError::InvalidArgs(args)) => { -// return Err(ParseAbilityError::InvalidArgs(())) -// } -// Err(ParseAbilityError::UnknownCommand(_)) => {} -// } -// -// match receive::Receive::try_parse(cmd, args) { -// Ok(receive) => return Ok(Ready::Receive(receive)), -// Err(ParseAbilityError::InvalidArgs(args)) => { -// return Err(ParseAbilityError::InvalidArgs(())) -// } -// Err(ParseAbilityError::UnknownCommand(cmd)) => {} -// } -// -// Err(ParseAbilityError::UnknownCommand(cmd.to_string())) -// } -// } - -impl ParseAbility for Builder { - type ArgsErr = (); - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result> { - if let Ok(send) = send::Builder::try_parse(cmd, args.clone()) { - return Ok(Builder::Send(send)); - } - - if let Ok(receive) = receive::Receive::try_parse(cmd, args) { - return Ok(Builder::Receive(receive)); - } - - Err(ParseAbilityError::UnknownCommand(cmd.to_string())) - } -} - -impl TryFrom for Ready { - type Error = (); - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Send(send) => send.try_into().map(Ready::Send).map_err(|_| ()), - Builder::Receive(receive) => Ok(Ready::Receive(receive)), - } - } -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Send(send) => Builder::Send(send.into()), - Ready::Receive(receive) => Builder::Receive(receive.into()), - } - } -} - impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { @@ -168,48 +77,30 @@ impl Resolvable for Ready { type Promised = Promised; } -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Send(this), Builder::Send(that)) => this.check_same(that), - (Builder::Receive(this), Builder::Receive(that)) => this.check_same(that), - _ => Err(()), +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), } } } -impl CheckParents for Builder { - type Parents = Any; - type ParentError = (); +impl ParseAbility for Ready { + type ArgsErr = (); - fn check_parent(&self, proof: &Any) -> Result<(), Self::ParentError> { - match (self, proof) { - (Builder::Send(this), any) => this.check_parent(&any), - (Builder::Receive(this), any) => this.check_parent(&any), + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Ready::try_parse(cmd, args.clone()) { + return Ok(Ready::Send(send)); } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Send(send) => send.into(), - Builder::Receive(receive) => receive.into(), + if let Ok(receive) = receive::Receive::try_parse(cmd, args) { + return Ok(Ready::Receive(receive)); } - } -} -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), - } + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 19373830..1b0e817b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,10 +2,10 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -59,9 +59,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Receive { - type Builder = Receive; -} +// impl Delegable for Receive { +// type Builder = Receive; +// } impl TryFrom> for Receive { type Error = (); @@ -94,33 +94,33 @@ impl From for arguments::Named { } } -impl Checkable for Receive { - type Hierarchy = Parentful; -} - -impl CheckSame for Receive { - type Error = (); // FIXME better error - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.from.check_same(&proof.from).map_err(|_| ()) - } -} - -impl CheckParents for Receive { - type Parents = super::Any; - type ParentError = ::Error; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(from) = &self.from { - if let Some(proof_from) = &proof.from { - if &from != &proof_from { - return Err(()); - } - } - } - - Ok(()) - } -} +// impl Checkable for Receive { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Receive { +// type Error = (); // FIXME better error +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// self.from.check_same(&proof.from).map_err(|_| ()) +// } +// } +// +// impl CheckParents for Receive { +// type Parents = super::Any; +// type ParentError = ::Error; +// +// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(from) = &self.from { +// if let Some(proof_from) = &proof.from { +// if &from != &proof_from { +// return Err(()); +// } +// } +// } +// +// Ok(()) +// } +// } impl From for Ipld { fn from(receive: Receive) -> Self { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fc8a2f2c..c65869d5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -2,16 +2,11 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, invocation::promise, - ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ipld, url, }; -use libipld_core::ipld::Ipld; +use libipld_core::{error::SerdeError, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -// use url::Url; -use crate::url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -56,48 +51,6 @@ pub struct Ready { pub message: String, } -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable variant of the `msg/send` ability. -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart LR -/// top("*") -/// -/// subgraph Message Abilities -/// any("msg/*") -/// -/// subgraph Invokable -/// send("msg/send") -/// end -/// end -/// -/// sendrun{{"invoke"}} -/// -/// top --> any -/// any --> send -.-> sendrun -/// -/// style send stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - /// The recipient of the message - pub to: Option, - - /// The sender address of the message - /// - /// This *may* be a URL (such as an email address). - /// If provided, the `subject` must have the right to send from this address. - pub from: Option, - - /// The main body of the message - pub message: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// @@ -143,14 +96,52 @@ pub struct Promised { pub message: promise::Resolves, } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl promise::Resolvable for Ready { type Promised = Promised; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, value) in named.0 { + match key.as_str() { + "to" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => { + to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?) + } + _ => return Err(()), + }, + "from" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => { + from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?) + } + _ => return Err(()), + }, + "message" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => message = Some(s), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Ready { + to: to.ok_or(())?, + from: from.ok_or(())?, + message: message.ok_or(())?, + }) + } +} + impl TryFrom> for Promised { type Error = (); @@ -196,19 +187,6 @@ impl TryFrom> for Promised { } } -impl From for arguments::Named { - fn from(b: Builder) -> Self { - let mut btree = BTreeMap::new(); - b.to.map(|to| btree.insert("to".into(), to.to_string().into())); - b.from - .map(|from| btree.insert("from".into(), from.to_string().into())); - b.message - .map(|msg| btree.insert("message".into(), msg.into())); - - arguments::Named(btree) - } -} - impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ @@ -219,103 +197,16 @@ impl From for arguments::Named { } } -impl From for Builder { - fn from(p: Promised) -> Self { - Builder { - to: p.to.into(), - from: p.from.into(), - message: p.message.into(), - } - } -} - const COMMAND: &'static str = "msg/send"; impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} - impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl CheckSame for Builder { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.to.check_same(&proof.to).map_err(|_| ())?; - self.from.check_same(&proof.from).map_err(|_| ())?; - self.message.check_same(&proof.message).map_err(|_| ()) - } -} - -impl CheckParents for Builder { - type Parents = super::Any; - type ParentError = ::Error; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&proof.from).map_err(|_| ()) - } -} - -impl TryFrom> for Builder { - type Error = (); - - fn try_from(args: arguments::Named) -> Result { - let mut to = None; - let mut from = None; - let mut message = None; - - for (key, ipld) in args.0 { - match key.as_str() { - "to" => { - // FIXME extract this common pattern - if let Ipld::String(s) = ipld { - to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); - } else { - return Err(()); - } - } - "from" => { - if let Ipld::String(s) = ipld { - from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); - } else { - return Err(()); - } - } - "message" => { - if let Ipld::String(s) = ipld { - message = Some(s); - } else { - return Err(()); - } - } - _ => return Err(()), - } - } - - Ok(Builder { to, from, message }) - } -} - -impl From for Builder { - fn from(resolved: Ready) -> Self { - Builder { - to: resolved.to.into(), - from: resolved.from.into(), - message: resolved.message.into(), - } - } -} - impl From for Promised { fn from(r: Ready) -> Self { Promised { @@ -337,24 +228,6 @@ impl TryFrom for Ready { } } -impl TryFrom for Ready { - type Error = Builder; - - fn try_from(b: Builder) -> Result { - // Entirely by refernce - if b.to.is_none() || b.from.is_none() || b.message.is_none() { - return Err(b); - } - - // Moves, and unwrap because we checked above instead of 2 clones per line - Ok(Ready { - to: b.to.unwrap(), - from: b.from.unwrap(), - message: b.message.unwrap(), - }) - } -} - impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 2443e26a..29e0510f 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -5,10 +5,8 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -20,37 +18,6 @@ pub enum Ready { Wasm(wasm::run::Ready), } -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Builder { - Crud(crud::Builder), - Msg(msg::Builder), - Wasm(wasm::run::Builder), -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Parents { - Crud(crud::MutableParents), - Msg(msg::Any), -} // NOTE WasmRun has no parents - -impl CheckSame for Parents { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof_)) => self_.check_same(proof_).map_err(|_| ()), - (Parents::Crud(self_), Parents::Crud(proof_)) => { - self_.check_same(proof_).map_err(|_| ()) - } - _ => Err(()), - } - } -} - -impl Delegable for Ready { - type Builder = Builder; -} - impl ToCommand for Ready { fn to_command(&self) -> String { match self { @@ -60,66 +27,6 @@ impl ToCommand for Ready { } } } - -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Crud(builder) => builder.to_command(), - Builder::Msg(builder) => builder.to_command(), - Builder::Wasm(builder) => builder.to_command(), - } - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} - -impl CheckParents for Builder { - type Parents = Parents; - type ParentError = (); // FIXME - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - match (self, proof) { - (Builder::Msg(builder), Parents::Msg(proof)) => builder.check_parent(proof), - _ => Err(()), - } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Crud(ready) => Builder::Crud(ready.into()), - Ready::Msg(ready) => Builder::Msg(ready.into()), - Ready::Wasm(ready) => Builder::Wasm(ready.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Crud(builder) => builder.try_into().map(Ready::Crud).map_err(|_| ()), - Builder::Msg(builder) => builder.try_into().map(Ready::Msg).map_err(|_| ()), - Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm).map_err(|_| ()), - } - } -} - #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Promised { Crud(crud::Promised), @@ -170,27 +77,27 @@ impl ParsePromised for Promised { } } -impl ParseAbility for Builder { +impl ParseAbility for Ready { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match msg::Builder::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Builder::Msg(builder)), + match msg::Ready::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Ready::Msg(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } - match crud::Builder::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Builder::Crud(builder)), + match crud::Ready::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Ready::Crud(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } - match wasm::run::Builder::try_parse(cmd, args) { - Ok(builder) => return Ok(Builder::Wasm(builder)), + match wasm::run::Ready::try_parse(cmd, args) { + Ok(builder) => return Ok(Ready::Wasm(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } @@ -199,24 +106,24 @@ impl ParseAbility for Builder { } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Crud(builder) => builder.into(), - Builder::Msg(builder) => builder.into(), - Builder::Wasm(builder) => builder.into(), - } - } -} - -impl From for arguments::Named { - fn from(parents: Parents) -> Self { - match parents { - Parents::Crud(parents) => parents.into(), - Parents::Msg(parents) => parents.into(), - } - } -} +// impl From for arguments::Named { +// fn from(builder: Builder) -> Self { +// match builder { +// Builder::Crud(builder) => builder.into(), +// Builder::Msg(builder) => builder.into(), +// Builder::Wasm(builder) => builder.into(), +// } +// } +// } +// +// impl From for arguments::Named { +// fn from(parents: Parents) -> Self { +// match parents { +// Parents::Crud(parents) => parents.into(), +// Parents::Msg(parents) => parents.into(), +// } +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index fd2b29cf..dcb7aec5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,14 +4,14 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, + // proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::fmt::Debug; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -26,17 +26,17 @@ impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} +// impl Command for Builder { +// const COMMAND: &'static str = COMMAND; +// } impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl TryFrom> for Ready { type Error = (); @@ -49,26 +49,26 @@ impl TryFrom> for Ready { } } -impl TryFrom> for Builder { - type Error = (); - - fn try_from(arguments: arguments::Named) -> Result { - if let Some(ipld) = arguments.get("ucan") { - let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; - Ok(Builder { ucan: Some(nt.cid) }) - } else { - Ok(Builder { ucan: None }) - } - } -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - ucan: promised.ucan.try_resolve().ok(), - } - } -} +// impl TryFrom> for Builder { +// type Error = (); +// +// fn try_from(arguments: arguments::Named) -> Result { +// if let Some(ipld) = arguments.get("ucan") { +// let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; +// Ok(Builder { ucan: Some(nt.cid) }) +// } else { +// Ok(Builder { ucan: None }) +// } +// } +// } + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// ucan: promised.ucan.try_resolve().ok(), +// } +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; @@ -80,49 +80,49 @@ impl From for arguments::Named { } } -/// A variant with some fields waiting to be set. -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -pub struct Builder { - pub ucan: Option, -} - -impl NoParents for Builder {} - -impl CheckSame for Builder { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.ucan.check_same(&proof.ucan) - } -} - -impl From for Builder { - fn from(resolved: Ready) -> Builder { - Builder { - ucan: Some(resolved.ucan), - } - } -} - -impl TryFrom for Ready { - type Error = (); - - fn try_from(b: Builder) -> Result { - Ok(Ready { - ucan: b.ucan.ok_or(())?, - }) - } -} - -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { - let mut btree = BTreeMap::new(); - if let Some(cid) = b.ucan { - btree.insert("ucan".into(), cid.into()); - } - arguments::Named(btree) - } -} +// /// A variant with some fields waiting to be set. +// #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +// pub struct Builder { +// pub ucan: Option, +// } +// +// impl NoParents for Builder {} +// +// impl CheckSame for Builder { +// type Error = OptionalFieldError; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// self.ucan.check_same(&proof.ucan) +// } +// } + +// impl From for Builder { +// fn from(resolved: Ready) -> Builder { +// Builder { +// ucan: Some(resolved.ucan), +// } +// } +// } + +// impl TryFrom for Ready { +// type Error = (); +// +// fn try_from(b: Builder) -> Result { +// Ok(Ready { +// ucan: b.ucan.ok_or(())?, +// }) +// } +// } +// +// impl From for arguments::Named { +// fn from(b: Builder) -> arguments::Named { +// let mut btree = BTreeMap::new(); +// if let Some(cid) = b.ucan { +// btree.insert("ucan".into(), cid.into()); +// } +// arguments::Named(btree) +// } +// } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index e9301a91..0c7f8cfa 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,14 +3,13 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{parentless::NoParents, same::CheckSame}, + // proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; const COMMAND: &'static str = "wasm/run"; @@ -18,10 +17,6 @@ impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} - impl Command for Promised { const COMMAND: &'static str = COMMAND; } @@ -39,11 +34,7 @@ pub struct Ready { pub args: Vec, } -impl Delegable for Ready { - type Builder = Builder; -} - -impl TryFrom> for Builder { +impl TryFrom> for Ready { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -74,10 +65,10 @@ impl TryFrom> for Builder { } } - Ok(Builder { - module, - function, - args, + Ok(Ready { + module: module.ok_or(())?, + function: function.ok_or(())?, + args: args.ok_or(())?, }) } } @@ -86,88 +77,6 @@ impl promise::Resolvable for Ready { type Promised = Promised; } -/// A variant meant for delegation, where fields may be omitted -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Builder { - /// The Wasm module to run - pub module: Option, - - /// The function from the module to run - pub function: Option, - - /// Arguments to pass to the function - pub args: Option>, -} - -impl NoParents for Builder {} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - let mut btree = BTreeMap::new(); - if let Some(module) = builder.module { - btree.insert("module".into(), Ipld::from(module)); - } - - if let Some(function) = builder.function { - btree.insert("function".into(), Ipld::String(function)); - } - - if let Some(args) = builder.args { - btree.insert("args".into(), Ipld::List(args)); - } - - arguments::Named(btree) - } -} - -impl From for Builder { - fn from(ready: Ready) -> Builder { - Builder { - module: Some(ready.module), - function: Some(ready.function), - args: Some(ready.args), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Ready { - module: b.module.ok_or(())?, - function: b.function.ok_or(())?, - args: b.args.ok_or(())?, - }) - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(module) = &self.module { - if module != proof.module.as_ref().unwrap() { - return Err(()); - } - } - - if let Some(function) = &self.function { - if function != proof.function.as_ref().unwrap() { - return Err(()); - } - } - - if let Some(args) = &self.args { - if args != proof.args.as_ref().unwrap() { - return Err(()); - } - } - - Ok(()) - } -} - /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Promised { diff --git a/src/delegation.rs b/src/delegation.rs index 5d81718e..e4c077db 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -16,18 +16,18 @@ pub mod condition; pub mod store; mod agent; -mod delegable; +// mod delegable; mod payload; pub use agent::Agent; -pub use delegable::Delegable; -pub use payload::{Payload, ValidationError}; +// pub use delegable::Delegable; +pub use payload::*; use crate::{ - ability, + // ability, crypto::{signature, varsig, Nonce}, did::{self, Did}, - proof::{parents::CheckParents, same::CheckSame}, + // proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; @@ -47,16 +47,14 @@ use web_time::SystemTime; /// FIXME #[derive(Clone, Debug, PartialEq)] pub struct Delegation< - D, C: Condition, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation< - ability::preset::Builder, condition::Preset, did::preset::Verifier, varsig::header::Preset, @@ -65,8 +63,8 @@ pub type Preset = Delegation< // FIXME checkable -> provable? -impl, Enc: Codec + Into + TryFrom> - Delegation +impl, Enc: Codec + Into + TryFrom> + Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { @@ -74,7 +72,7 @@ impl, Enc: Codec + Into + } /// Retrive the `subject` of a [`Delegation`] - pub fn subject(&self) -> &DID { + pub fn subject(&self) -> &Option { &self.0.payload.subject } @@ -83,22 +81,6 @@ impl, Enc: Codec + Into + &self.0.payload.audience } - /// Retrive the `ability_builder` of a [`Delegation`] - pub fn ability_builder(&self) -> &B { - &self.0.payload.ability_builder - } - - pub fn map_ability_builder(self, f: F) -> Delegation - where - F: FnOnce(B) -> T, - { - Delegation(signature::Envelope::new( - self.0.varsig_header, - self.0.signature, - self.0.payload.map_ability(f), - )) - } - /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { &self.0.payload.conditions @@ -128,7 +110,7 @@ impl, Enc: Codec + Into + self.0.payload.check_time(now) } - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -146,7 +128,7 @@ impl, Enc: Codec + Into + pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone + Encode, Ipld: Encode, { self.0.cid() @@ -154,7 +136,7 @@ impl, Enc: Codec + Into + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where - Payload: Clone, + Payload: Clone, Ipld: Encode, { self.0.validate_signature() @@ -163,43 +145,43 @@ impl, Enc: Codec + Into + pub fn try_sign( signer: &DID::Signer, varsig_header: V, - payload: Payload, + payload: Payload, ) -> Result where Ipld: Encode, - Payload: Clone, + Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl< - B: CheckSame, - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > CheckSame for Delegation -{ - type Error = ::Error; - - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { - self.0.payload.check_same(&proof.payload()) - } -} - -impl< - T: CheckParents, - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > CheckParents for Delegation -{ - type Parents = Delegation; - type ParentError = ::ParentError; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.payload().check_parent(&proof.payload()) - } -} +// impl< +// B: CheckSame, +// C: Condition, +// DID: Did, +// V: varsig::Header, +// Enc: Codec + TryFrom + Into, +// > CheckSame for Delegation +// { +// type Error = ::Error; +// +// fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { +// self.0.payload.check_same(&proof.payload()) +// } +// } +// +// impl< +// T: CheckParents, +// C: Condition, +// DID: Did, +// V: varsig::Header, +// Enc: Codec + TryFrom + Into, +// > CheckParents for Delegation +// { +// type Parents = Delegation; +// type ParentError = ::ParentError; +// +// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { +// self.payload().check_parent(&proof.payload()) +// } +// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 389de622..42745c80 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -2,7 +2,7 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, - proof::checkable::Checkable, + // proof::checkable::Checkable, time::Timestamp, }; use libipld_core::{ @@ -20,10 +20,9 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - B: Checkable, C: Condition, DID: Did, - S: Store, + S: Store, V: varsig::Header, Enc: Codec + TryFrom + Into, > { @@ -34,18 +33,17 @@ pub struct Agent< pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(B, C, V, Enc)>, + _marker: PhantomData<(C, V, Enc)>, } impl< 'a, - B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, + S: Store + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Agent<'a, B, C, DID, S, V, Enc> + > Agent<'a, C, DID, S, V, Enc> where Ipld: Encode, { @@ -61,37 +59,39 @@ where pub fn delegate( &self, audience: DID, - subject: DID, - ability_builder: B, + subject: Option, new_conditions: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); - if subject == *self.did { - let payload: Payload = Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - metadata, - nonce, - expiration: expiration.into(), - not_before: not_before.map(Into::into), - conditions: new_conditions, - }; - - return Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")); + if let Some(ref sub) = subject { + if sub == self.did { + let payload: Payload = Payload { + issuer: self.did.clone(), + audience, + subject, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + conditions: new_conditions, + }; + + return Ok( + Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME") + ); + } } let to_delegate = &self .store - .get_chain(&self.did, &subject, &ability_builder, vec![], now) + .get_chain(&self.did, &subject, vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -101,11 +101,10 @@ where let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, - ability_builder, conditions, metadata, nonce, @@ -119,7 +118,7 @@ where pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index a8929ab1..9ab7e613 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,51 +1,51 @@ -use crate::{ - ability::{ - arguments, - command::ToCommand, - parse::{ParseAbility, ParseAbilityError}, - }, - proof::checkable::Checkable, -}; -use libipld_core::ipld::Ipld; - -/// A trait for types that can be delegated. -/// -/// Since [`Delegation`]s may omit fields (until [`Invocation`]), -/// this trait helps associate the delegatable variant to the invocable one. -/// -/// [`Delegation`]: crate::delegation::Delegation -/// [`Invocation`]: crate::invocation::Invocation -// FIXME NOTE: don't need parse ability, because parse -> builder -> self -// FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. -pub trait Delegable: Sized { - /// A delegation with some arguments filled. - type Builder: TryInto - + From - + Checkable - + ParseAbility - + ToCommand - + Into>; - - fn into_command(self) -> String { - Self::Builder::from(self).to_command() - } - - fn into_named_args(self) -> arguments::Named { - Self::Builder::from(self).into() - } - - fn try_parse_to_ready( - command: &str, - named: arguments::Named, - ) -> Result::ArgsErr>> { - let builder = Self::Builder::try_parse(command, named)?; - builder.try_into().map_err(|err| todo!()) - } - - fn try_from_named( - named: arguments::Named, - ) -> Result::ArgsErr>> { - let builder = Self::Builder::try_parse("", named)?; - builder.try_into().map_err(|err| todo!()) - } -} +// use crate::{ +// ability::{ +// arguments, +// command::ToCommand, +// parse::{ParseAbility, ParseAbilityError}, +// }, +// proof::checkable::Checkable, +// }; +// use libipld_core::ipld::Ipld; +// +// /// A trait for types that can be delegated. +// /// +// /// Since [`Delegation`]s may omit fields (until [`Invocation`]), +// /// this trait helps associate the delegatable variant to the invocable one. +// /// +// /// [`Delegation`]: crate::delegation::Delegation +// /// [`Invocation`]: crate::invocation::Invocation +// // FIXME NOTE: don't need parse ability, because parse -> builder -> self +// // FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. +// pub trait Delegable: Sized { +// /// A delegation with some arguments filled. +// type Builder: TryInto +// + From +// + Checkable +// + ParseAbility +// + ToCommand +// + Into>; +// +// fn into_command(self) -> String { +// Self::Builder::from(self).to_command() +// } +// +// fn into_named_args(self) -> arguments::Named { +// Self::Builder::from(self).into() +// } +// +// fn try_parse_to_ready( +// command: &str, +// named: arguments::Named, +// ) -> Result::ArgsErr>> { +// let builder = Self::Builder::try_parse(command, named)?; +// builder.try_into().map_err(|err| todo!()) +// } +// +// fn try_from_named( +// named: arguments::Named, +// ) -> Result::ArgsErr>> { +// let builder = Self::Builder::try_parse("", named)?; +// builder.try_into().map_err(|err| todo!()) +// } +// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bd557979..bbde3e99 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,29 +1,13 @@ use super::condition::Condition; use crate::{ - ability::{ - arguments, - command::{Command, ToCommand}, - parse::ParseAbility, - }, capsule::Capsule, crypto::Nonce, did::{Did, Verifiable}, - proof::{ - checkable::Checkable, - parents::CheckParents, - prove::{Prove, Success}, - same::CheckSame, - }, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::{self, MapAccess, Visitor}, - ser::SerializeStruct, - Deserialize, Serialize, Serializer, -}; -use std::{collections::BTreeMap, fmt, fmt::Debug}; -use thiserror::Error; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -36,8 +20,8 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -48,7 +32,7 @@ pub struct Payload { /// by the subject. /// /// [`Delegation`]: super::Delegation - pub subject: DID, + pub subject: Option, /// The issuer of the [`Delegation`]. /// @@ -61,11 +45,6 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, - /// A delegatable ability chain. - /// - /// Note that this should be is some [Proof::Hierarchy] - pub ability_builder: D, - /// Any [`Condition`]s on the `ability_builder`. pub conditions: Vec, @@ -91,35 +70,7 @@ pub struct Payload { pub not_before: Option, } -impl Payload { - pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { - Payload { - issuer: self.issuer, - subject: self.subject, - audience: self.audience, - ability_builder: f(self.ability_builder), - conditions: self.conditions, - metadata: self.metadata, - nonce: self.nonce, - expiration: self.expiration, - not_before: self.not_before, - } - } - - pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { - Payload { - issuer: self.issuer, - subject: self.subject, - audience: self.audience, - ability_builder: self.ability_builder, - conditions: self.conditions.into_iter().map(f).collect(), - metadata: self.metadata, - nonce: self.nonce, - expiration: self.expiration, - not_before: self.not_before, - } - } - +impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); @@ -137,227 +88,18 @@ impl Payload { } } -impl Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +impl Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0"; } -impl Verifiable for Payload { +impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer } } -impl CheckSame for Payload { - type Error = ::Error; - - fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { - self.ability_builder.check_same(&proof.ability_builder) - } -} - -impl CheckParents for Payload { - type Parents = Payload; - type ParentError = ::ParentError; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.ability_builder.check_parent(&proof.ability_builder) - } -} - -impl Serialize - for Payload -where - Ipld: From, - arguments::Named: From, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let count_nbf = self.not_before.is_some() as usize; - let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; - - state.serialize_field("iss", &self.issuer.clone().into().to_string())?; - state.serialize_field("sub", &self.subject.clone().into().to_string())?; - state.serialize_field("aud", &self.audience.clone().into().to_string())?; - state.serialize_field("meta", &self.metadata)?; - state.serialize_field("nonce", &self.nonce)?; - state.serialize_field("exp", &self.expiration)?; - - state.serialize_field("cmd", &self.ability_builder.to_command())?; - - state.serialize_field( - "args", - &arguments::Named::from(self.ability_builder.clone()), - )?; - - state.serialize_field( - "cond", - &self - .conditions - .iter() - .map(|c| Ipld::from(c.clone())) - .collect::>(), - )?; - - if let Some(nbf) = self.not_before { - state.serialize_field("nbf", &nbf)?; - } - - state.end() - } -} - -impl< - 'de, - T: ParseAbility + Deserialize<'de> + ToCommand, - C: Condition + Deserialize<'de>, - DID: Did + Deserialize<'de>, - > Deserialize<'de> for Payload -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct DelegationPayloadVisitor( - std::marker::PhantomData<(T, C, DID)>, - ); - - const FIELDS: &'static [&'static str] = &[ - "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", - ]; - - impl< - 'de, - T: ParseAbility + Deserialize<'de>, - C: Condition + Deserialize<'de>, - DID: Did + Deserialize<'de>, - > Visitor<'de> for DelegationPayloadVisitor - { - type Value = Payload; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("struct delegation::Payload") - } - - fn visit_map>(self, mut map: M) -> Result { - let mut issuer = None; - let mut subject = None; - let mut audience = None; - let mut command = None; - let mut arguments = None; - let mut conditions = None; - let mut metadata = None; - let mut nonce = None; - let mut expiration = None; - let mut not_before = None; - - while let Some(key) = map.next_key()? { - match key { - "iss" => { - if issuer.is_some() { - return Err(de::Error::duplicate_field("iss")); - } - issuer = Some(map.next_value()?); - } - "sub" => { - if subject.is_some() { - return Err(de::Error::duplicate_field("sub")); - } - subject = Some(map.next_value()?); - } - "aud" => { - if audience.is_some() { - return Err(de::Error::duplicate_field("aud")); - } - audience = Some(map.next_value()?); - } - "cmd" => { - if command.is_some() { - return Err(de::Error::duplicate_field("cmd")); - } - command = Some(map.next_value()?); - } - "args" => { - if arguments.is_some() { - return Err(de::Error::duplicate_field("args")); - } - arguments = Some(map.next_value()?); - } - "cond" => { - if conditions.is_some() { - return Err(de::Error::duplicate_field("cond")); - } - conditions = Some(map.next_value()?); - } - "meta" => { - if metadata.is_some() { - return Err(de::Error::duplicate_field("meta")); - } - metadata = Some(map.next_value()?); - } - "nonce" => { - if nonce.is_some() { - return Err(de::Error::duplicate_field("nonce")); - } - nonce = Some(map.next_value()?); - } - "exp" => { - if expiration.is_some() { - return Err(de::Error::duplicate_field("exp")); - } - expiration = Some(map.next_value()?); - } - "nbf" => { - if not_before.is_some() { - return Err(de::Error::duplicate_field("nbf")); - } - not_before = Some(map.next_value()?); - } - other => { - return Err(de::Error::unknown_field(other, FIELDS)); - } - } - } - - let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; - let args = arguments.ok_or(de::Error::missing_field("args"))?; - - let ability_builder = - ::try_parse(cmd.as_str(), args).map_err(|e| { - de::Error::custom(format!( - "Unable to parse ability field for {:?} because {:?}", - cmd, e - )) - })?; - - Ok(Payload { - issuer: issuer.ok_or(de::Error::missing_field("iss"))?, - subject: subject.ok_or(de::Error::missing_field("sub"))?, - audience: audience.ok_or(de::Error::missing_field("aud"))?, - conditions: conditions.ok_or(de::Error::missing_field("cond"))?, - metadata: metadata.ok_or(de::Error::missing_field("meta"))?, - nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, - expiration: expiration.ok_or(de::Error::missing_field("exp"))?, - ability_builder, - not_before, - }) - } - } - - deserializer.deserialize_struct( - "DelegationPayload", - FIELDS, - DelegationPayloadVisitor(Default::default()), - ) - } -} - -impl< - T: ParseAbility + Command + for<'de> Deserialize<'de>, - C: Condition + for<'de> Deserialize<'de>, - DID: Did + for<'de> Deserialize<'de>, - > TryFrom for Payload +impl Deserialize<'de>, DID: Did + for<'de> Deserialize<'de>> TryFrom + for Payload { type Error = SerdeError; @@ -366,150 +108,25 @@ impl< } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl>, C: Condition, DID: Did> Payload { - pub fn check( - &self, - proofs: Vec<&Payload>, - now: &SystemTime, - ) -> Result<(), ValidationError<::Error, C>> - where - T: Clone, - C: Clone, - DID: Clone, - T::Hierarchy: Clone + Into>, - { - let start: Acc = Acc { - issuer: self.issuer.clone(), - subject: self.subject.clone(), - hierarchy: T::Hierarchy::from(self.ability_builder.clone()), - }; - - let args: arguments::Named = self.ability_builder.clone().into(); - - proofs.into_iter().fold(Ok(start), |prev, proof| { - if let Ok(prev_) = prev { - prev_.step(&proof, &args, now).map(move |success| { - match success { - Success::ProvenByAny => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: prev_.hierarchy, - }, - Success::Proven => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: proof.ability_builder.clone(), // FIXME double check - }, - } - }) - } else { - prev - } - })?; - - Ok(()) - } -} - -#[derive(Debug, Clone)] -struct Acc { - issuer: DID, - subject: DID, - hierarchy: H, -} - -impl Acc { - // FIXME this should move to Delegable? - fn step<'a, C: Condition>( - &self, - proof: &Payload, - args: &arguments::Named, - now: &SystemTime, - ) -> Result::Error, C>> - where - C: Clone, - H: Prove + Clone + Into>, - { - if self.issuer != proof.audience { - return Err(ValidationError::InvalidSubject.into()); - } - - if self.subject != proof.subject { - return Err(ValidationError::MisalignedIssAud.into()); - } - - if SystemTime::from(proof.expiration.clone()) > *now { - return Err(ValidationError::Expired.into()); - } - - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { - return Err(ValidationError::NotYetValid.into()); - } - } - - // This could be more efficient (dedup) with sets, but floats don't Ord :( - for c in proof.conditions.iter() { - // Validate both current & proof integrity. - // This should have the same semantic guarantees as looking at subsets, - // but for all known conditions will run much faster on average. - // Plz let me know if I got this wrong. - // —@expede - if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { - return Err(ValidationError::FailedCondition(c.clone())); - } - } - - self.hierarchy - .check(&proof.ability_builder.clone()) - .map_err(ValidationError::AbilityError) - } -} - -/// Delegation validation errors. -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum ValidationError { - #[error("The subject of the delegation is invalid")] - InvalidSubject, - - #[error("The issuer and audience of the delegation are misaligned")] - MisalignedIssAud, - - #[error("The delegation has expired")] - Expired, - - #[error("The delegation is not yet valid")] - NotYetValid, - - #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), - - #[error(transparent)] - AbilityError(AbilityError), -} - #[cfg(feature = "test_utils")] -impl Arbitrary - for Payload +impl Arbitrary for Payload where - T::Strategy: 'static, C::Strategy: 'static, DID::Parameters: Clone, C::Parameters: Clone, { - type Parameters = (T::Parameters, DID::Parameters, C::Parameters); + type Parameters = (DID::Parameters, C::Parameters); type Strategy = BoxedStrategy; - fn arbitrary_with((t_args, did_args, c_args): Self::Parameters) -> Self::Strategy { + fn arbitrary_with((did_args, c_args): Self::Parameters) -> Self::Strategy { ( - T::arbitrary_with(t_args), - DID::arbitrary_with(did_args.clone()), + Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), DID::arbitrary_with(did_args), Nonce::arbitrary(), @@ -524,10 +141,9 @@ where ) .prop_map( |( - ability_builder, + subject, issuer, audience, - subject, nonce, expiration, not_before, @@ -538,7 +154,6 @@ where issuer, subject, audience, - ability_builder, conditions, metadata, nonce, diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e87399af..e87bfc5d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,12 +1,12 @@ use super::Store; use crate::{ - ability::arguments, + // ability::arguments, crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, - proof::{checkable::Checkable, prove::Prove}, + // proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; +use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -72,41 +72,34 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - H, C: Condition, DID: Did + Ord, V: varsig::Header, Enc: Codec + TryFrom + Into, > { - ucans: BTreeMap>, - index: BTreeMap>>, + ucans: BTreeMap>, + index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid impl< - B: Checkable + Clone, C: Condition + PartialEq, DID: Did + Ord + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Store for MemoryStore -where - B::Hierarchy: Into> + Clone, + > Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng - fn get( - &self, - cid: &Cid, - ) -> Result<&Delegation, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) @@ -115,10 +108,7 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = - delegation.map_ability_builder(Into::into); - - self.ucans.insert(cid.clone(), hierarchy); + self.ucans.insert(cid.clone(), delegation); Ok(()) } @@ -130,15 +120,16 @@ where fn get_chain( &self, aud: &DID, - subject: &DID, - builder: &B, + subject: &Option, conditions: Vec, now: SystemTime, - ) -> Result< - Option)>>, - Self::DelegationStoreError, - > { - match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + ) -> Result)>>, Self::DelegationStoreError> + { + match self + .index + .get(subject) // FIXME probably need to rework this after last minbute chanegs + .and_then(|aud_map| aud_map.get(aud)) + { None => Ok(None), Some(delegation_subtree) => { #[derive(PartialEq)] @@ -150,7 +141,6 @@ where let mut status = Status::Looking; let mut target_aud = aud; - let mut args = &B::Hierarchy::from(builder.clone()); let mut chain = vec![]; while status == Status::Looking { @@ -166,24 +156,14 @@ where target_aud = &d.audience(); - if args.check(&d.ability_builder()).is_ok() { - args = &d.ability_builder(); - } else { - return ControlFlow::Continue(()); - } - - for condition in &conditions { - if !condition.validate(&d.ability_builder().clone().into()) { - return ControlFlow::Continue(()); - } - } - chain.push((*cid, d)); - if d.issuer() == subject { - status = Status::Complete; + if let Some(ref subject) = subject { + if d.issuer() == subject { + status = Status::Complete; + } } else { - target_aud = &d.issuer(); + status = Status::Complete; } ControlFlow::Break(()) diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 93c6d525..44b032dd 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -2,7 +2,7 @@ use crate::{ crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, - proof::checkable::Checkable, + // proof::checkable::Checkable, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -11,7 +11,6 @@ use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store< - B: Checkable, C: Condition, DID: Did, V: varsig::Header, @@ -20,10 +19,7 @@ pub trait Store< { type DelegationStoreError: Debug; - fn get( - &self, - cid: &Cid, - ) -> Result<&Delegation, Self::DelegationStoreError>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -31,7 +27,7 @@ pub trait Store< fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -42,33 +38,28 @@ pub trait Store< fn get_chain( &self, audience: &DID, - subject: &DID, - builder: &B, + subject: &Option, conditions: Vec, now: SystemTime, - ) -> Result< - Option)>>, - Self::DelegationStoreError, - >; + ) -> Result)>>, Self::DelegationStoreError>; fn can_delegate( &self, - issuer: &DID, + issuer: DID, audience: &DID, - builder: &B, conditions: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, issuer, builder, conditions, now) + self.get_chain(audience, &Some(issuer), conditions, now) .map(|chain| chain.is_some()) } fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/invocation.rs b/src/invocation.rs index 2aa36b8e..7e66670e 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -19,7 +19,7 @@ pub mod promise; pub mod store; pub use agent::Agent; -pub use payload::{Payload, Promised}; +pub use payload::*; use crate::{ ability, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 9bc24aab..5e728646 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,15 +1,20 @@ -use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; +use super::{ + payload::{Payload, ValidationError}, + promise::Resolvable, + store::Store, + Invocation, +}; use crate::{ ability::{ + arguments, parse::{ParseAbility, ParseAbilityError, ParsePromised}, ucan, }, crypto::{signature, varsig, Nonce}, - delegation, - delegation::{condition::Condition, Delegable}, + delegation::{self, condition::Condition}, did::Did, invocation::promise, - proof::{checkable::Checkable, prove::Prove}, + // proof::prove::Prove, time::Timestamp, }; use libipld_core::{ @@ -28,12 +33,12 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable + Delegable, + T: Resolvable, C: Condition, DID: Did, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, > { @@ -51,13 +56,13 @@ impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, - delegation::Payload<::Hierarchy, C, DID>: Clone, - T: Resolvable + Delegable + Clone, + delegation::Payload: Clone, + T: Resolvable + Clone, C: Condition, DID: Did + Clone, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, { @@ -80,8 +85,8 @@ where pub fn invoke( &mut self, - audience: Option<&DID>, - subject: &DID, + audience: Option, + subject: DID, ability: T, metadata: BTreeMap, cause: Option, @@ -90,32 +95,42 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + ParseAbilityError<()>, // FIXME argserror >, - > - where - <::Promised as ParsePromised>::PromisedArgsError: fmt::Debug, - { - self.invoke_promise( - audience, + > { + let proofs = self + .delegation_store + .get_chain(self.did, &Some(subject.clone()), vec![], now) + .map_err(InvokeError::DelegationStoreError)? + .map(|chain| chain.map(|(cid, _)| cid).into()) + .unwrap_or(vec![]); + + let mut seed = vec![]; + + let payload = Payload { + issuer: self.did.clone(), subject, - Resolvable::into_promised(ability), + audience, + ability, + proofs, metadata, + nonce: Nonce::generate_12(&mut seed), cause, expiration, issued_at, - now, - varsig_header, - ) + }; + + Ok(Invocation::try_sign(self.signer, varsig_header, payload) + .map_err(InvokeError::SignError)?) } pub fn invoke_promise( &mut self, audience: Option<&DID>, - subject: &DID, + subject: DID, ability: T::Promised, metadata: BTreeMap, cause: Option, @@ -127,19 +142,12 @@ where Invocation, InvokeError< D::DelegationStoreError, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + ParseAbilityError<()>, // FIXME errs >, > { let proofs = self .delegation_store - .get_chain( - self.did, - subject, - &::try_to_builder(ability.clone()) - .map_err(InvokeError::PromiseResolveError)?, - vec![], - now, - ) + .get_chain(self.did, &Some(subject.clone()), vec![], now) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -148,7 +156,7 @@ where let payload = Payload { issuer: self.did.clone(), - subject: subject.clone(), + subject, audience: audience.cloned(), ability, proofs, @@ -172,12 +180,10 @@ where ReceiveError, > where - Enc: From + Into, - T::Builder: Clone + Encode, C: Clone, - ::Hierarchy: Clone, + Enc: From + Into, + arguments::Named: From, Invocation: Clone, - <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, signature::Envelope, DID, V, Enc>: Clone, ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, @@ -217,9 +223,9 @@ where let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); - delegation::Payload::::from(resolved_payload.clone()) + let _ = &resolved_payload .check(proof_payloads, now) - .map_err(ReceiveError::DelegationValidationError)?; + .map_err(ReceiveError::ValidationError)?; if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); @@ -230,7 +236,7 @@ where pub fn revoke( &mut self, - subject: &DID, + subject: DID, cause: Option, cid: Cid, now: Timestamp, @@ -241,17 +247,11 @@ where T: From, { let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); - let proofs = if subject == self.did { + let proofs = if &subject == self.did { vec![] } else { self.delegation_store - .get_chain( - subject, - self.did, - &ability.clone().into(), - vec![], - now.into(), - ) + .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) .unwrap_or(vec![]) @@ -297,10 +297,6 @@ pub enum ReceiveError< V: varsig::Header, Enc: Codec + From + Into, > where - delegation::ValidationError< - <<::Builder as Checkable>::Hierarchy as Prove>::Error, - C, - >: fmt::Debug,

>::PromiseStoreError: fmt::Debug, ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { @@ -322,13 +318,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - DelegationValidationError( - #[source] - delegation::ValidationError< - <<::Builder as Checkable>::Hierarchy as Prove>::Error, - C, - >, - ), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fa0e9aea..0350aa98 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,9 +3,8 @@ use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, - delegation::{self, condition::Condition, Delegable, ValidationError}, + delegation::{self, condition::Condition}, //, ValidationError}, did::{Did, Verifiable}, - proof::{checkable::Checkable, prove::Prove}, time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; @@ -14,7 +13,8 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, fmt}; +use thiserror::Error; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -132,48 +132,95 @@ impl Payload { Ok(()) } - pub fn check( - self, - proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, + pub fn check( + &self, + proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError<<::Hierarchy as Prove>::Error, C>> + ) -> Result<(), ValidationError> where - A: Delegable, - A::Builder: Clone + Into>, - ::Hierarchy: Clone + Into>, + A: Clone, DID: Clone, + arguments::Named: From, { - let builder_payload: delegation::Payload = self.into(); - builder_payload.check(proofs, now) + let args: arguments::Named = self.ability.clone().into(); + + proofs.into_iter().try_fold(&self.issuer, |iss, proof| { + // FIXME extract step function? + if *iss != proof.audience { + return Err(ValidationError::InvalidSubject.into()); + } + + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::MisalignedIssAud.into()); + } + } + + if SystemTime::from(proof.expiration.clone()) > *now { + return Err(ValidationError::Expired.into()); + } + + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > *now { + return Err(ValidationError::NotYetValid.into()); + } + } + + for predicate in proof.conditions.iter() { + if !predicate.validate(&args) { + return Err(ValidationError::FailedCondition(predicate.clone())); + } + } + + Ok(&proof.issuer) + })?; + + Ok(()) } } +/// Delegation validation errors. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ValidationError { + #[error("The subject of the delegation is invalid")] + InvalidSubject, + + #[error("The issuer and audience of the delegation are misaligned")] + MisalignedIssAud, + + #[error("The delegation has expired")] + Expired, + + #[error("The delegation is not yet valid")] + NotYetValid, + + #[error("The delegation failed a condition: {0:?}")] + FailedCondition(C), +} + impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> - for delegation::Payload -{ - fn from(inv_payload: Payload) -> Self { - delegation::Payload { - issuer: inv_payload.issuer, - subject: inv_payload.subject.clone(), - audience: inv_payload.audience.unwrap_or(inv_payload.subject), - - ability_builder: A::Builder::from(inv_payload.ability), - conditions: vec![], - - metadata: inv_payload.metadata, - nonce: inv_payload.nonce, - - not_before: None, - expiration: inv_payload - .expiration - .unwrap_or(Timestamp::postel(SystemTime::now())), - } - } -} +// impl From> for delegation::Payload { +// fn from(inv_payload: Payload) -> Self { +// delegation::Payload { +// issuer: inv_payload.issuer, +// subject: Some(inv_payload.subject.clone()), +// audience: inv_payload.audience.unwrap_or(inv_payload.subject), +// +// conditions: vec![], +// +// metadata: inv_payload.metadata, +// nonce: inv_payload.nonce, +// +// not_before: None, +// expiration: inv_payload +// .expiration +// .unwrap_or(Timestamp::postel(SystemTime::now())), +// } +// } +// } impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { @@ -408,7 +455,7 @@ impl From> for Ipld { } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where T::Strategy: 'static, DID::Parameters: Clone, diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index 17c9cd0d..e9b351ad 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -4,7 +4,6 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Pending, ipld, }; @@ -18,7 +17,7 @@ use thiserror::Error; /// A trait for [`Delegable`]s that can be deferred (by promises). /// /// FIXME exmaples -pub trait Resolvable: Delegable { +pub trait Resolvable: Sized + ParseAbility + ToCommand { /// The promise type that resolves to `Self`. /// /// Note that this may be a more complex type than the promise selector @@ -30,18 +29,17 @@ pub trait Resolvable: Delegable { + ParsePromised // TryFrom> + Into>; - fn into_promised(self) -> Self::Promised - where - ::PromisedArgsError: fmt::Debug, - { - // FIXME In no way efficient... override where possible, or just cut the impl - let builder = Self::Builder::from(self); - let cmd = &builder.to_command(); - let named_ipld: arguments::Named = builder.into(); - let promised_ipld: arguments::Named = named_ipld.into(); - ::Promised::try_parse_promised(cmd, promised_ipld) - .expect("promise to always be possible from a ready ability") - } + // fn into_promised(self) -> Self::Promised + // where + // ::PromisedArgsError: fmt::Debug, + // { + // // FIXME In no way efficient... override where possible, or just cut the impl + // let cmd = &builder.to_command(); + // let named_ipld: arguments::Named = builder.into(); + // let promised_ipld: arguments::Named = named_ipld.into(); + // ::Promised::try_parse_promised(cmd, promised_ipld) + // .expect("promise to always be possible from a ready ability") + // } /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> @@ -55,15 +53,11 @@ pub trait Resolvable: Delegable { reason: ResolveError::StillWaiting(pending), }), Ok(named) => { - let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) - .map_err(|_reason| CantResolve { - promised: promised.clone(), + ParseAbility::try_parse(&promised.to_command(), named).map_err(|_reason| { + CantResolve { + promised, reason: ResolveError::ConversionError, - })?; - - builder.try_into().map_err(|_reason| CantResolve { - promised, - reason: ResolveError::ConversionError, + } }) } } @@ -82,32 +76,6 @@ pub trait Resolvable: Delegable { set }) } - - fn try_to_builder( - promised: Self::Promised, - ) -> Result< - Self::Builder, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, - > { - let cmd = promised.to_command(); - let ipld_promise: arguments::Named = promised.into(); - - let named: arguments::Named = - ipld_promise - .into_iter() - .fold(arguments::Named::new(), |mut acc, (k, v)| { - match v.try_into() { - Err(_) => (), - Ok(ipld) => { - acc.insert(k, ipld); // i.e. forget any promises - } - } - - acc - }); - - Self::Builder::try_parse(&cmd, named) - } } #[derive(Error, Clone)] @@ -119,7 +87,6 @@ pub struct CantResolve { impl fmt::Debug for CantResolve where S::Promised: fmt::Debug, - <::Builder as ParseAbility>::ArgsErr: fmt::Debug, Pending: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/lib.rs b/src/lib.rs index 694c91aa..32a40fdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod proof; +//pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs index 82092cb5..de58a8ff 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,12 +1,12 @@ //! Proof chains, checking, and utilities. -pub mod checkable; -pub mod error; -pub mod parentful; -pub mod parentless; -pub mod parents; -pub mod prove; -pub mod same; +// pub mod checkable; +// pub mod error; +// pub mod parentful; +// pub mod parentless; +// pub mod parents; +// pub mod prove; +// pub mod same; // NOTE must remain *un*exported! -pub(super) mod internal; +// pub(super) mod internal; diff --git a/src/url.rs b/src/url.rs index efa2f68f..d23b8efb 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,6 +1,6 @@ //! URL utilities. -use crate::proof::same::CheckSame; +// use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -50,17 +50,17 @@ impl fmt::Display for Newtype { } } -impl CheckSame for Newtype { - type Error = (); - - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - if self == other { - Ok(()) - } else { - Err(()) - } - } -} +// impl CheckSame for Newtype { +// type Error = (); +// +// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { +// if self == other { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } impl From for Ipld { fn from(newtype: Newtype) -> Self { From 56668999bcb5becbeefa0eb3898c1c349ae03488 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 27 Feb 2024 12:26:32 -0800 Subject: [PATCH 163/234] Working on policy DSL --- src/delegation.rs | 1 + src/delegation/condition.rs | 1 + src/delegation/policy.rs | 2 + src/delegation/policy/frontend.rs | 62 +++++ src/delegation/policy/ir.rs | 379 ++++++++++++++++++++++++++++++ 5 files changed, 445 insertions(+) create mode 100644 src/delegation/policy.rs create mode 100644 src/delegation/policy/frontend.rs create mode 100644 src/delegation/policy/ir.rs diff --git a/src/delegation.rs b/src/delegation.rs index e4c077db..543ce82b 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -13,6 +13,7 @@ //! - [`store`] is an interface for caching [`Delegation`]s. pub mod condition; +pub mod policy; pub mod store; mod agent; diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index f9594bbc..868f90b9 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -27,6 +27,7 @@ pub use traits::Condition; use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeMap; /// The union of the common [`Condition`]s that ship directly with this library. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs new file mode 100644 index 00000000..6f111432 --- /dev/null +++ b/src/delegation/policy.rs @@ -0,0 +1,2 @@ +pub mod frontend; +pub mod ir; diff --git a/src/delegation/policy/frontend.rs b/src/delegation/policy/frontend.rs new file mode 100644 index 00000000..4ac89bb9 --- /dev/null +++ b/src/delegation/policy/frontend.rs @@ -0,0 +1,62 @@ +use super::ir; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Term { + // Leaves + Args, // $ + Literal(Ipld), + Variable(Variable), + + Selector(Selector), + + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), + GreaterThan(Value, Value), + GreaterOrEqual(Value, Value), + LessThan(Value, Value), + LessOrEqual(Value, Value), + + // String Matcher + Glob(Value, String), + + // Existential Quantification + Exists(Variable, Collection), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(String); // ?x + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), + Variable(Variable), + Selector(Selector), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Selector(Vec); // .foo.bar[].baz + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Index { + This, + // RecDesend, // .. + FlattenAll, // .[] + Index(usize), // .[2] + Key(String), // .key +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Value { + Literal(Ipld), + Variable(Variable), + ImplicitBind(Selector), +} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs new file mode 100644 index 00000000..fe1ce03b --- /dev/null +++ b/src/delegation/policy/ir.rs @@ -0,0 +1,379 @@ +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Term { + // Leaves + Args, // $ + Literal(Ipld), + Stream(Stream), + + Selector(Selector), + + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), + GreaterThan(Value, Value), + GreaterOrEqual(Value, Value), + LessThan(Value, Value), + LessOrEqual(Value, Value), + + // String Matcher + Glob(Value, String), + + // Existential Quantification + Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(String); // ?x + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), + Variable(Variable), + Selector(Selector), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Selector(Vec); // .foo.bar[].baz + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PathSegment { + This, // . + // RecDesend, // .. + FlattenAll, // .[] --> creates an Every stream + Index(usize), // .[2] + Key(String), // .key +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Value { + Literal(Ipld), + Variable(Variable), +} + +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub Struct EveryStream(Vec); +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct SomeStream(Vec); + +pub fn glob(input: String, pattern: String) -> bool { + let mut input = input.chars(); + let mut pattern = pattern.chars(); + + loop { + match (input.next(), pattern.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } + } + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { + return true; + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Stream { + Every(Vec), // "All or nothing" + Some(Vec), +} + +pub struct EveryStream(Vec); +pub struct SomeStream(Vec); + +pub trait Apply { + fn apply(&self, other: &T, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool; +} + +impl Apply for Ipld { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + if f(self, other) { + ( + Stream::Every(vec![self.clone()]), + Stream::Every(vec![other.clone()]), + ) + } else { + (Stream::Every(vec![]), Stream::Every(vec![])) + } + } +} + +impl Apply for Ipld { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut y_results = vec![]; + + for y in other.0.iter() { + if f(self, y) { + y_results.push(y.clone()); + } else { + y_results = vec![]; + break; + } + } + + if y_results.is_empty() { + (Stream::Every(vec![]), Stream::Every(vec![])) + } else { + (Stream::Every(vec![self.clone()]), Stream::Every(y_results)) + } + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + + for x in self.0.iter() { + if f(x, other) { + x_results.push(x.clone()); + } else { + x_results = vec![]; + break; + } + } + + if x_results.is_empty() { + (Stream::Every(vec![]), Stream::Every(vec![])) + } else { + (Stream::Every(x_results), Stream::Every(vec![other.clone()])) + } + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results = vec![]; + break; + } + } + } + + (Stream::Every(x_results), Stream::Every(y_results)) + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results.push(y.clone()); + break; + } + } + } + + (Stream::Every(x_results), Stream::Some(y_results)) + } +} + +impl Apply for SomeStream { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results.push(y.clone()); + break; + } + } + } + + (Stream::Some(x_results), Stream::Every(y_results)) + } +} + +impl Apply for SomeStream { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } + } + } + + (Stream::Some(x_results), Stream::Some(y_results)) + } +} + +impl Apply for Stream { + fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match self { + Stream::Every(xs) => match other { + Stream::Every(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + Stream::Some(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + }, + + Stream::Some(xs) => match other { + Stream::Every(ys) => SomeStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + Stream::Some(ys) => SomeStream(xs.clone()).apply(&SomeStream(ys.clone()), f), + }, + } + } +} + +impl Stream { + /// Call like stream.apply(other_stream, |x, y| x == y) + pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match self { + Stream::Every(xs) => match other { + Stream::Every(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results = vec![]; + break; + } + } + } + + (Stream::Every(x_results), Stream::Every(y_results)) + } + Stream::Some(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + break; + } + } + } + + if &Stream::Every(x_results.clone()) == self { + (Stream::Every(x_results), Stream::Some(y_results)) + } else { + (Stream::Every(vec![]), Stream::Some(y_results)) + } + } + }, + + Stream::Some(xs) => match other { + Stream::Every(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(x.clone()); + } else { + x_results.push(x.clone()); + y_results = vec![]; + break; + } + } + } + + (Stream::Some(x_results), Stream::Every(y_results)) + } + Stream::Some(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } + } + } + + (Stream::Some(x_results), Stream::Some(y_results)) + } + }, + } + } +} From d1fbad78a320182cfa64577b5719853012b37578 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 27 Feb 2024 17:10:52 -0800 Subject: [PATCH 164/234] Big chunk through the interpreter --- src/delegation/policy.rs | 1 + src/delegation/policy/interpreter.rs | 128 ++++++++++ src/delegation/policy/ir.rs | 337 ++++++++++++++++++--------- 3 files changed, 350 insertions(+), 116 deletions(-) create mode 100644 src/delegation/policy/interpreter.rs diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 6f111432..7be6d057 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,2 +1,3 @@ pub mod frontend; +pub mod interpreter; pub mod ir; diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs new file mode 100644 index 00000000..b98b471c --- /dev/null +++ b/src/delegation/policy/interpreter.rs @@ -0,0 +1,128 @@ +use super::ir::*; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +// [".[]", "$", ?x] +// [".[]", "$", ?y] +// ["==", "?x", "?y"] +// ["==", "?y", "?x"] + +// Register machine +// { +// ports: { +// "?a": Stream<>, +// "?b": Stream<>, +// } +// } + +#[derive(Debug, Clone, PartialEq)] +pub struct Machine<'a> { + pub ports: BTreeMap<&'a str, Stream>, + pub program: BTreeMap<&'a str, Statement>, +} + +pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { + // run to exhaustion + loop { + if let Ok(next) = run_once(&machine) { + if next == &machine { + return machine; + } + } else { + panic!("failed some step"); + } + } +} + +pub fn run_once<'a>(machine: &'a Machine<'a>) -> Result<&'a Machine<'a>, ()> { + let mut ports = machine.ports.clone(); + let mut program = machine.program.clone(); + + // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom + // or at least that's one startegy + program + .clone() + .iter() + .try_fold((), |acc, (idx, statement)| { + // FIXME change from map to vec + match statement { + Statement::Glob(value, pattern) => value + .better_apply(&pattern, &glob) + .map(|_| program.remove(idx)), + Statement::Equal(left, right) => left + .better_apply(right, PartialEq::eq) + .map(|_| program.remove(idx)), + Statement::GreaterThan(left, right) => left + .better_apply(right, PartialEq::gt) + .map(|_| program.remove(idx)), + Statement::LessThan(left, right) => left + .better_apply(right, PartialEq::lt) + .map(|_| program.remove(idx)), + } + // Statement::Equal(left, right) => match (left, right) { + // (Value::Literal(left), Value::Literal(right)) => { + // if left == right { + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // (Value::Literal(left), Value::Variable(Variable(var_id))) => { + // if let Some(stream) = ports.get(var_id.as_str()) { + // let updated = left.apply(stream, PartialEq::eq); + // if updated.0.is_empty() { + // return Err(()); + // } + + // ports.insert(var_id.as_str(), updated.0); + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // (Value::Variable(Variable(var_id)), Value::Literal(right)) => { + // if let Some(stream) = ports.get(var_id.as_str()) { + // let updated = left.apply(stream, PartialEq::eq); + // if updated.0.is_empty() { + // return Err(()); + // } + + // ports.insert(var_id.as_str(), updated.0); + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // // (Value::Variable(left), Value::Literal(right)) => { + // // if let Some(stream) = ports.get(left.as_str()) { + // // let updated = stream.apply(&Ipld::String(right), &equal); + // // ports.insert(left.as_str(), updated.0); + // // } + + // // program.remove(name); + // // } + // // (Value::Variable(left), Value::Variable(right)) => { + // // if let Some(stream) = ports.get(left.as_str()) { + // // let updated = stream.apply(&Ipld::String(right.clone()), &equal); + // // ports.insert(left.as_str(), updated.0); + // // // FIXME UPDATE BOTH + // // } + + // // program.remove(name); + // // } + // _ => todo!(), + // }, + // _ => todo!(), + }) + .map(|_| &machine) +} + +// [".[]", "$", ?x] +// [".[]", "$", ?y] +// +// these will run to exhaustion? +// [".bar", "?x", "?y"] +// [".baz", "?y", "?x"] diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index fe1ce03b..1e8c9e19 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -9,7 +9,7 @@ pub enum Term { Literal(Ipld), Stream(Stream), - Selector(Selector), + Selector(Selector), // NOTE the IR version doens't inline the results // Connectives Not(Box), @@ -17,21 +17,39 @@ pub enum Term { Or(Vec), // Comparison - Equal(Value, Value), + Equal(Value, Value), // AKA unification GreaterThan(Value, Value), - GreaterOrEqual(Value, Value), LessThan(Value, Value), - LessOrEqual(Value, Value), // String Matcher - Glob(Value, String), + Glob(Value, Value), // Existential Quantification + Forall(Variable, Collection), // ∀x ∈ xs Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Variable(String); // ?x +pub enum Statement { + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), // AKA unification + GreaterThan(Value, Value), + LessThan(Value, Value), + + // String Matcher + Glob(Value, Value), + + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(pub String); // ?x #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Collection { @@ -42,7 +60,7 @@ pub enum Collection { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(Vec); // .foo.bar[].baz +pub struct Selector(pub Vec); // .foo.bar[].baz #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { @@ -59,38 +77,35 @@ pub enum Value { Variable(Variable), } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub Struct EveryStream(Vec); -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct SomeStream(Vec); - -pub fn glob(input: String, pattern: String) -> bool { - let mut input = input.chars(); - let mut pattern = pattern.chars(); - - loop { - match (input.next(), pattern.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; +pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { + if let (Ipld::String(s), Ipld::String(pat)) = (input, pattern) { + let mut input = s.chars(); + let mut pattern = pat.chars(); // Ugly + + loop { + match (input.next(), pattern.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { return true; } } - (None, None) => { - return true; - } } } + panic!("FIXME"); } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -99,13 +114,34 @@ pub enum Stream { Some(Vec), } +impl Stream { + pub fn is_empty(&self) -> bool { + match self { + Stream::Every(xs) => xs.is_empty(), + Stream::Some(xs) => xs.is_empty(), + } + } +} + pub struct EveryStream(Vec); pub struct SomeStream(Vec); pub trait Apply { + // FIXME -> Option<(Stream, Stream)>? fn apply(&self, other: &T, f: F) -> (Stream, Stream) where F: Fn(&Ipld, &Ipld) -> bool; + + fn better_apply(&self, other: &T, f: F) -> Result<(Stream, Stream), ()> + where + F: Fn(&Ipld, &Ipld) -> bool, + { + if self.apply(other, f).0.is_empty() { + Err(()) + } else { + Ok((self, other)) + } + } } impl Apply for Ipld { @@ -172,6 +208,43 @@ impl Apply for EveryStream { } } +impl Apply for SomeStream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + + for x in self.0.iter() { + if f(x, other) { + x_results.push(x.clone()); + } + } + + (Stream::Some(x_results), Stream::Every(vec![other.clone()])) + } +} + +impl Apply for Ipld { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut y_results = vec![]; + + for y in other.0.iter() { + if f(self, y) { + y_results.push(y.clone()); + } else { + y_results = vec![]; + break; + } + } + + (Stream::Every(vec![self.clone()]), Stream::Some(y_results)) + } +} + impl Apply for EveryStream { fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) where @@ -197,6 +270,7 @@ impl Apply for EveryStream { } } +// FIXME impl Apply for EveryStream { fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) where @@ -287,93 +361,124 @@ impl Apply for Stream { } } -impl Stream { - /// Call like stream.apply(other_stream, |x, y| x == y) - pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) +impl Apply for Stream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) where F: Fn(&Ipld, &Ipld) -> bool, { - match self { - Stream::Every(xs) => match other { - Stream::Every(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results = vec![]; - break; - } - } - } - - (Stream::Every(x_results), Stream::Every(y_results)) - } - Stream::Some(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - break; - } - } - } - - if &Stream::Every(x_results.clone()) == self { - (Stream::Every(x_results), Stream::Some(y_results)) - } else { - (Stream::Every(vec![]), Stream::Some(y_results)) - } - } - }, - - Stream::Some(xs) => match other { - Stream::Every(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(x.clone()); - } else { - x_results.push(x.clone()); - y_results = vec![]; - break; - } - } - } + todo!() + // match self { + // Stream::Every(xs) => EveryStream(xs).apply(&other, f) + // Stream::Some(xs) => SomeStream(xs).apply(&other, f), + // } + } +} - (Stream::Some(x_results), Stream::Every(y_results)) - } - Stream::Some(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } - } - } +impl Apply for Ipld { + fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + todo!() + } +} - (Stream::Some(x_results), Stream::Some(y_results)) - } - }, - } +impl Apply for Value { + fn apply(&self, other: &Value, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + todo!() } } + +// impl Stream { +// /// Call like stream.apply(other_stream, |x, y| x == y) +// pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) +// where +// F: Fn(&Ipld, &Ipld) -> bool, +// { +// match self { +// Stream::Every(xs) => match other { +// Stream::Every(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } else { +// x_results = vec![]; +// y_results = vec![]; +// break; +// } +// } +// } +// +// (Stream::Every(x_results), Stream::Every(y_results)) +// } +// Stream::Some(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } else { +// x_results = vec![]; +// break; +// } +// } +// } +// +// if &Stream::Every(x_results.clone()) == self { +// (Stream::Every(x_results), Stream::Some(y_results)) +// } else { +// (Stream::Every(vec![]), Stream::Some(y_results)) +// } +// } +// }, +// +// Stream::Some(xs) => match other { +// Stream::Every(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(x.clone()); +// } else { +// x_results.push(x.clone()); +// y_results = vec![]; +// break; +// } +// } +// } +// +// (Stream::Some(x_results), Stream::Every(y_results)) +// } +// Stream::Some(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } +// } +// } +// +// (Stream::Some(x_results), Stream::Some(y_results)) +// } +// }, +// } +// } +// } From ffd4f1a17a4d48efc88ee968e7ee3b5cd2a3e20e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 00:04:13 -0800 Subject: [PATCH 165/234] Big chunk of the way through the first pass on the interpreter --- src/delegation/policy/interpreter.rs | 420 +++++++++++++++++++++------ src/delegation/policy/ir.rs | 410 +++----------------------- 2 files changed, 379 insertions(+), 451 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index b98b471c..4059b1a7 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -1,7 +1,25 @@ use super::ir::*; +use crate::ability::arguments; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +// [and ["==", ".foo", "?x"] +// [">", "?x", 0] +// [">", "?x", 2] +// ["==", 10, 11] // Fails, so whole thing fails? +// ["or", ["==", ".bar", "?y"] +// [">", "?y", 12] +// ["and", ["<", "?x", 100] +// ["<", "?y", 100] +// ] +// ["every", "?x", "?e"] +// ] +// ["==", 22, "?e"] +// ["some", "?x" "?a"] +// ["==", "?a", [1, 2, "?z", 4]] +// ["==", ["?b", "?c", 20, 30], [10, "?a", 20, 30]] // -> b = 10, c = a, a = c +// ] + // [".[]", "$", ?x] // [".[]", "$", ?y] // ["==", "?x", "?y"] @@ -17,8 +35,10 @@ use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] pub struct Machine<'a> { - pub ports: BTreeMap<&'a str, Stream>, - pub program: BTreeMap<&'a str, Statement>, + pub args: arguments::Named, + pub frames: BTreeMap<&'a str, Stream>, + pub program: Statement, + pub index_counter: usize, } pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { @@ -34,95 +54,317 @@ pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { } } -pub fn run_once<'a>(machine: &'a Machine<'a>) -> Result<&'a Machine<'a>, ()> { - let mut ports = machine.ports.clone(); - let mut program = machine.program.clone(); - +pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy - program - .clone() - .iter() - .try_fold((), |acc, (idx, statement)| { - // FIXME change from map to vec - match statement { - Statement::Glob(value, pattern) => value - .better_apply(&pattern, &glob) - .map(|_| program.remove(idx)), - Statement::Equal(left, right) => left - .better_apply(right, PartialEq::eq) - .map(|_| program.remove(idx)), - Statement::GreaterThan(left, right) => left - .better_apply(right, PartialEq::gt) - .map(|_| program.remove(idx)), - Statement::LessThan(left, right) => left - .better_apply(right, PartialEq::lt) - .map(|_| program.remove(idx)), + match context.program { + Statement::And(left, right) => { + let lhs = Machine { + program: *left, + ..context + }; + + let lhs_result = run(lhs); + + let rhs = Machine { + args: context.args, + frames: lhs_result.frames, + program: *right, + index_counter: lhs_result.index_counter, + }; + + let mut rhs_result = run(rhs); + + if rhs_result.frames.is_empty() { + Err(()) + } else { + Ok(rhs_result) } - // Statement::Equal(left, right) => match (left, right) { - // (Value::Literal(left), Value::Literal(right)) => { - // if left == right { - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // (Value::Literal(left), Value::Variable(Variable(var_id))) => { - // if let Some(stream) = ports.get(var_id.as_str()) { - // let updated = left.apply(stream, PartialEq::eq); - // if updated.0.is_empty() { - // return Err(()); - // } - - // ports.insert(var_id.as_str(), updated.0); - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // (Value::Variable(Variable(var_id)), Value::Literal(right)) => { - // if let Some(stream) = ports.get(var_id.as_str()) { - // let updated = left.apply(stream, PartialEq::eq); - // if updated.0.is_empty() { - // return Err(()); - // } - - // ports.insert(var_id.as_str(), updated.0); - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // // (Value::Variable(left), Value::Literal(right)) => { - // // if let Some(stream) = ports.get(left.as_str()) { - // // let updated = stream.apply(&Ipld::String(right), &equal); - // // ports.insert(left.as_str(), updated.0); - // // } - - // // program.remove(name); - // // } - // // (Value::Variable(left), Value::Variable(right)) => { - // // if let Some(stream) = ports.get(left.as_str()) { - // // let updated = stream.apply(&Ipld::String(right.clone()), &equal); - // // ports.insert(left.as_str(), updated.0); - // // // FIXME UPDATE BOTH - // // } - - // // program.remove(name); - // // } - // _ => todo!(), - // }, - // _ => todo!(), - }) - .map(|_| &machine) + } + Statement::Or(left, right) => { + let lhs = Machine { + program: *left, + ..context + }; + + let rhs = Machine { + program: *right, + ..context + }; + + let lhs_result = run(lhs); + let rhs_result = run(rhs); + todo!() // merge_and_dedup(lhs_result, rhs_result); + } + Statement::Not(statement) => { + let next = Machine { + args: context.args, + frames: context.frames, + program: *statement, + index_counter: context.index_counter, + }; + + let not_results = run(next); + + todo!(); // remove all not_results from context.frames + } + Statement::Exists(var, collection) => { + let xs: Vec = match collection { + Collection::Array(vec) => vec, + Collection::Map(map) => map.values().cloned().collect(), + }; + + context.frames.insert(var.0.as_str(), Stream::Some(xs)); + Ok(context) + } + Statement::Forall(var, collection) => { + let xs: Vec = match collection { + Collection::Array(vec) => vec, + Collection::Map(map) => map.values().cloned().collect(), + }; + + context.frames.insert(var.0.as_str(), Stream::Every(xs)); + Ok(context) + } + Statement::Equal(left, right) => context + .apply(&left, &right, |a, b| a == b) + .map(|()| context), + Statement::GreaterThan(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a > b, + (Ipld::Float(a), Ipld::Float(b)) => a > b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) > *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a > (*b as f64), + _ => false, + }) + .map(|()| context), + Statement::LessThan(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a < b, + (Ipld::Float(a), Ipld::Float(b)) => a < b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) < *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a < (*b as f64), + _ => false, + }) + .map(|()| context), + Statement::GreaterThanOrEqual(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a >= b, + (Ipld::Float(a), Ipld::Float(b)) => a >= b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) >= *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a >= (*b as f64), + _ => false, + }) + .map(|()| context), + + Statement::LessThanOrEqual(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a <= b, + (Ipld::Float(a), Ipld::Float(b)) => a <= b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) <= *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a <= (*b as f64), + _ => false, + }) + .map(|()| context), + + Statement::Glob(left, right) => context + .apply(&left, &right, |a, b| glob(a, b)) + .map(|()| context), + Statement::Select(selector, target, var) => match target { + SelectorValue::Args => { + let ipld = Ipld::Map(context.args.0); + let selected = select(selector, ipld)?; + + context + .frames + .insert(var.0.as_str(), Stream::Every(vec![selected])); + + Ok(context) + } + SelectorValue::Literal(ipld) => { + let ipld = select(selector, ipld)?; + + context + .frames + .insert(var.0.as_str(), Stream::Every(vec![ipld])); + + Ok(context) + } + SelectorValue::Variable(var_id) => { + let current = context + .frames + .get(var_id.0.as_str()) + .unwrap_or(&Stream::Every(vec![])); + + let result: Result, ()> = current + .to_vec() + .iter() + .map(|ipld| select(selector.clone(), ipld.clone())) + .collect(); + + let updated = result?; + current.map(|_| updated); + + Ok(context) + } + }, + } } -// [".[]", "$", ?x] -// [".[]", "$", ?y] -// -// these will run to exhaustion? -// [".bar", "?x", "?y"] -// [".baz", "?y", "?x"] +pub fn select(selector: Selector, on: Ipld) -> Result { + let results: Vec<&Ipld> = + selector + .0 + .iter() + .try_fold(vec![&on], |mut ipld_stream, segment| match segment { + PathSegment::This => Ok(ipld_stream), + PathSegment::Index(i) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + if let Some(ipld) = vec.get(*i) { + acc.push(ipld); + Ok(acc) + } else { + Err(()) + } + } + _ => Err(()), + }) + } + PathSegment::Key(key) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::Map(map) => { + if let Some(ipld) = map.get(key) { + acc.push(ipld); + Ok(acc) + } else { + Err(()) + } + } + _ => Err(()), + }) + } + PathSegment::FlattenAll => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + acc.extend(vec); + Ok(acc) + } + _ => Err(()), + }) + } + })?; + + match results.as_slice() { + [ipld] => Ok(*ipld.clone()), + vec => Ok(Ipld::List( + vec.into_iter().map(|ipld| *ipld.clone()).collect(), + )), + } +} + +// pub fn select_step(segment: PathSegment, ipld: Ipld) -> Result { +// match segment { +// PathSegment::This => Ok(ipld), +// PathSegment::Index(i) => match ipld { +// Ipld::List(vec) => vec.get(i).cloned().ok_or(()), +// _ => Err(()), +// }, +// PathSegment::Key(key) => match ipld { +// Ipld::Map(map) => map.get(&key).cloned().ok_or(()), +// _ => Err(()), +// }, +// PathSegment::FlattenAll => todo!(), +// } +// } + +impl<'a> Machine<'a> { + pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match lhs { + Value::Literal(left_ipld) => match rhs { + Value::Literal(right_ipld) => { + if f(left_ipld, right_ipld) { + Ok(()) + } else { + Err(()) + } + } + Value::Variable(var_id) => { + let key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(key) { + let updated = stream + .map(|vec| vec.into_iter().filter(|ipld| f(left_ipld, ipld)).collect()); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(key, updated); + + Ok(()) + } else { + Err(()) + } + } + }, + Value::Variable(var_id) => { + let lhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(lhs_key) { + match rhs { + Value::Literal(right_ipld) => { + let updated = stream.map(|vec| { + vec.into_iter().filter(|ipld| f(ipld, right_ipld)).collect() + }); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(lhs_key, updated); + Ok(()) + } + Value::Variable(var_id) => { + let rhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(rhs_key) { + let updated = stream.map(|vec| { + vec.into_iter().filter(|ipld| f(ipld, ipld)).collect() + }); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(lhs_key, updated); + + Ok(()) + } else { + Err(()) + } + } + } + } else { + // FIXME not nessesarily! You may need to create new entires + Err(()) + } + } + } + } + + pub fn unify(&mut self, lhs: &Value, rhs: &Value) -> Result { + self.apply(lhs, rhs, |a, b| { + todo!(); + todo!(); + // FIXME pattern matching etc + }) + .map(|()| rhs.clone()) + } +} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index 1e8c9e19..e9d0a488 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -29,23 +29,33 @@ pub enum Term { Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some } +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Statement { // Connectives Not(Box), - And(Vec), - Or(Vec), + And(Box, Box), + Or(Box, Box), + + // Forall: unpack and unify size before and after + // Exists genrates more than one frame (unpacks an array) instead of one + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs // Comparison - Equal(Value, Value), // AKA unification + Equal(Value, Value), // AKA unification // FIXME value can also be a selector GreaterThan(Value, Value), + GreaterThanOrEqual(Value, Value), LessThan(Value, Value), + LessThanOrEqual(Value, Value), + // >= and <= are probably good for efficiency // String Matcher Glob(Value, Value), - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs + // Select from ?foo + Select(Selector, SelectorValue, Variable), // .foo.bar[].baz } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -55,20 +65,30 @@ pub struct Variable(pub String); // ?x pub enum Collection { Array(Vec), Map(BTreeMap), - Variable(Variable), - Selector(Selector), + // NOTE The below can always be desugared, esp because this is now only used with forall/exists + // Variable(Variable), ] + // Selector(Selector), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Selector(pub Vec); // .foo.bar[].baz +// FIXME need an IR representation of $args + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { This, // . // RecDesend, // .. - FlattenAll, // .[] --> creates an Every stream - Index(usize), // .[2] - Key(String), // .key + Index(usize), // [2] + Key(String), // ["key"] (or .key) + FlattenAll, // [] --> creates an Array +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorValue { + Args, + Literal(Ipld), + Variable(Variable), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -115,370 +135,36 @@ pub enum Stream { } impl Stream { - pub fn is_empty(&self) -> bool { + pub fn to_vec(self) -> Vec { match self { - Stream::Every(xs) => xs.is_empty(), - Stream::Some(xs) => xs.is_empty(), - } - } -} - -pub struct EveryStream(Vec); -pub struct SomeStream(Vec); - -pub trait Apply { - // FIXME -> Option<(Stream, Stream)>? - fn apply(&self, other: &T, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool; - - fn better_apply(&self, other: &T, f: F) -> Result<(Stream, Stream), ()> - where - F: Fn(&Ipld, &Ipld) -> bool, - { - if self.apply(other, f).0.is_empty() { - Err(()) - } else { - Ok((self, other)) - } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - if f(self, other) { - ( - Stream::Every(vec![self.clone()]), - Stream::Every(vec![other.clone()]), - ) - } else { - (Stream::Every(vec![]), Stream::Every(vec![])) - } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut y_results = vec![]; - - for y in other.0.iter() { - if f(self, y) { - y_results.push(y.clone()); - } else { - y_results = vec![]; - break; - } - } - - if y_results.is_empty() { - (Stream::Every(vec![]), Stream::Every(vec![])) - } else { - (Stream::Every(vec![self.clone()]), Stream::Every(y_results)) - } - } -} - -impl Apply for EveryStream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - - for x in self.0.iter() { - if f(x, other) { - x_results.push(x.clone()); - } else { - x_results = vec![]; - break; - } - } - - if x_results.is_empty() { - (Stream::Every(vec![]), Stream::Every(vec![])) - } else { - (Stream::Every(x_results), Stream::Every(vec![other.clone()])) + Stream::Every(xs) => xs, + Stream::Some(xs) => xs, } } -} - -impl Apply for SomeStream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - - for x in self.0.iter() { - if f(x, other) { - x_results.push(x.clone()); - } - } - - (Stream::Some(x_results), Stream::Every(vec![other.clone()])) - } -} -impl Apply for Ipld { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut y_results = vec![]; - - for y in other.0.iter() { - if f(self, y) { - y_results.push(y.clone()); - } else { - y_results = vec![]; - break; - } - } - - (Stream::Every(vec![self.clone()]), Stream::Some(y_results)) - } -} - -impl Apply for EveryStream { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results = vec![]; - break; - } + pub fn map(self, f: impl Fn(Vec) -> Vec) -> Stream { + match self { + Stream::Every(xs) => { + let updated = f(xs); + Stream::Every(updated) } - } - - (Stream::Every(x_results), Stream::Every(y_results)) - } -} - -// FIXME -impl Apply for EveryStream { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results.push(y.clone()); - break; - } + Stream::Some(xs) => { + let updated = f(xs); + Stream::Some(updated) } } - - (Stream::Every(x_results), Stream::Some(y_results)) } -} - -impl Apply for SomeStream { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results.push(y.clone()); - break; - } - } - } - - (Stream::Some(x_results), Stream::Every(y_results)) - } -} - -impl Apply for SomeStream { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } - } - } - - (Stream::Some(x_results), Stream::Some(y_results)) - } -} - -impl Apply for Stream { - fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { + pub fn is_empty(&self) -> bool { match self { - Stream::Every(xs) => match other { - Stream::Every(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - Stream::Some(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - }, - - Stream::Some(xs) => match other { - Stream::Every(ys) => SomeStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - Stream::Some(ys) => SomeStream(xs.clone()).apply(&SomeStream(ys.clone()), f), - }, + Stream::Every(xs) => xs.is_empty(), + Stream::Some(xs) => xs.is_empty(), } } } -impl Apply for Stream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - // match self { - // Stream::Every(xs) => EveryStream(xs).apply(&other, f) - // Stream::Some(xs) => SomeStream(xs).apply(&other, f), - // } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - } -} - -impl Apply for Value { - fn apply(&self, other: &Value, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - } -} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EveryStream(Vec); -// impl Stream { -// /// Call like stream.apply(other_stream, |x, y| x == y) -// pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) -// where -// F: Fn(&Ipld, &Ipld) -> bool, -// { -// match self { -// Stream::Every(xs) => match other { -// Stream::Every(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } else { -// x_results = vec![]; -// y_results = vec![]; -// break; -// } -// } -// } -// -// (Stream::Every(x_results), Stream::Every(y_results)) -// } -// Stream::Some(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } else { -// x_results = vec![]; -// break; -// } -// } -// } -// -// if &Stream::Every(x_results.clone()) == self { -// (Stream::Every(x_results), Stream::Some(y_results)) -// } else { -// (Stream::Every(vec![]), Stream::Some(y_results)) -// } -// } -// }, -// -// Stream::Some(xs) => match other { -// Stream::Every(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(x.clone()); -// } else { -// x_results.push(x.clone()); -// y_results = vec![]; -// break; -// } -// } -// } -// -// (Stream::Some(x_results), Stream::Every(y_results)) -// } -// Stream::Some(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } -// } -// } -// -// (Stream::Some(x_results), Stream::Some(y_results)) -// } -// }, -// } -// } -// } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SomeStream(Vec); From 00c980642bcf27bcf9d4b3a6e5fcd0d402b209e5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 00:44:57 -0800 Subject: [PATCH 166/234] Switched to maps for better indexing --- src/delegation/policy/interpreter.rs | 249 +++++++++++++++------------ src/delegation/policy/ir.rs | 18 +- 2 files changed, 148 insertions(+), 119 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 4059b1a7..8267900c 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -34,34 +34,35 @@ use std::collections::BTreeMap; // } #[derive(Debug, Clone, PartialEq)] -pub struct Machine<'a> { +pub struct Machine { pub args: arguments::Named, - pub frames: BTreeMap<&'a str, Stream>, + pub frames: BTreeMap, pub program: Statement, pub index_counter: usize, } -pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { +pub fn run(machine: Machine) -> Machine { // run to exhaustion - loop { - if let Ok(next) = run_once(&machine) { - if next == &machine { - return machine; - } - } else { - panic!("failed some step"); - } - } + // loop { + // if let Ok(next) = run_once(&machine) { + // if next == &machine { + // return machine; + // } + // } else { + // panic!("failed some step"); + // } + // } + todo!() } -pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { +pub fn run_once(mut context: Machine) -> Result { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy - match context.program { + match context.clone().program { Statement::And(left, right) => { let lhs = Machine { program: *left, - ..context + ..context.clone() }; let lhs_result = run(lhs); @@ -73,7 +74,7 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { index_counter: lhs_result.index_counter, }; - let mut rhs_result = run(rhs); + let rhs_result = run(rhs); if rhs_result.frames.is_empty() { Err(()) @@ -84,7 +85,7 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { Statement::Or(left, right) => { let lhs = Machine { program: *left, - ..context + ..context.clone() }; let rhs = Machine { @@ -109,21 +110,39 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { todo!(); // remove all not_results from context.frames } Statement::Exists(var, collection) => { - let xs: Vec = match collection { - Collection::Array(vec) => vec, - Collection::Map(map) => map.values().cloned().collect(), + let btree: BTreeMap = match collection { + Collection::Array(vec) => vec + .into_iter() + .map(|ipld| (context.next_index(), ipld)) + .collect(), + + Collection::Map(map) => map + .into_iter() + .map(|(_k, ipld)| (context.next_index(), ipld)) + .collect(), }; - context.frames.insert(var.0.as_str(), Stream::Some(xs)); + context.frames.insert(var.0, Stream::Some(btree)); Ok(context) } Statement::Forall(var, collection) => { - let xs: Vec = match collection { - Collection::Array(vec) => vec, - Collection::Map(map) => map.values().cloned().collect(), + let btree: BTreeMap = match collection { + Collection::Array(vec) => vec + .into_iter() + .map(|ipld| (context.next_index(), ipld)) + .collect(), + + Collection::Map(map) => map + .into_iter() + .map(|(_k, ipld)| (context.next_index(), ipld)) + .collect(), }; - context.frames.insert(var.0.as_str(), Stream::Every(xs)); + context.frames.insert(var.0, Stream::Every(btree)); + + // FIXME needs to check that nothing changed + // ...perhaps at the end of the iteration, loop through the streams? + Ok(context) } Statement::Equal(left, right) => context @@ -172,38 +191,45 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { .map(|()| context), Statement::Select(selector, target, var) => match target { SelectorValue::Args => { - let ipld = Ipld::Map(context.args.0); + let ipld = Ipld::Map(context.args.clone().0); let selected = select(selector, ipld)?; + let idx = context.next_index(); context .frames - .insert(var.0.as_str(), Stream::Every(vec![selected])); + .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, selected)]))); Ok(context) } SelectorValue::Literal(ipld) => { let ipld = select(selector, ipld)?; + let idx = context.next_index(); context .frames - .insert(var.0.as_str(), Stream::Every(vec![ipld])); + .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, ipld)]))); Ok(context) } SelectorValue::Variable(var_id) => { let current = context .frames - .get(var_id.0.as_str()) - .unwrap_or(&Stream::Every(vec![])); - - let result: Result, ()> = current - .to_vec() - .iter() - .map(|ipld| select(selector.clone(), ipld.clone())) + .get(&var_id.0) + .cloned() + .unwrap_or(Stream::Every(BTreeMap::new())); + + let result: Result, ()> = current + .clone() + .to_btree() + .into_iter() + .map(|(idx, ipld)| select(selector.clone(), ipld).map(|ipld| (idx, ipld))) .collect(); let updated = result?; - current.map(|_| updated); + + context + .frames + .insert(var.0, current.map(|_| updated.clone())); Ok(context) } @@ -212,79 +238,69 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { } pub fn select(selector: Selector, on: Ipld) -> Result { - let results: Vec<&Ipld> = - selector - .0 - .iter() - .try_fold(vec![&on], |mut ipld_stream, segment| match segment { - PathSegment::This => Ok(ipld_stream), - PathSegment::Index(i) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - if let Some(ipld) = vec.get(*i) { - acc.push(ipld); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::Key(key) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::Map(map) => { - if let Some(ipld) = map.get(key) { - acc.push(ipld); - Ok(acc) - } else { - Err(()) - } + let results: Vec = selector + .0 + .iter() + .try_fold(vec![on], |ipld_stream, segment| match segment { + PathSegment::This => Ok(ipld_stream), + PathSegment::Index(i) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + if let Some(ipld) = vec.get(*i) { + acc.push(ipld.clone()); + Ok(acc) + } else { + Err(()) } - _ => Err(()), - }) - } - PathSegment::FlattenAll => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - acc.extend(vec); + } + _ => Err(()), + }) + } + PathSegment::Key(key) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::Map(map) => { + if let Some(ipld) = map.get(key) { + acc.push(ipld.clone()); Ok(acc) + } else { + Err(()) } - _ => Err(()), - }) - } - })?; + } + _ => Err(()), + }) + } + PathSegment::FlattenAll => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + acc.extend(vec.clone()); + Ok(acc.iter().cloned().collect()) + } + _ => Err(()), + }) + } + })?; - match results.as_slice() { - [ipld] => Ok(*ipld.clone()), + match &results[..] { + [ipld] => Ok(ipld.clone()), vec => Ok(Ipld::List( - vec.into_iter().map(|ipld| *ipld.clone()).collect(), + vec.into_iter().map(|ipld| ipld.clone()).collect(), )), } } -// pub fn select_step(segment: PathSegment, ipld: Ipld) -> Result { -// match segment { -// PathSegment::This => Ok(ipld), -// PathSegment::Index(i) => match ipld { -// Ipld::List(vec) => vec.get(i).cloned().ok_or(()), -// _ => Err(()), -// }, -// PathSegment::Key(key) => match ipld { -// Ipld::Map(map) => map.get(&key).cloned().ok_or(()), -// _ => Err(()), -// }, -// PathSegment::FlattenAll => todo!(), -// } -// } +impl Machine { + pub fn next_index(&mut self) -> usize { + let prev = self.index_counter; + self.index_counter += 1; + prev + } -impl<'a> Machine<'a> { pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> where F: Fn(&Ipld, &Ipld) -> bool, @@ -299,10 +315,15 @@ impl<'a> Machine<'a> { } } Value::Variable(var_id) => { - let key = var_id.0.as_str(); - if let Some(stream) = self.frames.get(key) { - let updated = stream - .map(|vec| vec.into_iter().filter(|ipld| f(left_ipld, ipld)).collect()); + let key = var_id.0.clone(); + + if let Some(stream) = self.frames.get(&key) { + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(left_ipld, ipld)) + .collect() + }); if updated.is_empty() { return Err(()); @@ -317,33 +338,41 @@ impl<'a> Machine<'a> { } }, Value::Variable(var_id) => { - let lhs_key = var_id.0.as_str(); + let lhs_key = &var_id.0; + if let Some(stream) = self.frames.get(lhs_key) { match rhs { Value::Literal(right_ipld) => { - let updated = stream.map(|vec| { - vec.into_iter().filter(|ipld| f(ipld, right_ipld)).collect() + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(ipld, right_ipld)) + .collect() }); if updated.is_empty() { return Err(()); } - self.frames.insert(lhs_key, updated); + self.frames.insert(lhs_key.clone(), updated); Ok(()) } Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(rhs_key) { - let updated = stream.map(|vec| { - vec.into_iter().filter(|ipld| f(ipld, ipld)).collect() + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(ipld, ipld)) + .collect() }); if updated.is_empty() { return Err(()); } - self.frames.insert(lhs_key, updated); + self.frames.insert(lhs_key.clone(), updated); Ok(()) } else { diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index e9d0a488..d747b6ab 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -130,19 +130,19 @@ pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Stream { - Every(Vec), // "All or nothing" - Some(Vec), + Every(BTreeMap), // "All or nothing" + Some(BTreeMap), } impl Stream { - pub fn to_vec(self) -> Vec { + pub fn to_btree(self) -> BTreeMap { match self { Stream::Every(xs) => xs, Stream::Some(xs) => xs, } } - pub fn map(self, f: impl Fn(Vec) -> Vec) -> Stream { + pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { match self { Stream::Every(xs) => { let updated = f(xs); @@ -163,8 +163,8 @@ impl Stream { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct EveryStream(Vec); - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SomeStream(Vec); +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct EveryStream(V); +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct SomeStream(Vec); From a3b328a7a9d4c4fd740a39cabe6ab0388ecf9dea Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 01:20:55 -0800 Subject: [PATCH 167/234] Saving --- src/delegation/policy/interpreter.rs | 121 +++++++++++++++++++++------ src/delegation/policy/ir.rs | 5 +- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 8267900c..1c66b24b 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -52,10 +52,9 @@ pub fn run(machine: Machine) -> Machine { // panic!("failed some step"); // } // } - todo!() } -pub fn run_once(mut context: Machine) -> Result { +pub fn step(mut context: Machine) -> Result { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy match context.clone().program { @@ -90,24 +89,59 @@ pub fn run_once(mut context: Machine) -> Result { let rhs = Machine { program: *right, - ..context + ..context.clone() }; let lhs_result = run(lhs); let rhs_result = run(rhs); - todo!() // merge_and_dedup(lhs_result, rhs_result); + + let merged_frames = lhs_result + .frames + .into_iter() + .map(|(key, lhs_stream)| { + let rhs_stream = rhs_result + .frames + .get(&key) + .cloned() + .unwrap_or(lhs_stream.clone()); + + let merged = match (lhs_stream, rhs_stream) { + (Stream::Every(lhs), Stream::Every(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Some(lhs), Stream::Some(rhs)) => { + Stream::Some(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Every(lhs), Stream::Some(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Some(lhs), Stream::Every(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + }; + + (key, merged) + }) + .collect(); + + Ok(Machine { + frames: merged_frames, + ..context + }) } Statement::Not(statement) => { let next = Machine { - args: context.args, - frames: context.frames, program: *statement, - index_counter: context.index_counter, + ..context.clone() }; let not_results = run(next); - todo!(); // remove all not_results from context.frames + for (idx, _) in not_results.frames.iter() { + context.frames.remove(idx); + } + + Ok(context) } Statement::Exists(var, collection) => { let btree: BTreeMap = match collection { @@ -145,7 +179,7 @@ pub fn run_once(mut context: Machine) -> Result { Ok(context) } - Statement::Equal(left, right) => context + Statement::Equal(left, right) => context // FIXME do unification .apply(&left, &right, |a, b| a == b) .map(|()| context), Statement::GreaterThan(left, right) => context @@ -325,12 +359,20 @@ impl Machine { .collect() }); - if updated.is_empty() { - return Err(()); + match updated { + Stream::Every(btree) => { + if btree.len() < stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(key, updated); - Ok(()) } else { Err(()) @@ -340,18 +382,27 @@ impl Machine { Value::Variable(var_id) => { let lhs_key = &var_id.0; - if let Some(stream) = self.frames.get(lhs_key) { + if let Some(lhs_stream) = self.frames.get(lhs_key) { match rhs { Value::Literal(right_ipld) => { - let updated = stream.clone().map(|btree| { + let updated = lhs_stream.clone().map(|btree| { btree .into_iter() .filter(|(_idx, ipld)| f(ipld, right_ipld)) .collect() }); - if updated.is_empty() { - return Err(()); + match updated { + Stream::Every(btree) => { + if btree.len() < lhs_stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(lhs_key.clone(), updated); @@ -360,16 +411,36 @@ impl Machine { Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); - if let Some(stream) = self.frames.get(rhs_key) { - let updated = stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(ipld, ipld)) - .collect() - }); + // ["every", ".email", "?email"] + // ["some", ".req", "?req"] + // ["==", "?email", "?req"] - if updated.is_empty() { - return Err(()); + // ["some", ".email", "?email"] + // ["every", ".req", "?req"] + // ["==", "?email", "?req"] + + if let Some(rhs_stream) = self.frames.get(rhs_key) { + let updated: Vec<((usize, Ipld), (usize, Ipld))> = lhs_stream + .to_btree() + .into_iter() + .zip(rhs_stream.to_btree()) + .filter(|((lhs_idx, lhs_ipld), (rhs_idx, rhs_ipld))| { + f(lhs_ipld, rhs_ipld) + }) + .collect(); + + // FIXME check both sides + match (updated_lhs, updated_rhs) { + Stream::Every(btree) => { + if btree.len() < stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(lhs_key.clone(), updated); diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index d747b6ab..ecf75d93 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,3 +1,4 @@ +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -128,10 +129,10 @@ pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { panic!("FIXME"); } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] pub enum Stream { Every(BTreeMap), // "All or nothing" - Some(BTreeMap), + Some(BTreeMap), // FIXME disambiguate from Option::Some } impl Stream { From 8c936aa329ef0906baf4da7ade7a1ffda1c81364 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 11:29:33 -0800 Subject: [PATCH 168/234] Just need to add pattern matching unification --- src/delegation/policy/interpreter.rs | 85 ++++++++++++++++------------ src/delegation/policy/ir.rs | 30 +++++++++- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 1c66b24b..7b378937 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -42,6 +42,7 @@ pub struct Machine { } pub fn run(machine: Machine) -> Machine { + todo!() // run to exhaustion // loop { // if let Ok(next) = run_once(&machine) { @@ -360,12 +361,12 @@ impl Machine { }); match updated { - Stream::Every(btree) => { - if btree.len() < stream.to_btree().len() { + Stream::Every(ref btree) => { + if btree.len() < stream.len() { return Err(()); } } - Stream::Some(btree) => { + Stream::Some(ref btree) => { if btree.is_empty() { return Err(()); } @@ -393,12 +394,12 @@ impl Machine { }); match updated { - Stream::Every(btree) => { - if btree.len() < lhs_stream.to_btree().len() { + Stream::Every(ref btree) => { + if btree.len() < lhs_stream.len() { return Err(()); } } - Stream::Some(btree) => { + Stream::Some(ref btree) => { if btree.is_empty() { return Err(()); } @@ -411,41 +412,55 @@ impl Machine { Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); - // ["every", ".email", "?email"] - // ["some", ".req", "?req"] - // ["==", "?email", "?req"] - - // ["some", ".email", "?email"] - // ["every", ".req", "?req"] - // ["==", "?email", "?req"] - if let Some(rhs_stream) = self.frames.get(rhs_key) { - let updated: Vec<((usize, Ipld), (usize, Ipld))> = lhs_stream - .to_btree() - .into_iter() - .zip(rhs_stream.to_btree()) - .filter(|((lhs_idx, lhs_ipld), (rhs_idx, rhs_ipld))| { - f(lhs_ipld, rhs_ipld) - }) - .collect(); - - // FIXME check both sides - match (updated_lhs, updated_rhs) { - Stream::Every(btree) => { - if btree.len() < stream.to_btree().len() { - return Err(()); + let mut non_matches: BTreeMap> = BTreeMap::new(); + + for (lhs_id, lhs_value) in lhs_stream.iter() { + for (rhs_id, rhs_value) in rhs_stream.iter() { + if !f(lhs_value, rhs_value) { + if let Some(rhs_ids) = non_matches.get_mut(lhs_id) { + rhs_ids.push(*rhs_id); + } else { + non_matches.insert(*lhs_id, vec![*rhs_id]); + } } } - Stream::Some(btree) => { - if btree.is_empty() { - return Err(()); + } + + // Double negatives, but for good reason + let did_quantify = + match (lhs_stream.is_every(), rhs_stream.is_every()) { + (true, true) => non_matches.is_empty(), + (true, false) => non_matches + .values() + .all(|rhs_ids| rhs_ids.len() != rhs_stream.len()), + (false, true) => { + non_matches.values().any(|rhs_ids| rhs_ids.is_empty()) + } + (false, false) => non_matches + .values() + .any(|rhs_ids| rhs_ids.len() < rhs_stream.len()), + }; + + if did_quantify { + let mut new_lhs_stream = lhs_stream.clone(); + let mut new_rhs_stream = rhs_stream.clone(); + + for (l_key, r_keys) in non_matches { + new_lhs_stream.remove(l_key); + + for r_key in r_keys { + new_rhs_stream.remove(r_key); } } - } - self.frames.insert(lhs_key.clone(), updated); + self.frames.insert(lhs_key.into(), new_lhs_stream); + self.frames.insert(rhs_key.into(), new_rhs_stream); - Ok(()) + Ok(()) + } else { + Err(()) + } } else { Err(()) } @@ -459,7 +474,7 @@ impl Machine { } } - pub fn unify(&mut self, lhs: &Value, rhs: &Value) -> Result { + pub fn pattern_matching_unification(&mut self, lhs: &Value, rhs: &Value) -> Result { self.apply(lhs, rhs, |a, b| { todo!(); todo!(); diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index ecf75d93..ef2f5788 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,3 +1,4 @@ +//FIXME rename core use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -50,7 +51,6 @@ pub enum Statement { GreaterThanOrEqual(Value, Value), LessThan(Value, Value), LessThanOrEqual(Value, Value), - // >= and <= are probably good for efficiency // String Matcher Glob(Value, Value), @@ -78,8 +78,7 @@ pub struct Selector(pub Vec); // .foo.bar[].baz #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { - This, // . - // RecDesend, // .. + This, // . Index(usize), // [2] Key(String), // ["key"] (or .key) FlattenAll, // [] --> creates an Array @@ -136,6 +135,31 @@ pub enum Stream { } impl Stream { + pub fn remove(&mut self, key: usize) { + match self { + Stream::Every(xs) => { + xs.remove(&key); + } + Stream::Some(xs) => { + xs.remove(&key); + } + } + } + + pub fn len(&self) -> usize { + match self { + Stream::Every(xs) => xs.len(), + Stream::Some(xs) => xs.len(), + } + } + + pub fn iter(&self) -> impl Iterator { + match self { + Stream::Every(xs) => xs.iter(), + Stream::Some(xs) => xs.iter(), + } + } + pub fn to_btree(self) -> BTreeMap { match self { Stream::Every(xs) => xs, From 730894f571f1ebb04a4cae6e43eee57e697048a1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 29 Feb 2024 00:07:11 -0800 Subject: [PATCH 169/234] Autoencode --- src/ability.rs | 1 + src/ability/command.rs | 4 +- src/ability/crud.rs | 10 +- src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 125 +++----------- src/ability/crud/destroy.rs | 2 +- src/ability/crud/js.rs | 14 -- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 8 +- src/ability/pipe.rs | 38 +++++ src/ability/ucan.rs | 1 - src/ability/ucan/pipe.rs | 11 ++ src/ability/ucan/proxy.rs | 59 ------- src/ability/ucan/revoke.rs | 76 +-------- src/ability/wasm/run.rs | 2 +- src/crypto/signature/envelope.rs | 10 ++ src/delegation.rs | 60 +++---- src/delegation/agent.rs | 13 +- src/delegation/condition.rs | 2 +- src/delegation/condition/traits.rs | 4 +- src/delegation/payload.rs | 12 +- src/delegation/policy.rs | 6 +- src/delegation/policy/ir.rs | 251 ++++++++++++++++++++++++++--- src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 6 +- src/invocation.rs | 7 + src/invocation/payload.rs | 36 ++--- src/ipld/promised.rs | 32 ++-- src/lib.rs | 8 +- src/receipt.rs | 19 ++- src/task.rs | 1 - src/task/id.rs | 2 + src/url.rs | 12 -- 37 files changed, 441 insertions(+), 405 deletions(-) create mode 100644 src/ability/pipe.rs create mode 100644 src/ability/ucan/pipe.rs delete mode 100644 src/ability/ucan/proxy.rs diff --git a/src/ability.rs b/src/ability.rs index 1cc28455..d582fc5c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -33,6 +33,7 @@ //! field may include a promise pointing at another invocation. Once fully //! resolved ("ready"), they must be validatable against the delegation chain. +pub mod pipe; pub mod ucan; #[cfg(feature = "ability-crud")] diff --git a/src/ability/command.rs b/src/ability/command.rs index 5cfa1975..eb97ea7f 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -7,7 +7,7 @@ //! { //! "iss": "did:example:123", //! "aud": "did:example:456", -//! "cmd": "msg/send", // <--- This is the command +//! "cmd": "/msg/send", // <--- This is the command //! "args": { // ┐ //! "to": "mailto:alice@example.com", // ├─ These are determined by the command //! "message": "Hello, World!", // │ @@ -32,7 +32,7 @@ /// } /// /// impl Command for Upload { -/// const COMMAND: &'static str = "storage/upload"; +/// const COMMAND: &'static str = "/storage/upload"; /// } /// /// assert_eq!(Upload::COMMAND, "storage/upload"); diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 57df163c..c9d5f777 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -26,7 +26,7 @@ //! ```js //! { //! "sub: "did:example:1234", // <-- e.g. Wraps a web API -//! "cmd": "crud/update", +//! "cmd": "/crud/update", //! "args": { //! "path": "/some/path/to/a/resource", //! }, @@ -67,7 +67,7 @@ pub mod js; #[derive(Debug, Clone, PartialEq)] pub enum Ready { - Create(create::Ready), + Create(create::Create), Read(read::Ready), Update(update::Ready), Destroy(destroy::Ready), @@ -75,7 +75,7 @@ pub enum Ready { #[derive(Debug, Clone, PartialEq)] pub enum Promised { - Create(create::Promised), + Create(create::PromisedCreate), Read(read::Promised), Update(update::Promised), Destroy(destroy::Promised), @@ -88,7 +88,7 @@ impl ParsePromised for Promised { cmd: &str, args: arguments::Named, ) -> Result> { - match create::Promised::try_parse_promised(cmd, args.clone()) { + match create::PromisedCreate::try_parse_promised(cmd, args.clone()) { Ok(create) => return Ok(Promised::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) @@ -131,7 +131,7 @@ impl ParseAbility for Ready { cmd: &str, args: arguments::Named, ) -> Result> { - match create::Ready::try_parse(cmd, args.clone()) { + match create::Create::try_parse(cmd, args.clone()) { Ok(create) => return Ok(Ready::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 6fcdd310..c85d4fb2 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -61,7 +61,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "crud/*"; + const COMMAND: &'static str = "/crud"; } impl TryFrom> for Any { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index baa417cc..b018fe38 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -11,21 +11,6 @@ use libipld_core::ipld::Ipld; use serde::Serialize; use std::path::PathBuf; -// FIXME deserialize instance - -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be created. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments for creation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -49,8 +34,8 @@ pub struct Generic { /// end /// end /// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") +/// createpromise("crud::create::PromisedCreate") +/// createready("crud::create::Create") /// /// top --> any --> mutate --> create /// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} @@ -59,7 +44,7 @@ pub struct Generic { /// ``` #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Create { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, @@ -93,8 +78,8 @@ pub struct Ready { /// end /// end /// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") +/// createpromise("crud::create::PromisedCreate") +/// createready("crud::create::Create") /// /// top --> any --> mutate --> create /// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} @@ -103,7 +88,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, @@ -113,17 +98,17 @@ pub struct Promised { pub args: Option>>, } -const COMMAND: &str = "crud/create"; +const COMMAND: &str = "/crud/create"; -impl Command for Ready { +impl Command for Create { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedCreate { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedCreate { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -171,11 +156,11 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedCreate { path, args }) } } -impl TryFrom> for Ready { +impl TryFrom> for Create { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -198,79 +183,13 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Create { path, args }) } } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// let mut named = arguments::Named::new(); -// -// if let Some(path) = promised.path { -// named.insert("path".to_string(), Ipld::String(path.to_string())); -// } -// -// if let Some(args) = promised.args { -// named.insert("args".to_string(), Ipld::from(args)); -// } -// -// named -// } -// } - -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedCreate { + fn from(r: Create) -> PromisedCreate { + PromisedCreate { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -280,12 +199,12 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Create { + type Promised = PromisedCreate; } -impl From for arguments::Named { - fn from(builder: Ready) -> Self { +impl From for arguments::Named { + fn from(builder: Create) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { @@ -306,8 +225,8 @@ impl From for arguments::Named { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedCreate) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index e43123cb..f7a7bf41 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -133,7 +133,7 @@ impl TryFrom> for Promised { } } -const COMMAND: &'static str = "crud/destroy"; +const COMMAND: &'static str = "/crud/destroy"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index f7978aa9..77a7879c 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -21,20 +21,6 @@ impl CrudAny { pub fn to_command(&self) -> String { self.to_command() } - - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - if self.path.is_some() { - if self.path != proof.path { - return Err(OptionalFieldError { - field: "path".into(), - err: OptionalFieldReason::NotEqual, - } - .into()); - } - } - - Ok(()) - } } #[wasm_bindgen] diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 4900e00c..a8ca9746 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -62,7 +62,7 @@ pub struct Mutate { } impl Command for Mutate { - const COMMAND: &'static str = "crud/mutate"; + const COMMAND: &'static str = "/crud/mutate"; } impl From for Ipld { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index b9201b26..db25111b 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -147,7 +147,7 @@ impl TryFrom> for Promised { } } -const COMMAND: &'static str = "crud/read"; +const COMMAND: &'static str = "/crud/read"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 54863a76..e355fa2b 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -143,7 +143,7 @@ pub struct Promised { args: Option>>, } -const COMMAND: &'static str = "crud/update"; +const COMMAND: &'static str = "/crud/update"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index a20e3db9..16e8eac8 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -49,7 +49,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "msg/*"; + const COMMAND: &'static str = "/msg"; } impl From for Ipld { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 1b0e817b..d730da8c 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -49,7 +49,7 @@ pub struct Receive { // FIXME needs promisory version -const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "/msg/send"; impl Command for Receive { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c65869d5..66905c68 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -5,7 +5,7 @@ use crate::{ invocation::promise, ipld, url, }; -use libipld_core::{error::SerdeError, ipld::Ipld}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -96,10 +96,6 @@ pub struct Promised { pub message: promise::Resolves, } -// impl Delegable for Ready { -// type Builder = Builder; -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -197,7 +193,7 @@ impl From for arguments::Named { } } -const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "/msg/send"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs new file mode 100644 index 00000000..32ab4744 --- /dev/null +++ b/src/ability/pipe.rs @@ -0,0 +1,38 @@ +use crate::{crypto::varsig, delegation, delegation::condition::Condition, did::Did, ipld}; +use libipld_core::{codec::Codec, ipld::Ipld}; + +pub struct Pipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + pub source: Cap, + pub sink: Cap, +} + +pub enum Cap, Enc: Codec + TryFrom + Into> +{ + Chain(delegation::Chain), + Literal(Ipld), +} + +pub struct PromisedPipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + pub source: PromisedCap, + pub sink: PromisedCap, +} + +pub enum PromisedCap< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + Chain(delegation::Chain), + Promised(ipld::Promised), +} diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 0e3daaad..f2302b1a 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,4 +1,3 @@ //! Abilities for and about UCANs themselves pub mod revoke; -// FIXME pub mod proxy; diff --git a/src/ability/ucan/pipe.rs b/src/ability/ucan/pipe.rs new file mode 100644 index 00000000..2d79fcf8 --- /dev/null +++ b/src/ability/ucan/pipe.rs @@ -0,0 +1,11 @@ + use crate::delegation; + +pub struct Pipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > { + pub from: delegation::Chain, + pub to: delegation::Chain, +} diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs deleted file mode 100644 index 1aa056c1..00000000 --- a/src/ability/ucan/proxy.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::{ - ability::{arguments, command::Command}, - delegation::Delegable, - invocation::Promise, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -// NOTE This one is primarily for enabling delegationd recipets - -// FIXME can this *only* be a builder? -// NOTE UNLIKE the dynamic ability, this has cmd as an argument *at runtime* -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - pub cmd: String, - pub args: Args, // FIXME Does this have specific fields? - // FIXME should args just be a CID -} - -pub type Ready = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; - -impl Command for Generic { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl Delegable for Ready { - type Builder = Builder; -} - -impl From for Builder { - fn from(resolved: Ready) -> Builder { - Builder { - cmd: resolved.cmd, - args: Some(resolved.args), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Ready { - cmd: b.cmd, - args: b.args.ok_or(())?, - }) - } -} - -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { - let mut args = b.args.unwrap_or_default(); - args.insert("cmd".into(), Ipld::String(b.cmd)); - args - } -} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index dcb7aec5..f6268fe5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -20,24 +20,15 @@ pub struct Ready { pub ucan: Cid, } -const COMMAND: &'static str = "ucan/revoke"; +const COMMAND: &'static str = "/ucan/revoke"; impl Command for Ready { const COMMAND: &'static str = COMMAND; } - -// impl Command for Builder { -// const COMMAND: &'static str = COMMAND; -// } - impl Command for Promised { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Builder; -// } - impl TryFrom> for Ready { type Error = (); @@ -49,27 +40,6 @@ impl TryFrom> for Ready { } } -// impl TryFrom> for Builder { -// type Error = (); -// -// fn try_from(arguments: arguments::Named) -> Result { -// if let Some(ipld) = arguments.get("ucan") { -// let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; -// Ok(Builder { ucan: Some(nt.cid) }) -// } else { -// Ok(Builder { ucan: None }) -// } -// } -// } - -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// ucan: promised.ucan.try_resolve().ok(), -// } -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -80,50 +50,6 @@ impl From for arguments::Named { } } -// /// A variant with some fields waiting to be set. -// #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -// pub struct Builder { -// pub ucan: Option, -// } -// -// impl NoParents for Builder {} -// -// impl CheckSame for Builder { -// type Error = OptionalFieldError; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// self.ucan.check_same(&proof.ucan) -// } -// } - -// impl From for Builder { -// fn from(resolved: Ready) -> Builder { -// Builder { -// ucan: Some(resolved.ucan), -// } -// } -// } - -// impl TryFrom for Ready { -// type Error = (); -// -// fn try_from(b: Builder) -> Result { -// Ok(Ready { -// ucan: b.ucan.ok_or(())?, -// }) -// } -// } -// -// impl From for arguments::Named { -// fn from(b: Builder) -> arguments::Named { -// let mut btree = BTreeMap::new(); -// if let Some(cid) = b.ucan { -// btree.insert("ucan".into(), cid.into()); -// } -// arguments::Named(btree) -// } -// } - /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Promised { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 0c7f8cfa..8d97d8b9 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -11,7 +11,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -const COMMAND: &'static str = "wasm/run"; +const COMMAND: &'static str = "/wasm/run"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 22576fc0..f4f03f09 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -62,6 +62,16 @@ impl< } } + // FIXME extract into trait? + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + let codec = self.varsig_header.codec().clone(); + let ipld: Ipld = self.into(); + ipld.encode(codec, w) + } + /// Attempt to sign some payload with a given signer. /// /// # Arguments diff --git a/src/delegation.rs b/src/delegation.rs index 543ce82b..bd0d975f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -17,18 +17,16 @@ pub mod policy; pub mod store; mod agent; -// mod delegable; mod payload; pub use agent::Agent; -// pub use delegable::Delegable; pub use payload::*; +use crate::capsule::Capsule; use crate::{ // ability, crypto::{signature, varsig, Nonce}, did::{self, Did}, - // proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; @@ -54,6 +52,20 @@ pub struct Delegation< Enc: Codec + TryFrom + Into, >(pub signature::Envelope, DID, V, Enc>); +#[derive(Clone, Debug, PartialEq)] +pub struct Chain< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(Vec>); + +impl, Enc: Codec + TryFrom + Into> Capsule + for Chain +{ + const TAG: &'static str = "ucan/chain"; +} + /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation< condition::Preset, @@ -83,8 +95,8 @@ impl, Enc: Codec + Into + Tr } /// Retrive the `condition` of a [`Delegation`] - pub fn conditions(&self) -> &[C] { - &self.0.payload.conditions + pub fn policy(&self) -> &[C] { + &self.0.payload.policy } /// Retrive the `metadata` of a [`Delegation`] @@ -119,6 +131,13 @@ impl, Enc: Codec + Into + Tr &self.0.varsig_header } + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } + pub fn signature(&self) -> &DID::Signature { &self.0.signature } @@ -155,34 +174,3 @@ impl, Enc: Codec + Into + Tr signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } - -// impl< -// B: CheckSame, -// C: Condition, -// DID: Did, -// V: varsig::Header, -// Enc: Codec + TryFrom + Into, -// > CheckSame for Delegation -// { -// type Error = ::Error; -// -// fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { -// self.0.payload.check_same(&proof.payload()) -// } -// } -// -// impl< -// T: CheckParents, -// C: Condition, -// DID: Did, -// V: varsig::Header, -// Enc: Codec + TryFrom + Into, -// > CheckParents for Delegation -// { -// type Parents = Delegation; -// type ParentError = ::ParentError; -// -// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { -// self.payload().check_parent(&proof.payload()) -// } -// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 42745c80..971a6e97 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -60,7 +60,8 @@ where &self, audience: DID, subject: Option, - new_conditions: Vec, + command: String, + new_policy: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, @@ -76,11 +77,12 @@ where issuer: self.did.clone(), audience, subject, + command, metadata, nonce, expiration: expiration.into(), not_before: not_before.map(Into::into), - conditions: new_conditions, + policy: new_policy, }; return Ok( @@ -98,14 +100,15 @@ where .1 .payload(); - let mut conditions = to_delegate.conditions.clone(); - conditions.append(&mut new_conditions.clone()); + let mut policy = to_delegate.policy.clone(); + policy.append(&mut new_policy.clone()); let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, - conditions, + command, + policy, metadata, nonce, expiration: expiration.into(), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 868f90b9..207a181e 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,4 +1,4 @@ -//! Conditions for syntactic validation of abilities in [`Delegation`][super::Delegation]s. +//! Policies for syntactic validation of abilities in [`Delegation`][super::Delegation]s. mod contains_all; mod contains_any; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 1c7b8f90..f1fcf7a6 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,10 +1,10 @@ -//! Traits for abstracting over conditions. +//! Traits for abstracting over policies. use crate::ability::arguments; use libipld_core::ipld::Ipld; use std::fmt; -/// A trait for conditions that can be run on named IPLD arguments. +/// A trait for policies that can be run on named IPLD arguments. pub trait Condition: fmt::Debug { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bbde3e99..8247384b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -45,8 +45,11 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, + /// The command being delegated. + pub command: String, + /// Any [`Condition`]s on the `ability_builder`. - pub conditions: Vec, + pub policy: Vec, /// Extensible, free-form fields. pub metadata: BTreeMap, @@ -129,6 +132,7 @@ where Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), DID::arbitrary_with(did_args), + String::arbitrary(), Nonce::arbitrary(), Timestamp::arbitrary(), Option::::arbitrary(), @@ -144,17 +148,19 @@ where subject, issuer, audience, + command, nonce, expiration, not_before, metadata, - conditions, + policy, )| { Payload { issuer, subject, audience, - conditions, + command, + policy, metadata, nonce, expiration, diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 7be6d057..64b58ce5 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,3 @@ -pub mod frontend; -pub mod interpreter; -pub mod ir; +//pub mod frontend; +//pub mod interpreter; +// pub mod ir; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index ef2f5788..a424f6e1 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -2,15 +2,12 @@ use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Term { // Leaves - Args, // $ Literal(Ipld), - Stream(Stream), - Selector(Selector), // NOTE the IR version doens't inline the results // Connectives @@ -27,48 +24,46 @@ pub enum Term { Glob(Value, Value), // Existential Quantification - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some + Every(Variable, Value), // ∀x ∈ xs + Some(Variable, Value), // ∃x ∈ xs -> convert every -> some } // FIXME exract domain gen selectors first? // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Statement { + // Select from ?foo + Select(Selector, SelectorValue, Variable), // .foo.bar[].baz + // Connectives Not(Box), And(Box, Box), Or(Box, Box), - // Forall: unpack and unify size before and after - // Exists genrates more than one frame (unpacks an array) instead of one - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs + // String Matcher + Glob(Value, Value), + Equal(Value, Value), // AKA unification // FIXME value can also be a selector // Comparison - Equal(Value, Value), // AKA unification // FIXME value can also be a selector GreaterThan(Value, Value), GreaterThanOrEqual(Value, Value), LessThan(Value, Value), LessThanOrEqual(Value, Value), - // String Matcher - Glob(Value, Value), - - // Select from ?foo - Select(Selector, SelectorValue, Variable), // .foo.bar[].baz + // Forall: unpack and unify size before and after + // Exists genrates more than one frame (unpacks an array) instead of one + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Variable(pub String); // ?x #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Collection { Array(Vec), Map(BTreeMap), - // NOTE The below can always be desugared, esp because this is now only used with forall/exists - // Variable(Variable), ] - // Selector(Selector), + Selector(Vec), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -81,12 +76,11 @@ pub enum PathSegment { This, // . Index(usize), // [2] Key(String), // ["key"] (or .key) - FlattenAll, // [] --> creates an Array + // FlattenAll, // [] --> creates an Array } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum SelectorValue { - Args, Literal(Ipld), Variable(Variable), } @@ -94,7 +88,7 @@ pub enum SelectorValue { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Value { Literal(Ipld), - Variable(Variable), + Selector(Vec), } pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { @@ -193,3 +187,214 @@ impl Stream { // // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // pub struct SomeStream(Vec); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum LogicIpld { + Null, + Bool(bool), + Float(f64), + Integer(i128), + String(String), + Bytes(Vec), + List(Vec), + Map(BTreeMap), + + // A new challenger has appeared! + Var(String), +} + +impl LogicIpld { + pub fn substitute(&mut self, bindings: BTreeMap) { + match self { + LogicIpld::Var(var_id) => { + if let Some(value) = bindings.get(&Variable(*var_id)) { + *self = value.clone(); + } + } + LogicIpld::List(xs) => { + for x in xs { + x.substitute(bindings.clone()); + } + } + LogicIpld::Map(btree) => { + for (_, x) in btree { + x.substitute(bindings.clone()); + } + } + _other => (), + } + } + + pub fn extract_into(&self, vars: &mut BTreeSet) { + match self { + LogicIpld::Var(var_id) => { + vars.insert(Variable(var_id.clone())); + } + LogicIpld::List(xs) => { + for x in xs { + x.extract_into(vars); + } + } + LogicIpld::Map(btree) => { + for (_, x) in btree { + x.extract_into(vars); + } + } + _other => (), + } + } + + pub fn unify( + self, + other: Self, + bindings: BTreeMap, + ) -> Result<(Self, BTreeSet), ()> { + match (self, other) { + (LogicIpld::Null, LogicIpld::Null) => Ok((LogicIpld::Null, BTreeSet::new())), + (LogicIpld::Bool(a), LogicIpld::Bool(b)) => { + if a == b { + Ok((LogicIpld::Bool(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Float(a), LogicIpld::Float(b)) => { + if a == b { + Ok((LogicIpld::Float(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Integer(a), LogicIpld::Integer(b)) => { + if a == b { + Ok((LogicIpld::Integer(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::String(a), LogicIpld::String(b)) => { + if a == b { + Ok((LogicIpld::String(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Bytes(a), LogicIpld::Bytes(b)) => { + if a == b { + Ok((LogicIpld::Bytes(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::List(a), LogicIpld::List(b)) => { + // FIXME + if a.len() != b.len() { + return Err(()); + } + let mut bindings = BTreeSet::new(); + let mut result = Vec::with_capacity(a.len()); + for (a, b) in a.into_iter().zip(b.into_iter()) { + let (unified, mut new_bindings) = a.unify(b)?; + result.push(unified); + bindings.append(&mut new_bindings); + } + Ok((LogicIpld::List(result), bindings)) + } + (LogicIpld::Map(a), LogicIpld::Map(b)) => { + // FIXME + if a.len() != b.len() { + return Err(()); + } + let mut bindings = BTreeSet::new(); + let mut result = BTreeMap::new(); + for (k, a) in a.into_iter() { + if let Some(b) = b.get(&k) { + let (unified, mut new_bindings) = a.unify(b.clone())?; + result.insert(k, unified); + bindings.append(&mut new_bindings); + } else { + return Err(()); + } + } + Ok((LogicIpld::Map(result), bindings)) + } + (LogicIpld::Var(a), LogicIpld::Var(b)) => { + // FIXME + + // If I have a binding for a, and no binding for b, set ?b := ?a + // If I have a binding for b, and no binding for a, set ?a := ?b + // If I have neither binding, what do??? + // If I have both bindings, and they are the same, great + // 1. check ?a == ?b + // 2 set ?a := ?b + // NOTE: would need to update the lookup procedure elsewhere + // to do recursive lookup + // If I have both bindings, and they are not immeditely equal, + // recursively unify them, and then set ?a := ?b + // NOTE: during recursion, if we see ?a in ?b or ?b in ?a, you need to bail + + if a == b { + Ok((LogicIpld::Var(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Var(lhs_tag), logic_ipld) => { + match logic_ipld { + rhs @ LogicIpld::Var(rhs_tag) => { + //FIXME double check + if let Some(b) = bindings.get(&rhs_tag) { + let (unified, mut new_bindings) = + LogicIpld::Var(a).unify(rhs.clone())?; + new_bindings.insert(rhs.clone()); + Ok((unified, new_bindings)) + } else { + let mut new_bindings = BTreeSet::new(); + new_bindings.insert(Variable(b.clone())); + Ok((LogicIpld::Var(a), new_bindings)) + } + } + + _ => { + let mut new_bindings = BTreeSet::new(); + new_bindings.insert(Variable(a.clone())); + Ok((logic_ipld, new_bindings)) + } + } + } + (lhs, rhs @ LogicIpld::Var(_)) => rhs.unify(lhs, bindings), + + _ => Err(()), + } + } +} + +impl TryFrom for Ipld { + type Error = Variable; + + fn try_from(logic: LogicIpld) -> Result { + match logic { + LogicIpld::Null => Ok(Ipld::Null), + LogicIpld::Bool(b) => Ok(Ipld::Bool(b)), + LogicIpld::Float(f) => Ok(Ipld::Float(f)), + LogicIpld::Integer(i) => Ok(Ipld::Integer(i)), + LogicIpld::String(s) => Ok(Ipld::String(s)), + LogicIpld::Bytes(b) => Ok(Ipld::Bytes(b)), + LogicIpld::List(xs) => xs + .into_iter() + .try_fold(vec![], |mut acc, x| { + acc.push(Ipld::try_from(x)?); + Ok(acc) + }) + .map(Ipld::List), + LogicIpld::Map(btree) => btree + .into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, Ipld::try_from(v)?); + Ok(acc) + }) + .map(Ipld::Map), + LogicIpld::Var(var_id) => Err(Variable(var_id)), + } + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e87bfc5d..b05b4039 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -121,7 +121,7 @@ impl< &self, aud: &DID, subject: &Option, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 44b032dd..e3090ff8 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -39,7 +39,7 @@ pub trait Store< &self, audience: &DID, subject: &Option, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; @@ -47,10 +47,10 @@ pub trait Store< &self, issuer: DID, audience: &DID, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, &Some(issuer), conditions, now) + self.get_chain(audience, &Some(issuer), policy, now) .map(|chain| chain.is_some()) } diff --git a/src/invocation.rs b/src/invocation.rs index 7e66670e..a878bebc 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -78,6 +78,13 @@ where Invocation(signature::Envelope::new(varsig_header, signature, payload)) } + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } + pub fn payload(&self) -> &Payload { &self.0.payload } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0350aa98..3885afaa 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -138,12 +138,17 @@ impl Payload { now: &SystemTime, ) -> Result<(), ValidationError> where - A: Clone, + A: ToCommand + Clone, DID: Clone, arguments::Named: From, { let args: arguments::Named = self.ability.clone().into(); + let mut cmd = self.ability.to_command(); + if !cmd.ends_with('/') { + cmd.push('/'); + } + proofs.into_iter().try_fold(&self.issuer, |iss, proof| { // FIXME extract step function? if *iss != proof.audience { @@ -166,7 +171,11 @@ impl Payload { } } - for predicate in proof.conditions.iter() { + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } + + for predicate in proof.policy.iter() { if !predicate.validate(&args) { return Err(ValidationError::FailedCondition(predicate.clone())); } @@ -194,6 +203,9 @@ pub enum ValidationError { #[error("The delegation is not yet valid")] NotYetValid, + #[error("The command of the delegation does not match the proof: {0:?}")] + CommandMismatch(String), + #[error("The delegation failed a condition: {0:?}")] FailedCondition(C), } @@ -202,26 +214,6 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -// impl From> for delegation::Payload { -// fn from(inv_payload: Payload) -> Self { -// delegation::Payload { -// issuer: inv_payload.issuer, -// subject: Some(inv_payload.subject.clone()), -// audience: inv_payload.audience.unwrap_or(inv_payload.subject), -// -// conditions: vec![], -// -// metadata: inv_payload.metadata, -// nonce: inv_payload.nonce, -// -// not_before: None, -// expiration: inv_payload -// .expiration -// .unwrap_or(Timestamp::postel(SystemTime::now())), -// } -// } -// } - impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 589fe934..52a0c907 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,4 +1,3 @@ -// use super::enriched::Enriched; use crate::{ ability::arguments, invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, @@ -14,22 +13,40 @@ use std::{collections::BTreeMap, path::PathBuf}; /// [`Promised`] resolves to regular [`Ipld`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] pub enum Promised { - // Resolved Leaves + /// Resolved null. Null, + + /// Resolved Boolean. Bool(bool), + + /// Resolved integer. Integer(i128), + + /// Resolved float. Float(f64), + + /// Resolved string. String(String), + + /// Resolved bytes. Bytes(Vec), + + /// Resolved link. Link(Cid), - // Pending Leaves + /// Promise pending the `ok` branch. WaitOk(Cid), + + /// Promise pending the `err` branch. WaitErr(Cid), + + /// Promise pending either branch. WaitAny(Cid), - // Recursive + /// Recursively promised list. List(Vec), + + /// Recursively promised map. Map(BTreeMap), } @@ -278,7 +295,6 @@ impl TryFrom for url::Newtype { fn try_from(promised: Promised) -> Result { match promised { Promised::String(s) => Ok(url::Newtype(::url::Url::parse(&s).map_err(|_| ())?)), - // FIXME Promised::Link(cid) => Ok(url::Newtype::from(cid)), _ => Err(()), } } @@ -366,12 +382,6 @@ pub struct PostOrderIpldIter<'a> { outbound: Vec<&'a Promised>, } -// #[derive(Clone, Debug, PartialEq)] -// pub enum Item<'a> { -// Node(&'a Promised), -// Inner(&'a Cid), -// } - impl<'a> PostOrderIpldIter<'a> { /// Initialize a new [`PostOrderIpldIter`] #[must_use] diff --git a/src/lib.rs b/src/lib.rs index 32a40fdc..86efec3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,9 +35,5 @@ pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; -///////////// -// FIXME s // -///////////// - -// show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this +// FIXME +// show pipe diff --git a/src/receipt.rs b/src/receipt.rs index cb43ce6b..f771b90f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -5,6 +5,9 @@ //! - [`Store`] is the storage interface for [`Receipt`]s. //! - [`Responds`] associates the response success type to an [Ability][crate::ability]. +// FIXME consider "assertion"? +// + mod payload; mod responds; @@ -19,7 +22,10 @@ use crate::{ crypto::{signature, varsig}, did, }; -use libipld_core::codec::Codec; +use libipld_core::{ + codec::{Codec, Encode}, + ipld::Ipld, +}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] @@ -39,8 +45,8 @@ pub type Preset = Receipt< varsig::encoding::Preset, >; -impl, C: Codec + Into + TryFrom> - Receipt +impl, Enc: Codec + Into + TryFrom> + Receipt { /// Returns the [`Payload`] of the [`Receipt`]. pub fn payload(&self) -> &Payload { @@ -51,4 +57,11 @@ impl, C: Codec + Into + Tr pub fn signature(&self) -> &DID::Signature { &self.0.signature } + + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } } diff --git a/src/task.rs b/src/task.rs index 032378e7..6b931c59 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,7 +1,6 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. mod id; - pub use id::Id; use crate::{ability::arguments, crypto::Nonce, did}; diff --git a/src/task/id.rs b/src/task/id.rs index 0414e722..de799110 100644 --- a/src/task/id.rs +++ b/src/task/id.rs @@ -1,3 +1,5 @@ +//! A newtype wrapper around [`Cid`]s to tag them as able to identify a particular invocation. + use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/url.rs b/src/url.rs index d23b8efb..bb8ca202 100644 --- a/src/url.rs +++ b/src/url.rs @@ -50,18 +50,6 @@ impl fmt::Display for Newtype { } } -// impl CheckSame for Newtype { -// type Error = (); -// -// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { -// if self == other { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } - impl From for Ipld { fn from(newtype: Newtype) -> Self { newtype.into() From 5bdba9bdab70d8acb00902bb60687d49102421b4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 1 Mar 2024 18:45:48 -0800 Subject: [PATCH 170/234] Minimal predicate language --- src/ability/command.rs | 2 +- src/delegation/policy.rs | 2 +- src/delegation/policy/ir.rs | 689 +++++++++++++++++++----------------- src/ipld/newtype.rs | 62 +++- src/ipld/number.rs | 13 +- 5 files changed, 430 insertions(+), 338 deletions(-) diff --git a/src/ability/command.rs b/src/ability/command.rs index eb97ea7f..2cd85c3b 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -35,7 +35,7 @@ /// const COMMAND: &'static str = "/storage/upload"; /// } /// -/// assert_eq!(Upload::COMMAND, "storage/upload"); +/// assert_eq!(Upload::COMMAND, "/storage/upload"); /// ``` pub trait Command { /// The value that will be placed in the UCAN's `cmd` field for the given type diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 64b58ce5..30e6f251 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,3 @@ //pub mod frontend; //pub mod interpreter; -// pub mod ir; +pub mod ir; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index a424f6e1..3396d526 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,400 +1,421 @@ //FIXME rename core +use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Term { - // Leaves - Literal(Ipld), - Selector(Selector), // NOTE the IR version doens't inline the results - - // Connectives - Not(Box), - And(Vec), - Or(Vec), - - // Comparison - Equal(Value, Value), // AKA unification - GreaterThan(Value, Value), - LessThan(Value, Value), - - // String Matcher - Glob(Value, Value), - - // Existential Quantification - Every(Variable, Value), // ∀x ∈ xs - Some(Variable, Value), // ∃x ∈ xs -> convert every -> some +use std::{collections::BTreeMap, fmt}; + +impl Predicate { + pub fn run(self, data: &Ipld) -> Result { + Ok(match self { + Predicate::True => true, + Predicate::False => false, + Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, + Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, + Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, + Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, + Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, + Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Not(inner) => !inner.run(data)?, + Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, + Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, + Predicate::Forall(xs, p) => xs + .resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, ipld| Ok(acc && p.clone().run(ipld)?))?, + Predicate::Exists(xs, p) => { + let pred = p.clone(); + + xs.resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, ipld| Ok(acc || pred.clone().run(ipld)?))? + } + }) + } } -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Statement { - // Select from ?foo - Select(Selector, SelectorValue, Variable), // .foo.bar[].baz - - // Connectives - Not(Box), - And(Box, Box), - Or(Box, Box), +pub enum RunError { + IndexOutOfBounds, + KeyNotFound, + NotAList, + NotAMap, + NotACollection, + NotANumber(>::Error), +} - // String Matcher - Glob(Value, Value), - Equal(Value, Value), // AKA unification // FIXME value can also be a selector +trait Resolve { + fn resolve(self, ctx: &Ipld) -> Result; +} - // Comparison - GreaterThan(Value, Value), - GreaterThanOrEqual(Value, Value), - LessThan(Value, Value), - LessThanOrEqual(Value, Value), - - // Forall: unpack and unify size before and after - // Exists genrates more than one frame (unpacks an array) instead of one - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs +impl Resolve for Ipld { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Variable(pub String); // ?x +impl Resolve for ipld::Number { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } +} -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), - Selector(Vec), +impl Resolve for Collection { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(pub Vec); // .foo.bar[].baz +// impl Resolve for Collection { +// fn resolve(self, ctx: Ipld) -> Result { +// match self { +// Collection::Array(xs) => Ok(Ipld::List(xs)), +// Collection::Map(xs) => Ok(Ipld::Map(xs)), +// } +// } +// } -// FIXME need an IR representation of $args +// FIXME Normal form? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum PathSegment { - This, // . - Index(usize), // [2] - Key(String), // ["key"] (or .key) - // FlattenAll, // [] --> creates an Array +impl Resolve for String { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorValue { - Literal(Ipld), - Variable(Variable), -} +pub struct Text(String); -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Literal(Ipld), - Selector(Vec), -} +// impl TryFrom for String { +// fn try_from(ipld: Ipld) -> Result>::Error> { +// match ipld { +// Ipld::String(s) => Ok(s), +// _ => Err(()), +// } +// } +// } -pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { - if let (Ipld::String(s), Ipld::String(pat)) = (input, pattern) { - let mut input = s.chars(); - let mut pattern = pat.chars(); // Ugly - - loop { - match (input.next(), pattern.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; - } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; - } - } - (None, None) => { - return true; - } +impl SelectorOr { + fn resolve(self, ctx: &Ipld) -> Result { + match self { + SelectorOr::Pure(inner) => Ok(inner), + SelectorOr::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + SelectorOp::This => Ok((ipld, seen_ops)), + SelectorOp::Try(inner) => { + let op: SelectorOp = *inner.clone(); + let ipld: Ipld = SelectorOr::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + SelectorOp::Index(i) => { + let result = match ipld { + Ipld::List(xs) => xs + .get(*i) + .ok_or(SelectorError { + path: seen_ops.iter().map(|op| (*op).clone()).collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }) + .cloned(), + // FIXME behaviour on maps? type error + _ => Err(SelectorError { + path: seen_ops.iter().map(|op| (*op).clone()).collect(), + reason: SelectorErrorReason::NotAList, + }), + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Key(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?.clone(), seen_ops)) + } + SelectorOp::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?.clone(), seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) } } } - panic!("FIXME"); } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -pub enum Stream { - Every(BTreeMap), // "All or nothing" - Some(BTreeMap), // FIXME disambiguate from Option::Some +pub trait TryFromIpld: Sized { + fn try_from_ipld(ipld: Ipld) -> Result; } -impl Stream { - pub fn remove(&mut self, key: usize) { - match self { - Stream::Every(xs) => { - xs.remove(&key); - } - Stream::Some(xs) => { - xs.remove(&key); - } - } +impl TryFromIpld for Ipld { + fn try_from_ipld(ipld: Ipld) -> Result { + Ok(ipld) } +} - pub fn len(&self) -> usize { - match self { - Stream::Every(xs) => xs.len(), - Stream::Some(xs) => xs.len(), +impl TryFromIpld for ipld::Number { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), + Ipld::Float(f) => Ok(ipld::Number::Float(f)), + _ => Err(SelectorErrorReason::NotANumber), } } +} - pub fn iter(&self) -> impl Iterator { - match self { - Stream::Every(xs) => xs.iter(), - Stream::Some(xs) => xs.iter(), +impl TryFromIpld for String { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::String(s) => Ok(s), + _ => Err(SelectorErrorReason::NotAString), } } +} - pub fn to_btree(self) -> BTreeMap { - match self { - Stream::Every(xs) => xs, - Stream::Some(xs) => xs, +impl TryFromIpld for Collection { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::List(xs) => Ok(Collection::Array(xs)), + Ipld::Map(xs) => Ok(Collection::Map(xs)), + _ => Err(SelectorErrorReason::NotACollection), } } +} - pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { - match self { - Stream::Every(xs) => { - let updated = f(xs); - Stream::Every(updated) - } - Stream::Some(xs) => { - let updated = f(xs); - Stream::Some(updated) - } - } - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SelectorError { + pub path: Vec, + pub reason: SelectorErrorReason, +} - pub fn is_empty(&self) -> bool { - match self { - Stream::Every(xs) => xs.is_empty(), - Stream::Some(xs) => xs.is_empty(), +impl SelectorError { + pub fn from_refs(path_refs: &Vec<&SelectorOp>, reason: SelectorErrorReason) -> SelectorError { + SelectorError { + path: path_refs.iter().map(|op| (*op).clone()).collect(), + reason, } } } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct EveryStream(V); -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct SomeStream(Vec); +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SelectorErrorReason { + IndexOutOfBounds, + KeyNotFound, + NotAList, + NotAMap, + NotACollection, + NotANumber, + NotAString, +} +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum LogicIpld { - Null, - Bool(bool), - Float(f64), - Integer(i128), - String(String), - Bytes(Vec), - List(Vec), - Map(BTreeMap), - - // A new challenger has appeared! - Var(String), +pub enum Predicate { + // Booleans for connectives? FIXME + True, + False, + + // Comparison + Equal(SelectorOr, SelectorOr), + + GreaterThan(SelectorOr, SelectorOr), + GreaterThanOrEqual(SelectorOr, SelectorOr), + + LessThan(SelectorOr, SelectorOr), + LessThanOrEqual(SelectorOr, SelectorOr), + + Like(SelectorOr, SelectorOr), + + // Connectives + Not(Box), + And(Box, Box), + Or(Box, Box), + + // Collection iteration + Forall(SelectorOr, Box), // ∀x ∈ xs + Exists(SelectorOr, Box), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOr { + Get(Vec), + Pure(T), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), } -impl LogicIpld { - pub fn substitute(&mut self, bindings: BTreeMap) { +impl Collection { + pub fn to_vec(self) -> Vec { match self { - LogicIpld::Var(var_id) => { - if let Some(value) = bindings.get(&Variable(*var_id)) { - *self = value.clone(); - } - } - LogicIpld::List(xs) => { - for x in xs { - x.substitute(bindings.clone()); - } - } - LogicIpld::Map(btree) => { - for (_, x) in btree { - x.substitute(bindings.clone()); - } - } - _other => (), + Collection::Array(xs) => xs, + Collection::Map(xs) => xs.into_values().collect(), } } +} - pub fn extract_into(&self, vars: &mut BTreeSet) { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOp { + This, // . + Index(usize), // [2] + Key(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? +} + +impl fmt::Display for SelectorOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - LogicIpld::Var(var_id) => { - vars.insert(Variable(var_id.clone())); - } - LogicIpld::List(xs) => { - for x in xs { - x.extract_into(vars); - } - } - LogicIpld::Map(btree) => { - for (_, x) in btree { - x.extract_into(vars); + SelectorOp::This => write!(f, "."), + SelectorOp::Index(i) => write!(f, "[{}]", i), + SelectorOp::Key(k) => { + if let Some(first) = k.chars().next() { + if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + write!(f, ".{}", k) + } else { + write!(f, "[\"{}\"]", k) + } + } else { + write!(f, "[\"{}\"]", k) } } - _other => (), + SelectorOp::Values => write!(f, "[]"), + SelectorOp::Try(inner) => write!(f, "{}?", inner), } } +} - pub fn unify( - self, - other: Self, - bindings: BTreeMap, - ) -> Result<(Self, BTreeSet), ()> { - match (self, other) { - (LogicIpld::Null, LogicIpld::Null) => Ok((LogicIpld::Null, BTreeSet::new())), - (LogicIpld::Bool(a), LogicIpld::Bool(b)) => { - if a == b { - Ok((LogicIpld::Bool(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Float(a), LogicIpld::Float(b)) => { - if a == b { - Ok((LogicIpld::Float(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Integer(a), LogicIpld::Integer(b)) => { - if a == b { - Ok((LogicIpld::Integer(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::String(a), LogicIpld::String(b)) => { - if a == b { - Ok((LogicIpld::String(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Bytes(a), LogicIpld::Bytes(b)) => { - if a == b { - Ok((LogicIpld::Bytes(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::List(a), LogicIpld::List(b)) => { - // FIXME - if a.len() != b.len() { - return Err(()); - } - let mut bindings = BTreeSet::new(); - let mut result = Vec::with_capacity(a.len()); - for (a, b) in a.into_iter().zip(b.into_iter()) { - let (unified, mut new_bindings) = a.unify(b)?; - result.push(unified); - bindings.append(&mut new_bindings); +#[derive(Debug, Clone, PartialEq)] +pub struct Selector(pub Vec); + +impl Serialize for Selector { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0 + .iter() + .fold("".into(), |acc, seg| format!("{}{}", acc, seg.to_string())) + .serialize(serializer) + } +} + +pub fn glob(input: &String, pattern: &String) -> bool { + let mut chars = input.chars(); + let mut like = pattern.chars(); + + loop { + match (chars.next(), like.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; } - Ok((LogicIpld::List(result), bindings)) } - (LogicIpld::Map(a), LogicIpld::Map(b)) => { - // FIXME - if a.len() != b.len() { - return Err(()); - } - let mut bindings = BTreeSet::new(); - let mut result = BTreeMap::new(); - for (k, a) in a.into_iter() { - if let Some(b) = b.get(&k) { - let (unified, mut new_bindings) = a.unify(b.clone())?; - result.insert(k, unified); - bindings.append(&mut new_bindings); - } else { - return Err(()); - } - } - Ok((LogicIpld::Map(result), bindings)) + (Some(_), None) => { + return false; // FIXME correct? } - (LogicIpld::Var(a), LogicIpld::Var(b)) => { - // FIXME - - // If I have a binding for a, and no binding for b, set ?b := ?a - // If I have a binding for b, and no binding for a, set ?a := ?b - // If I have neither binding, what do??? - // If I have both bindings, and they are the same, great - // 1. check ?a == ?b - // 2 set ?a := ?b - // NOTE: would need to update the lookup procedure elsewhere - // to do recursive lookup - // If I have both bindings, and they are not immeditely equal, - // recursively unify them, and then set ?a := ?b - // NOTE: during recursion, if we see ?a in ?b or ?b in ?a, you need to bail - - if a == b { - Ok((LogicIpld::Var(a), BTreeSet::new())) - } else { - Err(()) + (None, Some(p)) => { + if p == '*' { + return true; } } - (LogicIpld::Var(lhs_tag), logic_ipld) => { - match logic_ipld { - rhs @ LogicIpld::Var(rhs_tag) => { - //FIXME double check - if let Some(b) = bindings.get(&rhs_tag) { - let (unified, mut new_bindings) = - LogicIpld::Var(a).unify(rhs.clone())?; - new_bindings.insert(rhs.clone()); - Ok((unified, new_bindings)) - } else { - let mut new_bindings = BTreeSet::new(); - new_bindings.insert(Variable(b.clone())); - Ok((LogicIpld::Var(a), new_bindings)) - } - } - - _ => { - let mut new_bindings = BTreeSet::new(); - new_bindings.insert(Variable(a.clone())); - Ok((logic_ipld, new_bindings)) - } - } + (None, None) => { + return true; } - (lhs, rhs @ LogicIpld::Var(_)) => rhs.unify(lhs, bindings), - - _ => Err(()), } } } -impl TryFrom for Ipld { - type Error = Variable; - - fn try_from(logic: LogicIpld) -> Result { - match logic { - LogicIpld::Null => Ok(Ipld::Null), - LogicIpld::Bool(b) => Ok(Ipld::Bool(b)), - LogicIpld::Float(f) => Ok(Ipld::Float(f)), - LogicIpld::Integer(i) => Ok(Ipld::Integer(i)), - LogicIpld::String(s) => Ok(Ipld::String(s)), - LogicIpld::Bytes(b) => Ok(Ipld::Bytes(b)), - LogicIpld::List(xs) => xs - .into_iter() - .try_fold(vec![], |mut acc, x| { - acc.push(Ipld::try_from(x)?); - Ok(acc) - }) - .map(Ipld::List), - LogicIpld::Map(btree) => btree - .into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - acc.insert(k, Ipld::try_from(v)?); - Ok(acc) - }) - .map(Ipld::Map), - LogicIpld::Var(var_id) => Err(Variable(var_id)), - } - } -} +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +// pub enum Stream { +// Every(BTreeMap), // "All or nothing" +// Some(BTreeMap), // FIXME disambiguate from Option::Some +// } +// +// impl Stream { +// pub fn remove(&mut self, key: usize) { +// match self { +// Stream::Every(xs) => { +// xs.remove(&key); +// } +// Stream::Some(xs) => { +// xs.remove(&key); +// } +// } +// } +// +// pub fn len(&self) -> usize { +// match self { +// Stream::Every(xs) => xs.len(), +// Stream::Some(xs) => xs.len(), +// } +// } +// +// pub fn iter(&self) -> impl Iterator { +// match self { +// Stream::Every(xs) => xs.iter(), +// Stream::Some(xs) => xs.iter(), +// } +// } +// +// pub fn to_btree(self) -> BTreeMap { +// match self { +// Stream::Every(xs) => xs, +// Stream::Some(xs) => xs, +// } +// } +// +// pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { +// match self { +// Stream::Every(xs) => { +// let updated = f(xs); +// Stream::Every(updated) +// } +// Stream::Some(xs) => { +// let updated = f(xs); +// Stream::Some(updated) +// } +// } +// } +// +// pub fn is_empty(&self) -> bool { +// match self { +// Stream::Every(xs) => xs.is_empty(), +// Stream::Some(xs) => xs.is_empty(), +// } +// } +// } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 88132b2b..f6509b76 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,6 +1,7 @@ use libipld_core::ipld::Ipld; +use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::{cmp::Ordering, path::PathBuf}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -43,6 +44,65 @@ use super::cid; #[serde(transparent)] pub struct Newtype(pub Ipld); +// impl Eq for Newtype {} +// +// impl PartialOrd for Newtype { +// fn partial_cmp(&self, other: &Self) -> Option { +// match (&self.0, &other.0) { +// (Ipld::Null, Ipld::Null) => Some(Ordering::Equal), +// (Ipld::Null, _) => Some(Ordering::Less), +// +// // +// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bool(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Float(lhs_f), Ipld::Float(rhs_f)) => { +// OrderedFloat(*lhs_f).partial_cmp(&OrderedFloat(*rhs_f)) +// } +// (lhs @ Ipld::Float(_), rhs) => Some(Ordering::Less), +// +// // +// (Ipld::Integer(lhs), Ipld::Integer(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Integer(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::String(lhs), Ipld::String(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::String(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Bytes(lhs), Ipld::Bytes(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bytes(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Link(lhs), Ipld::Link(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Link(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::List(lhs), Ipld::List(rhs)) => { +// let result = lhs.iter().zip(rhs).try_fold((), |acc, (l, r)| { +// match Newtype(*l).partial_cmp(&Newtype(*r)) { +// Some(Ordering::Equal) => Ok(()), +// Some(other) => Err(other), +// None => Err(Ordering::Less), +// } +// }); +// +// match result { +// Ok(()) => Some(Ordering::Equal), +// Err(comp) => Some(comp), +// } +// } +// (Ipld::List(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Map(lhs), Ipld::Map(rhs)) => None, +// (Ipld::Map(_), _) => Some(Ordering::Less), +// } +// } +// } + impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/ipld/number.rs b/src/ipld/number.rs index bfb5084c..a66f10d8 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -10,7 +10,7 @@ use serde_derive::{Deserialize, Serialize}; /// bounds checking in [`Condition`]s. /// /// [`Condition`]: crate::delegation::Condition -#[derive(Debug, Clone, PartialEq, PartialOrd, EnumAsInner, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number @@ -20,6 +20,17 @@ pub enum Number { Integer(i128), } +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Number::Float(a), Number::Float(b)) => a.partial_cmp(b), + (Number::Integer(a), Number::Integer(b)) => a.partial_cmp(b), + (Number::Float(a), Number::Integer(b)) => a.partial_cmp(&(*b as f64)), + (Number::Integer(a), Number::Float(b)) => (*a as f64).partial_cmp(b), + } + } +} + impl From for Ipld { fn from(number: Number) -> Self { number.into() From e00f68dee6aed5d502d40ca57628c85edc9466ef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 3 Mar 2024 14:59:34 -0800 Subject: [PATCH 171/234] selector parser --- Cargo.toml | 1 + src/delegation/policy.rs | 1 + src/delegation/policy/ir.rs | 172 +++++------------------- src/delegation/policy/selector.rs | 84 ++++++++++++ src/delegation/policy/selector/error.rs | 11 ++ src/delegation/policy/selector/op.rs | 142 +++++++++++++++++++ src/ipld/newtype.rs | 3 +- 7 files changed, 274 insertions(+), 140 deletions(-) create mode 100644 src/delegation/policy/selector.rs create mode 100644 src/delegation/policy/selector/error.rs create mode 100644 src/delegation/policy/selector/op.rs diff --git a/Cargo.toml b/Cargo.toml index 54384153..a3b4fcd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ base64 = "0.21" bs58 = "0.5" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" +nom = "7.1" # Web Stack did_url = "0.1" diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 30e6f251..525057de 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,4 @@ //pub mod frontend; //pub mod interpreter; pub mod ir; +pub mod selector; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index 3396d526..c520658b 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,9 +1,9 @@ //FIXME rename core +use super::selector::op::SelectorOp; use crate::ipld; -use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt}; +use std::collections::BTreeMap; impl Predicate { pub fn run(self, data: &Ipld) -> Result { @@ -36,15 +36,6 @@ impl Predicate { } } -pub enum RunError { - IndexOutOfBounds, - KeyNotFound, - NotAList, - NotAMap, - NotACollection, - NotANumber(>::Error), -} - trait Resolve { fn resolve(self, ctx: &Ipld) -> Result; } @@ -67,15 +58,6 @@ impl Resolve for Collection { } } -// impl Resolve for Collection { -// fn resolve(self, ctx: Ipld) -> Result { -// match self { -// Collection::Array(xs) => Ok(Ipld::List(xs)), -// Collection::Map(xs) => Ok(Ipld::Map(xs)), -// } -// } -// } - // FIXME Normal form? impl Resolve for String { @@ -84,17 +66,6 @@ impl Resolve for String { } } -pub struct Text(String); - -// impl TryFrom for String { -// fn try_from(ipld: Ipld) -> Result>::Error> { -// match ipld { -// Ipld::String(s) => Ok(s), -// _ => Err(()), -// } -// } -// } - impl SelectorOr { fn resolve(self, ctx: &Ipld) -> Result { match self { @@ -105,7 +76,7 @@ impl SelectorOr { seen_ops.push(op); match op { - SelectorOp::This => Ok((ipld, seen_ops)), + // SelectorOp::This => Ok((ipld, seen_ops)), SelectorOp::Try(inner) => { let op: SelectorOp = *inner.clone(); let ipld: Ipld = SelectorOr::Get::(vec![op]) @@ -114,25 +85,41 @@ impl SelectorOr { Ok((ipld, seen_ops)) } - SelectorOp::Index(i) => { - let result = match ipld { - Ipld::List(xs) => xs - .get(*i) - .ok_or(SelectorError { + SelectorOp::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError { + path: seen_ops + .iter() + .map(|op| (*op).clone()) + .collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }); + } + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError { + path: seen_ops + .iter() + .map(|op| (*op).clone()) + .collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError { path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }) - .cloned(), - // FIXME behaviour on maps? type error - _ => Err(SelectorError { - path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::NotAList, - }), + reason: SelectorErrorReason::NotAList, + }), + } }; Ok((result?, seen_ops)) } - SelectorOp::Key(k) => { + SelectorOp::Field(k) => { let result = match ipld { Ipld::Map(xs) => xs .get(k) @@ -240,7 +227,7 @@ pub enum SelectorErrorReason { // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Predicate { - // Booleans for connectives? FIXME + // Booleans True, False, @@ -286,37 +273,6 @@ impl Collection { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOp { - This, // . - Index(usize), // [2] - Key(String), // ["key"] (or .key) - Values, // .[] - Try(Box), // ? -} - -impl fmt::Display for SelectorOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SelectorOp::This => write!(f, "."), - SelectorOp::Index(i) => write!(f, "[{}]", i), - SelectorOp::Key(k) => { - if let Some(first) = k.chars().next() { - if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { - write!(f, ".{}", k) - } else { - write!(f, "[\"{}\"]", k) - } - } else { - write!(f, "[\"{}\"]", k) - } - } - SelectorOp::Values => write!(f, "[]"), - SelectorOp::Try(inner) => write!(f, "{}?", inner), - } - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Selector(pub Vec); @@ -359,63 +315,3 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } } - -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -// pub enum Stream { -// Every(BTreeMap), // "All or nothing" -// Some(BTreeMap), // FIXME disambiguate from Option::Some -// } -// -// impl Stream { -// pub fn remove(&mut self, key: usize) { -// match self { -// Stream::Every(xs) => { -// xs.remove(&key); -// } -// Stream::Some(xs) => { -// xs.remove(&key); -// } -// } -// } -// -// pub fn len(&self) -> usize { -// match self { -// Stream::Every(xs) => xs.len(), -// Stream::Some(xs) => xs.len(), -// } -// } -// -// pub fn iter(&self) -> impl Iterator { -// match self { -// Stream::Every(xs) => xs.iter(), -// Stream::Some(xs) => xs.iter(), -// } -// } -// -// pub fn to_btree(self) -> BTreeMap { -// match self { -// Stream::Every(xs) => xs, -// Stream::Some(xs) => xs, -// } -// } -// -// pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { -// match self { -// Stream::Every(xs) => { -// let updated = f(xs); -// Stream::Every(updated) -// } -// Stream::Some(xs) => { -// let updated = f(xs); -// Stream::Some(updated) -// } -// } -// } -// -// pub fn is_empty(&self) -> bool { -// match self { -// Stream::Every(xs) => xs.is_empty(), -// Stream::Some(xs) => xs.is_empty(), -// } -// } -// } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs new file mode 100644 index 00000000..20b1966e --- /dev/null +++ b/src/delegation/policy/selector.rs @@ -0,0 +1,84 @@ +pub mod error; +pub mod op; + +use error::ParseError; +use nom::{ + self, + branch::alt, + bytes::complete::tag, + character::complete::char, + combinator::map_res, + error::context, + multi::{many0, many1}, + sequence::preceded, + IResult, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Selector(Vec); + +pub fn parse(input: &str) -> IResult<&str, Selector> { + let without_this = many1(op::parse); + let with_this = preceded(char('.'), many0(op::parse)); + + // NOTE: must try without this first, to disambiguate `.field` from `.` + let p = map_res(alt((without_this, with_this)), |found| { + Ok::(Selector(found)) + }); + + context("selector", p)(input) +} + +pub fn parse_this(input: &str) -> IResult<&str, Selector> { + let p = map_res(tag("."), |_| Ok::(Selector(vec![]))); + context("this", p)(input) +} + +pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { + let p = many1(op::parse); + context("selector ops", p)(input) +} + +impl fmt::Display for Selector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut ops = self.0.iter(); + + if let Some(field) = ops.next() { + field.fmt(f)?; + } else { + write!(f, ".")?; + } + + for op in ops { + op.fmt(f)?; + } + + Ok(()) + } +} + +impl FromStr for Selector { + type Err = nom::Err; + + fn from_str(s: &str) -> Result { + match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { + ("", selector) => Ok(selector), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + } + } +} + +impl Serialize for Selector { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Selector { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Selector::from_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs new file mode 100644 index 00000000..b4e583ac --- /dev/null +++ b/src/delegation/policy/selector/error.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Serialize, Deserialize)] +pub enum ParseError { + #[error("unmatched trailing input")] + TrailingInput(String), + + #[error("unknown pattern: {0}")] + UnknownPattern(String), +} diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/op.rs new file mode 100644 index 00000000..26ca0499 --- /dev/null +++ b/src/delegation/policy/selector/op.rs @@ -0,0 +1,142 @@ +use super::error::ParseError; +use enum_as_inner::EnumAsInner; +use nom::{ + self, + branch::alt, + bytes::complete::tag, + character::complete::{alphanumeric1, anychar, char, digit1}, + combinator::{map_opt, map_res}, + error::context, + multi::many1, + sequence::{delimited, preceded, terminated}, + IResult, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr}; + +#[derive(Debug, Clone, PartialEq, EnumAsInner)] +pub enum SelectorOp { + // FIXME remove #[default] + // This, // . + ArrayIndex(i32), // [2] + Field(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? +} + +impl fmt::Display for SelectorOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + // SelectorOp::This => write!(f, "."), + SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), + SelectorOp::Field(k) => { + if let Some(first) = k.chars().next() { + if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + write!(f, ".{}", k) + } else { + write!(f, "[\"{}\"]", k) + } + } else { + write!(f, "[\"{}\"]", k) + } + } + SelectorOp::Values => write!(f, "[]"), + SelectorOp::Try(inner) => write!(f, "{}?", inner), + } + } +} + +pub fn parse(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_try, parse_non_try)); + context("selector_op", p)(input) +} + +pub fn parse_non_try(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_values, parse_array_index, parse_field)); + context("non_try", p)(input) +} + +pub fn parse_try(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(terminated(parse_non_try, tag("?")), |found: SelectorOp| { + Ok::(SelectorOp::Try(Box::new(found))) + }); + + context("try", p)(input) +} + +pub fn parse_array_index(input: &str) -> IResult<&str, SelectorOp> { + let num = map_opt(tag("-"), |s: &str| { + let (_rest, matched) = digit1::<&str, ()>(s).ok()?; + matched.parse::().ok() + }); + + let array_index = map_res(delimited(char('['), num, char(']')), |idx| { + Ok::(SelectorOp::ArrayIndex(idx)) + }); + + context("array_index", array_index)(input) +} + +pub fn parse_values(input: &str) -> IResult<&str, SelectorOp> { + context("values", tag("[]"))(input).map(|(rest, _)| (rest, SelectorOp::Values)) +} + +pub fn parse_field(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_delim_field, parse_dot_field)); + + context("map_field", p)(input) +} + +pub fn parse_dot_field(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); + context("dot_field", p)(input) +} + +pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { + Ok::(SelectorOp::Field(found.to_string())) + }); + + context("dot_field", p)(input) +} + +pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { + let key = format!("{}{}", '_', found); + Ok::(SelectorOp::Field(key)) + }); + + context("dot_field", p)(input) +} + +pub fn parse_delim_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { + let field = String::from_iter(found); + Ok::(SelectorOp::Field(field)) + }); + + context("delimited_field", p)(input) +} + +impl FromStr for SelectorOp { + type Err = nom::Err; + + fn from_str(s: &str) -> Result { + match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { + ("", found) => Ok(found), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + } + } +} +impl Serialize for SelectorOp { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for SelectorOp { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f6509b76..f202599b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,7 +1,6 @@ use libipld_core::ipld::Ipld; -use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, path::PathBuf}; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; From 3143c397e7dd857aecae8f1ba0595bda8b56654f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 3 Mar 2024 22:52:28 -0800 Subject: [PATCH 172/234] Swap all files to predicates from condition --- src/ability/arguments/named.rs | 6 - src/ability/pipe.rs | 38 +- src/ability/ucan/pipe.rs | 11 - src/delegation.rs | 60 ++- src/delegation/agent.rs | 23 +- src/delegation/condition.rs | 78 ---- src/delegation/condition/contains_all.rs | 46 -- src/delegation/condition/contains_any.rs | 45 -- src/delegation/condition/contains_key.rs | 69 --- src/delegation/condition/excludes_all.rs | 85 ---- src/delegation/condition/excludes_key.rs | 72 ---- src/delegation/condition/matches_regex.rs | 76 ---- src/delegation/condition/max_length.rs | 44 -- src/delegation/condition/max_number.rs | 49 --- src/delegation/condition/min_length.rs | 44 -- src/delegation/condition/min_number.rs | 49 --- src/delegation/condition/traits.rs | 11 - src/delegation/payload.rs | 34 +- src/delegation/policy.rs | 4 +- src/delegation/policy/collection.rs | 36 ++ src/delegation/policy/frontend.rs | 62 --- src/delegation/policy/interpreter.rs | 485 ---------------------- src/delegation/policy/ir.rs | 281 ++----------- src/delegation/policy/predicate.rs | 144 +++++++ src/delegation/policy/selector.rs | 25 +- src/delegation/policy/selector/error.rs | 24 ++ src/delegation/policy/selector/op.rs | 22 +- src/delegation/policy/selector/or.rs | 113 +++++ src/delegation/store/memory.rs | 24 +- src/delegation/store/traits.rs | 25 +- src/invocation/agent.rs | 26 +- src/invocation/payload.rs | 34 +- src/ipld/number.rs | 20 +- src/lib.rs | 4 - src/proof.rs | 12 - src/proof/checkable.rs | 17 - src/proof/error.rs | 45 -- src/proof/internal.rs | 3 - src/proof/parentful.rs | 189 --------- src/proof/parentless.rs | 108 ----- src/proof/parents.rs | 22 - src/proof/prove.rs | 37 -- src/proof/same.rs | 68 --- src/receipt/payload.rs | 2 +- 44 files changed, 521 insertions(+), 2151 deletions(-) delete mode 100644 src/ability/ucan/pipe.rs delete mode 100644 src/delegation/condition.rs delete mode 100644 src/delegation/condition/contains_all.rs delete mode 100644 src/delegation/condition/contains_any.rs delete mode 100644 src/delegation/condition/contains_key.rs delete mode 100644 src/delegation/condition/excludes_all.rs delete mode 100644 src/delegation/condition/excludes_key.rs delete mode 100644 src/delegation/condition/matches_regex.rs delete mode 100644 src/delegation/condition/max_length.rs delete mode 100644 src/delegation/condition/max_number.rs delete mode 100644 src/delegation/condition/min_length.rs delete mode 100644 src/delegation/condition/min_number.rs delete mode 100644 src/delegation/condition/traits.rs create mode 100644 src/delegation/policy/collection.rs delete mode 100644 src/delegation/policy/frontend.rs delete mode 100644 src/delegation/policy/interpreter.rs create mode 100644 src/delegation/policy/predicate.rs create mode 100644 src/delegation/policy/selector/or.rs delete mode 100644 src/proof.rs delete mode 100644 src/proof/checkable.rs delete mode 100644 src/proof/error.rs delete mode 100644 src/proof/internal.rs delete mode 100644 src/proof/parentful.rs delete mode 100644 src/proof/parentless.rs delete mode 100644 src/proof/parents.rs delete mode 100644 src/proof/prove.rs delete mode 100644 src/proof/same.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 0120da7b..7b5baa5f 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -150,12 +150,6 @@ impl> TryFrom for Named { } } -// impl From> for Named { -// fn from(map: BTreeMap) -> Self { -// Named(map) -// } -// } - impl> From> for Ipld { fn from(arguments: Named) -> Self { Ipld::Map( diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 32ab4744..55a80555 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,38 +1,22 @@ -use crate::{crypto::varsig, delegation, delegation::condition::Condition, did::Did, ipld}; +use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - pub source: Cap, - pub sink: Cap, +pub struct Pipe, Enc: Codec + TryFrom + Into> { + pub source: Cap, + pub sink: Cap, } -pub enum Cap, Enc: Codec + TryFrom + Into> -{ - Chain(delegation::Chain), +pub enum Cap, Enc: Codec + TryFrom + Into> { + Chain(delegation::Chain), Literal(Ipld), } -pub struct PromisedPipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - pub source: PromisedCap, - pub sink: PromisedCap, +pub struct PromisedPipe, Enc: Codec + TryFrom + Into> { + pub source: PromisedCap, + pub sink: PromisedCap, } -pub enum PromisedCap< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - Chain(delegation::Chain), +pub enum PromisedCap, Enc: Codec + TryFrom + Into> { + Chain(delegation::Chain), Promised(ipld::Promised), } diff --git a/src/ability/ucan/pipe.rs b/src/ability/ucan/pipe.rs deleted file mode 100644 index 2d79fcf8..00000000 --- a/src/ability/ucan/pipe.rs +++ /dev/null @@ -1,11 +0,0 @@ - use crate::delegation; - -pub struct Pipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > { - pub from: delegation::Chain, - pub to: delegation::Chain, -} diff --git a/src/delegation.rs b/src/delegation.rs index bd0d975f..852b61b9 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,18 +1,17 @@ -//! An [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. +//! A [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. //! //! ## Data //! //! - [`Delegation`] is the top-level, signed data struture. //! - [`Payload`] is the fields unique to an invocation. //! - [`Preset`] is an [`Delegation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). -//! - [`Condition`]s are syntactically-driven validation rules for [`Delegation`]s. +//! - [`Predicate`]s are syntactically-driven validation rules for [`Delegation`]s. //! //! ## Stateful Helpers //! //! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. //! - [`store`] is an interface for caching [`Delegation`]s. -pub mod condition; pub mod policy; pub mod store; @@ -22,19 +21,18 @@ mod payload; pub use agent::Agent; pub use payload::*; -use crate::capsule::Capsule; use crate::{ - // ability, + capsule::Capsule, crypto::{signature, varsig, Nonce}, did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use condition::Condition; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, ipld::Ipld, }; +use policy::predicate::Predicate; use std::collections::BTreeMap; use web_time::SystemTime; @@ -45,39 +43,29 @@ use web_time::SystemTime; /// # Examples /// FIXME #[derive(Clone, Debug, PartialEq)] -pub struct Delegation< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); +pub struct Delegation, Enc: Codec + TryFrom + Into>( + pub signature::Envelope, DID, V, Enc>, +); #[derive(Clone, Debug, PartialEq)] -pub struct Chain< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(Vec>); - -impl, Enc: Codec + TryFrom + Into> Capsule - for Chain +pub struct Chain, Enc: Codec + TryFrom + Into>( + Vec>, +); + +impl, Enc: Codec + TryFrom + Into> Capsule + for Chain { const TAG: &'static str = "ucan/chain"; } /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Delegation< - condition::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; +pub type Preset = + Delegation; // FIXME checkable -> provable? -impl, Enc: Codec + Into + TryFrom> - Delegation +impl, Enc: Codec + Into + TryFrom> + Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { @@ -94,8 +82,8 @@ impl, Enc: Codec + Into + Tr &self.0.payload.audience } - /// Retrive the `condition` of a [`Delegation`] - pub fn policy(&self) -> &[C] { + /// Retrive the `policy` of a [`Delegation`] + pub fn policy(&self) -> &Vec { &self.0.payload.policy } @@ -123,7 +111,7 @@ impl, Enc: Codec + Into + Tr self.0.payload.check_time(now) } - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -148,7 +136,7 @@ impl, Enc: Codec + Into + Tr pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone + Encode, Ipld: Encode, { self.0.cid() @@ -156,7 +144,7 @@ impl, Enc: Codec + Into + Tr pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where - Payload: Clone, + Payload: Clone, Ipld: Encode, { self.0.validate_signature() @@ -165,11 +153,11 @@ impl, Enc: Codec + Into + Tr pub fn try_sign( signer: &DID::Signer, varsig_header: V, - payload: Payload, + payload: Payload, ) -> Result where Ipld: Encode, - Payload: Clone, + Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 971a6e97..1e399e43 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,8 +1,7 @@ -use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use super::{payload::Payload, policy::predicate::Predicate, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, - // proof::checkable::Checkable, time::Timestamp, }; use libipld_core::{ @@ -20,9 +19,8 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - C: Condition, DID: Did, - S: Store, + S: Store, V: varsig::Header, Enc: Codec + TryFrom + Into, > { @@ -33,17 +31,16 @@ pub struct Agent< pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(C, V, Enc)>, + _marker: PhantomData<(V, Enc)>, } impl< 'a, - C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, + S: Store + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Agent<'a, C, DID, S, V, Enc> + > Agent<'a, DID, S, V, Enc> where Ipld: Encode, { @@ -61,19 +58,19 @@ where audience: DID, subject: Option, command: String, - new_policy: Vec, + new_policy: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); if let Some(ref sub) = subject { if sub == self.did { - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -103,7 +100,7 @@ where let mut policy = to_delegate.policy.clone(); policy.append(&mut new_policy.clone()); - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -121,7 +118,7 @@ where pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs deleted file mode 100644 index 207a181e..00000000 --- a/src/delegation/condition.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Policies for syntactic validation of abilities in [`Delegation`][super::Delegation]s. - -mod contains_all; -mod contains_any; -mod contains_key; -mod excludes_all; -mod excludes_key; -mod matches_regex; -mod max_length; -mod max_number; -mod min_length; -mod min_number; -mod traits; - -pub use contains_all::ContainsAll; -pub use contains_any::ContainsAny; -pub use contains_key::ContainsKey; -pub use excludes_all::ExcludesAll; -pub use excludes_key::ExcludesKey; -pub use matches_regex::MatchesRegex; -pub use max_length::MaxLength; -pub use max_number::MaxNumber; -pub use min_length::MinLength; -pub use min_number::MinNumber; -pub use traits::Condition; - -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -/// The union of the common [`Condition`]s that ship directly with this library. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[allow(missing_docs)] -pub enum Preset { - ContainsAll(contains_all::ContainsAll), - ContainsAny(contains_any::ContainsAny), - ContainsKey(contains_key::ContainsKey), - ExcludesKey(excludes_key::ExcludesKey), - ExcludesAll(excludes_all::ExcludesAll), - MinLength(min_length::MinLength), - MaxLength(max_length::MaxLength), - MinNumber(min_number::MinNumber), - MaxNumber(max_number::MaxNumber), - MatchesRegex(matches_regex::MatchesRegex), -} - -impl From for Ipld { - fn from(common: Preset) -> Self { - common.into() - } -} - -impl TryFrom for Preset { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for Preset { - fn validate(&self, args: &arguments::Named) -> bool { - match self { - Preset::ContainsAll(c) => c.validate(args), - Preset::ContainsAny(c) => c.validate(args), - Preset::ContainsKey(c) => c.validate(args), - Preset::ExcludesKey(c) => c.validate(args), - Preset::ExcludesAll(c) => c.validate(args), - Preset::MinLength(c) => c.validate(args), - Preset::MaxLength(c) => c.validate(args), - Preset::MinNumber(c) => c.validate(args), - Preset::MaxNumber(c) => c.validate(args), - Preset::MatchesRegex(c) => c.validate(args), - } - } -} diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs deleted file mode 100644 index d0882e64..00000000 --- a/src/delegation/condition/contains_all.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! A [`Condition`] for ensuring a field contains all of a set of values. - -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A condition for ensuring a field contains all of a set of values. -/// -/// This works on lists and maps. Maps will check the values, not keys. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAll { - /// Name of the field to check - pub field: String, - - /// The elements that must be present - pub contains_all: Vec, -} - -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - contains_all.into() - } -} - -impl TryFrom for ContainsAll { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsAll { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), - Some(Ipld::Map(btree)) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.contains_all.iter().all(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs deleted file mode 100644 index 46af682f..00000000 --- a/src/delegation/condition/contains_any.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! A [`Condition`] for ensuring a field contains some of a set of values. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a field contains one or more of a set of values. -/// -/// This works on lists and maps. Maps will check the values, not keys. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAny { - /// Name of the field to check. - pub field: String, - - /// The elements that must be present. - pub contains_any: Vec, -} - -impl From for Ipld { - fn from(contains_any: ContainsAny) -> Self { - contains_any.into() - } -} - -impl TryFrom for ContainsAny { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsAny { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), - Some(Ipld::Map(btree)) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.contains_any.iter().any(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs deleted file mode 100644 index df6844df..00000000 --- a/src/delegation/condition/contains_key.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! A [`Condition`] for ensuring a map contains a key. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a map contains a key. -/// -/// Note that this operates on a key inside the args, not the args themselves. -/// The shape of an [`ability`][crate::ability] is pretermined, so further -/// constraining the top-level argument keys is not necessary. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ContainsKey, Condition}}; -/// # use libipld::ipld; -/// # -/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); -/// let cond = ContainsKey{ -/// field: "a".into(), -/// key: "b".into() -/// }; -/// -/// assert!(cond.validate(&args)); -/// -/// // Fails when the key is not present -/// assert!(!ContainsKey { -/// field: "nope".into(), -/// key: "b".into() -/// }.validate(&args)); -/// -/// // Also fails when the input is not a map -/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); -/// assert!(!cond.validate(&list)); -/// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsKey { - /// Name of the field to check. - pub field: String, - - /// The elements that must be present. - pub key: String, -} - -impl From for Ipld { - fn from(contains_key: ContainsKey) -> Self { - contains_key.into() - } -} - -impl TryFrom for ContainsKey { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsKey { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Map(map)) => map.contains_key(&self.key), - _ => false, - } - } -} diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs deleted file mode 100644 index fff743e4..00000000 --- a/src/delegation/condition/excludes_all.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! A [`Condition`] for ensuring a field contains none of a set of values. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a field contains none of a set of values. -/// -/// This works on all [`Ipld`] types. For lists and maps, it checks the values, not keys. -/// For the rest, it checks the value itself. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ExcludesAll, Condition}}; -/// # use libipld::ipld; -/// # -/// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); -/// let cond = ExcludesAll { -/// field: "a".into(), -/// excludes_all: vec![ipld!(2), ipld!("a")] -/// }; -/// -/// assert!(cond.validate(&args)); -/// -/// // Fails when the values of a map match -/// assert!(!cond.validate(&ipld!({"a": {"b": 2}}).try_into().unwrap())); -/// -/// // Succeeds when the key is not present -/// assert!(ExcludesAll { -/// field: "nope".into(), -/// excludes_all: vec![ipld!(1), ipld!("b")] -/// }.validate(&args)); -/// -/// // Also checks non-maps/non-lists -/// assert!(!cond.validate(&ipld!({"a": 2}).try_into().unwrap())); -/// assert!(cond.validate(&ipld!({"a": "hello world"}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesAll { - /// Name of the field to check. - pub field: String, - - /// The elements that must not be present. - pub excludes_all: Vec, -} - -impl From for Ipld { - fn from(excludes_all: ExcludesAll) -> Self { - excludes_all.into() - } -} - -impl TryFrom for ExcludesAll { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ExcludesAll { - fn validate(&self, args: &arguments::Named) -> bool { - if let Some(ipld) = args.get(&self.field) { - let mut it = self.excludes_all.iter(); - match ipld { - Ipld::Null => it.all(|x| x != ipld), - Ipld::Bool(_) => it.all(|x| x != ipld), - Ipld::Float(_) => it.all(|x| x != ipld), - Ipld::Integer(_) => it.all(|x| x != ipld), - Ipld::Bytes(_) => it.all(|x| x != ipld), - Ipld::String(_) => it.all(|x| x != ipld), - Ipld::Link(_) => it.all(|x| x != ipld), - Ipld::List(array) => it.all(|x| !array.contains(x)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - it.all(|x| !vals.contains(&x)) - } - } - } else { - true - } - } -} diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs deleted file mode 100644 index 09ec8523..00000000 --- a/src/delegation/condition/excludes_key.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! A [`Condition`] for ensuring a field contains none of a set of keys. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a map excludes a key. -/// -/// Note that this operates on a key inside the args, not the args themselves. -/// The shape of an [`ability`][crate::ability] is pretermined, so further -/// constraining the top-level argument keys is not necessary. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; -/// # use libipld::ipld; -/// # -/// let cond = ExcludesKey{ -/// field: "a".into(), -/// key: "b".into() -/// }; -/// -/// let args1 = ipld!({"a": "b", "c": "d"}).try_into().unwrap(); -/// let args2 = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); -/// -/// assert!(cond.validate(&args1)); -/// assert!(!cond.validate(&args2)); -/// -/// // Succeeds when the key is not present -/// assert!(ExcludesKey { -/// field: "nope".into(), -/// key: "b".into() -/// }.validate(&args2)); -/// -/// // Also succeeds when the input is not a map -/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); -/// assert!(cond.validate(&list)); -/// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesKey { - /// Name of the field to check. - pub field: String, - - /// The key that must not be present. - pub key: String, -} - -impl From for Ipld { - fn from(excludes_key: ExcludesKey) -> Self { - excludes_key.into() - } -} - -impl TryFrom for ExcludesKey { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ExcludesKey { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Map(map)) => !map.contains_key(&self.key), - _ => true, - } - } -} diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs deleted file mode 100644 index 4b7c9426..00000000 --- a/src/delegation/condition/matches_regex.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! A regular expression [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use regex::Regex; -use serde_derive::{Deserialize, Serialize}; - -/// A regular expression [`Condition`] -/// -/// This checks a string against a regular expression. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MatchesRegex { - /// Name of the field to check - pub field: String, - - /// The minimum length - pub matches_regex: Matcher, -} - -impl From for Ipld { - fn from(matches_regex: MatchesRegex) -> Self { - matches_regex.into() - } -} - -impl TryFrom for MatchesRegex { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MatchesRegex { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), - _ => false, - } - } -} - -/// A newtype wrapper around [`Regex`] -#[derive(Debug, Clone)] -pub struct Matcher(Regex); - -impl PartialEq for Matcher { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for Matcher {} - -impl serde::Serialize for Matcher { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.as_str().serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for Matcher { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &str = serde::Deserialize::deserialize(deserializer)?; - match Regex::new(s) { - Ok(regex) => Ok(Matcher(regex)), - Err(_) => Err(serde::de::Error::custom(format!("Invalid regex: {}", s))), - } - } -} diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs deleted file mode 100644 index 94b0a8f5..00000000 --- a/src/delegation/condition/max_length.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A max length [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A maximum length [`Condition`] -/// -/// A condition that checks if the length of a string, list, -/// or map is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxLength { - /// Name of the field to check - pub field: String, - - /// The maximum length - pub max_length: usize, -} - -impl From for Ipld { - fn from(max_length: MaxLength) -> Self { - max_length.into() - } -} - -impl TryFrom for MaxLength { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MaxLength { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => string.len() <= self.max_length, - Some(Ipld::List(list)) => list.len() <= self.max_length, - Some(Ipld::Map(map)) => map.len() <= self.max_length, - _ => false, - } - } -} diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs deleted file mode 100644 index 41cc6eb0..00000000 --- a/src/delegation/condition/max_number.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! A max number [`Condition`]. -use super::traits::Condition; -use crate::{ability::arguments, ipld::Number}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A maximum number [`Condition`] -/// -/// A condition that checks if the length of an integer -/// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxNumber { - /// Name of the field to check - pub field: String, - - /// The maximum number - pub max_number: Number, -} - -impl From for Ipld { - fn from(max_number: MaxNumber) -> Self { - max_number.into() - } -} - -impl TryFrom for MaxNumber { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MaxNumber { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Integer(integer)) => match self.max_number { - Number::Float(float) => *integer as f64 <= float, - Number::Integer(integer) => integer <= integer, - }, - Some(Ipld::Float(float)) => match self.max_number { - Number::Float(float) => float <= float, - Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs deleted file mode 100644 index 5d137df7..00000000 --- a/src/delegation/condition/min_length.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A min length [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A mimimum length [`Condition`] -/// -/// This checks if the length of a string, list, -/// or map is greater than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinLength { - /// Name of the field to check - pub field: String, - - /// The minimum length - pub min_length: usize, -} - -impl From for Ipld { - fn from(min_length: MinLength) -> Self { - min_length.into() - } -} - -impl TryFrom for MinLength { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MinLength { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => string.len() >= self.min_length, - Some(Ipld::List(list)) => list.len() >= self.min_length, - Some(Ipld::Map(map)) => map.len() >= self.min_length, - _ => false, - } - } -} diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs deleted file mode 100644 index 996c5f81..00000000 --- a/src/delegation/condition/min_number.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! A min number [`Condition`]. -use super::traits::Condition; -use crate::{ability::arguments, ipld::Number}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A minimum number [`Condition`] -/// -/// A condition that checks if the length of an integer -/// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinNumber { - /// Name of the field to check - pub field: String, - - /// The minimum number - pub min_number: Number, -} - -impl From for Ipld { - fn from(min_number: MinNumber) -> Self { - min_number.into() - } -} - -impl TryFrom for MinNumber { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MinNumber { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Integer(integer)) => match self.min_number { - Number::Float(float) => *integer as f64 >= float, - Number::Integer(integer) => integer >= integer, - }, - Some(Ipld::Float(float)) => match self.min_number { - Number::Float(float) => float >= float, - Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs deleted file mode 100644 index f1fcf7a6..00000000 --- a/src/delegation/condition/traits.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Traits for abstracting over policies. - -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::fmt; - -/// A trait for policies that can be run on named IPLD arguments. -pub trait Condition: fmt::Debug { - /// Check that some condition is met on named IPLD arguments. - fn validate(&self, args: &arguments::Named) -> bool; -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8247384b..0b9c33ad 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::condition::Condition; +use super::policy::predicate::Predicate; use crate::{ capsule::Capsule, crypto::Nonce, @@ -21,7 +21,7 @@ use crate::ipld; /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload { +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -48,8 +48,8 @@ pub struct Payload { /// The command being delegated. pub command: String, - /// Any [`Condition`]s on the `ability_builder`. - pub policy: Vec, + /// Any [`Predicate`] policies that constrain the `args` on an [`Invocation`][crate::invocation::Invocation]. + pub policy: Vec, /// Extensible, free-form fields. pub metadata: BTreeMap, @@ -73,7 +73,7 @@ pub struct Payload { pub not_before: Option, } -impl Payload { +impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); @@ -91,19 +91,17 @@ impl Payload { } } -impl Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0"; +impl Capsule for Payload { + const TAG: &'static str = "ucan/d@1.0.0-rc.1"; } -impl Verifiable for Payload { +impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer } } -impl Deserialize<'de>, DID: Did + for<'de> Deserialize<'de>> TryFrom - for Payload -{ +impl Deserialize<'de>> TryFrom for Payload { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -111,23 +109,21 @@ impl Deserialize<'de>, DID: Did + for<'de> Deserialize<' } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where - C::Strategy: 'static, DID::Parameters: Clone, - C::Parameters: Clone, { - type Parameters = (DID::Parameters, C::Parameters); + type Parameters = (DID::Parameters, ::Parameters); type Strategy = BoxedStrategy; - fn arbitrary_with((did_args, c_args): Self::Parameters) -> Self::Strategy { + fn arbitrary_with((did_args, pred_args): Self::Parameters) -> Self::Strategy { ( Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), @@ -141,7 +137,7 @@ where .map(|(k, v)| (k, v.0)) .collect::>() }), - prop::collection::vec(C::arbitrary_with(c_args), 0..10), + prop::collection::vec(Predicate::arbitrary_with(pred_args), 0..10), ) .prop_map( |( diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 525057de..25985d64 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,4 +1,4 @@ -//pub mod frontend; -//pub mod interpreter; +pub mod collection; pub mod ir; +pub mod predicate; pub mod selector; diff --git a/src/delegation/policy/collection.rs b/src/delegation/policy/collection.rs new file mode 100644 index 00000000..1cab0451 --- /dev/null +++ b/src/delegation/policy/collection.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; +use crate::ipld; +use std::collections::BTreeMap; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), +} + +impl Collection { + pub fn to_vec(self) -> Vec { + match self { + Collection::Array(xs) => xs, + Collection::Map(xs) => xs.into_values().collect(), + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Collection { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + prop::collection::vec(ipld::Newtype::arbitrary(), 0..10).prop_map(Collection::Array), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..10) + .prop_map(Collection::Map), + ] + .boxed() + } +} diff --git a/src/delegation/policy/frontend.rs b/src/delegation/policy/frontend.rs deleted file mode 100644 index 4ac89bb9..00000000 --- a/src/delegation/policy/frontend.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::ir; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Term { - // Leaves - Args, // $ - Literal(Ipld), - Variable(Variable), - - Selector(Selector), - - // Connectives - Not(Box), - And(Vec), - Or(Vec), - - // Comparison - Equal(Value, Value), - GreaterThan(Value, Value), - GreaterOrEqual(Value, Value), - LessThan(Value, Value), - LessOrEqual(Value, Value), - - // String Matcher - Glob(Value, String), - - // Existential Quantification - Exists(Variable, Collection), // ∃x ∈ xs -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Variable(String); // ?x - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), - Variable(Variable), - Selector(Selector), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(Vec); // .foo.bar[].baz - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Index { - This, - // RecDesend, // .. - FlattenAll, // .[] - Index(usize), // .[2] - Key(String), // .key -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Literal(Ipld), - Variable(Variable), - ImplicitBind(Selector), -} diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs deleted file mode 100644 index 7b378937..00000000 --- a/src/delegation/policy/interpreter.rs +++ /dev/null @@ -1,485 +0,0 @@ -use super::ir::*; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -// [and ["==", ".foo", "?x"] -// [">", "?x", 0] -// [">", "?x", 2] -// ["==", 10, 11] // Fails, so whole thing fails? -// ["or", ["==", ".bar", "?y"] -// [">", "?y", 12] -// ["and", ["<", "?x", 100] -// ["<", "?y", 100] -// ] -// ["every", "?x", "?e"] -// ] -// ["==", 22, "?e"] -// ["some", "?x" "?a"] -// ["==", "?a", [1, 2, "?z", 4]] -// ["==", ["?b", "?c", 20, 30], [10, "?a", 20, 30]] // -> b = 10, c = a, a = c -// ] - -// [".[]", "$", ?x] -// [".[]", "$", ?y] -// ["==", "?x", "?y"] -// ["==", "?y", "?x"] - -// Register machine -// { -// ports: { -// "?a": Stream<>, -// "?b": Stream<>, -// } -// } - -#[derive(Debug, Clone, PartialEq)] -pub struct Machine { - pub args: arguments::Named, - pub frames: BTreeMap, - pub program: Statement, - pub index_counter: usize, -} - -pub fn run(machine: Machine) -> Machine { - todo!() - // run to exhaustion - // loop { - // if let Ok(next) = run_once(&machine) { - // if next == &machine { - // return machine; - // } - // } else { - // panic!("failed some step"); - // } - // } -} - -pub fn step(mut context: Machine) -> Result { - // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom - // or at least that's one startegy - match context.clone().program { - Statement::And(left, right) => { - let lhs = Machine { - program: *left, - ..context.clone() - }; - - let lhs_result = run(lhs); - - let rhs = Machine { - args: context.args, - frames: lhs_result.frames, - program: *right, - index_counter: lhs_result.index_counter, - }; - - let rhs_result = run(rhs); - - if rhs_result.frames.is_empty() { - Err(()) - } else { - Ok(rhs_result) - } - } - Statement::Or(left, right) => { - let lhs = Machine { - program: *left, - ..context.clone() - }; - - let rhs = Machine { - program: *right, - ..context.clone() - }; - - let lhs_result = run(lhs); - let rhs_result = run(rhs); - - let merged_frames = lhs_result - .frames - .into_iter() - .map(|(key, lhs_stream)| { - let rhs_stream = rhs_result - .frames - .get(&key) - .cloned() - .unwrap_or(lhs_stream.clone()); - - let merged = match (lhs_stream, rhs_stream) { - (Stream::Every(lhs), Stream::Every(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Some(lhs), Stream::Some(rhs)) => { - Stream::Some(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Every(lhs), Stream::Some(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Some(lhs), Stream::Every(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - }; - - (key, merged) - }) - .collect(); - - Ok(Machine { - frames: merged_frames, - ..context - }) - } - Statement::Not(statement) => { - let next = Machine { - program: *statement, - ..context.clone() - }; - - let not_results = run(next); - - for (idx, _) in not_results.frames.iter() { - context.frames.remove(idx); - } - - Ok(context) - } - Statement::Exists(var, collection) => { - let btree: BTreeMap = match collection { - Collection::Array(vec) => vec - .into_iter() - .map(|ipld| (context.next_index(), ipld)) - .collect(), - - Collection::Map(map) => map - .into_iter() - .map(|(_k, ipld)| (context.next_index(), ipld)) - .collect(), - }; - - context.frames.insert(var.0, Stream::Some(btree)); - Ok(context) - } - Statement::Forall(var, collection) => { - let btree: BTreeMap = match collection { - Collection::Array(vec) => vec - .into_iter() - .map(|ipld| (context.next_index(), ipld)) - .collect(), - - Collection::Map(map) => map - .into_iter() - .map(|(_k, ipld)| (context.next_index(), ipld)) - .collect(), - }; - - context.frames.insert(var.0, Stream::Every(btree)); - - // FIXME needs to check that nothing changed - // ...perhaps at the end of the iteration, loop through the streams? - - Ok(context) - } - Statement::Equal(left, right) => context // FIXME do unification - .apply(&left, &right, |a, b| a == b) - .map(|()| context), - Statement::GreaterThan(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a > b, - (Ipld::Float(a), Ipld::Float(b)) => a > b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) > *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a > (*b as f64), - _ => false, - }) - .map(|()| context), - Statement::LessThan(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a < b, - (Ipld::Float(a), Ipld::Float(b)) => a < b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) < *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a < (*b as f64), - _ => false, - }) - .map(|()| context), - Statement::GreaterThanOrEqual(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a >= b, - (Ipld::Float(a), Ipld::Float(b)) => a >= b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) >= *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a >= (*b as f64), - _ => false, - }) - .map(|()| context), - - Statement::LessThanOrEqual(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a <= b, - (Ipld::Float(a), Ipld::Float(b)) => a <= b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) <= *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a <= (*b as f64), - _ => false, - }) - .map(|()| context), - - Statement::Glob(left, right) => context - .apply(&left, &right, |a, b| glob(a, b)) - .map(|()| context), - Statement::Select(selector, target, var) => match target { - SelectorValue::Args => { - let ipld = Ipld::Map(context.args.clone().0); - let selected = select(selector, ipld)?; - let idx = context.next_index(); - - context - .frames - .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, selected)]))); - - Ok(context) - } - SelectorValue::Literal(ipld) => { - let ipld = select(selector, ipld)?; - let idx = context.next_index(); - - context - .frames - .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, ipld)]))); - - Ok(context) - } - SelectorValue::Variable(var_id) => { - let current = context - .frames - .get(&var_id.0) - .cloned() - .unwrap_or(Stream::Every(BTreeMap::new())); - - let result: Result, ()> = current - .clone() - .to_btree() - .into_iter() - .map(|(idx, ipld)| select(selector.clone(), ipld).map(|ipld| (idx, ipld))) - .collect(); - - let updated = result?; - - context - .frames - .insert(var.0, current.map(|_| updated.clone())); - - Ok(context) - } - }, - } -} - -pub fn select(selector: Selector, on: Ipld) -> Result { - let results: Vec = selector - .0 - .iter() - .try_fold(vec![on], |ipld_stream, segment| match segment { - PathSegment::This => Ok(ipld_stream), - PathSegment::Index(i) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - if let Some(ipld) = vec.get(*i) { - acc.push(ipld.clone()); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::Key(key) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::Map(map) => { - if let Some(ipld) = map.get(key) { - acc.push(ipld.clone()); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::FlattenAll => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - acc.extend(vec.clone()); - Ok(acc.iter().cloned().collect()) - } - _ => Err(()), - }) - } - })?; - - match &results[..] { - [ipld] => Ok(ipld.clone()), - vec => Ok(Ipld::List( - vec.into_iter().map(|ipld| ipld.clone()).collect(), - )), - } -} - -impl Machine { - pub fn next_index(&mut self) -> usize { - let prev = self.index_counter; - self.index_counter += 1; - prev - } - - pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> - where - F: Fn(&Ipld, &Ipld) -> bool, - { - match lhs { - Value::Literal(left_ipld) => match rhs { - Value::Literal(right_ipld) => { - if f(left_ipld, right_ipld) { - Ok(()) - } else { - Err(()) - } - } - Value::Variable(var_id) => { - let key = var_id.0.clone(); - - if let Some(stream) = self.frames.get(&key) { - let updated = stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(left_ipld, ipld)) - .collect() - }); - - match updated { - Stream::Every(ref btree) => { - if btree.len() < stream.len() { - return Err(()); - } - } - Stream::Some(ref btree) => { - if btree.is_empty() { - return Err(()); - } - } - } - - self.frames.insert(key, updated); - Ok(()) - } else { - Err(()) - } - } - }, - Value::Variable(var_id) => { - let lhs_key = &var_id.0; - - if let Some(lhs_stream) = self.frames.get(lhs_key) { - match rhs { - Value::Literal(right_ipld) => { - let updated = lhs_stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(ipld, right_ipld)) - .collect() - }); - - match updated { - Stream::Every(ref btree) => { - if btree.len() < lhs_stream.len() { - return Err(()); - } - } - Stream::Some(ref btree) => { - if btree.is_empty() { - return Err(()); - } - } - } - - self.frames.insert(lhs_key.clone(), updated); - Ok(()) - } - Value::Variable(var_id) => { - let rhs_key = var_id.0.as_str(); - - if let Some(rhs_stream) = self.frames.get(rhs_key) { - let mut non_matches: BTreeMap> = BTreeMap::new(); - - for (lhs_id, lhs_value) in lhs_stream.iter() { - for (rhs_id, rhs_value) in rhs_stream.iter() { - if !f(lhs_value, rhs_value) { - if let Some(rhs_ids) = non_matches.get_mut(lhs_id) { - rhs_ids.push(*rhs_id); - } else { - non_matches.insert(*lhs_id, vec![*rhs_id]); - } - } - } - } - - // Double negatives, but for good reason - let did_quantify = - match (lhs_stream.is_every(), rhs_stream.is_every()) { - (true, true) => non_matches.is_empty(), - (true, false) => non_matches - .values() - .all(|rhs_ids| rhs_ids.len() != rhs_stream.len()), - (false, true) => { - non_matches.values().any(|rhs_ids| rhs_ids.is_empty()) - } - (false, false) => non_matches - .values() - .any(|rhs_ids| rhs_ids.len() < rhs_stream.len()), - }; - - if did_quantify { - let mut new_lhs_stream = lhs_stream.clone(); - let mut new_rhs_stream = rhs_stream.clone(); - - for (l_key, r_keys) in non_matches { - new_lhs_stream.remove(l_key); - - for r_key in r_keys { - new_rhs_stream.remove(r_key); - } - } - - self.frames.insert(lhs_key.into(), new_lhs_stream); - self.frames.insert(rhs_key.into(), new_rhs_stream); - - Ok(()) - } else { - Err(()) - } - } else { - Err(()) - } - } - } - } else { - // FIXME not nessesarily! You may need to create new entires - Err(()) - } - } - } - } - - pub fn pattern_matching_unification(&mut self, lhs: &Value, rhs: &Value) -> Result { - self.apply(lhs, rhs, |a, b| { - todo!(); - todo!(); - // FIXME pattern matching etc - }) - .map(|()| rhs.clone()) - } -} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index c520658b..f897fe9e 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,42 +1,13 @@ //FIXME rename core -use super::selector::op::SelectorOp; +use super::{ + collection::Collection, + selector::{error::SelectorErrorReason, SelectorError}, +}; use crate::ipld; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -impl Predicate { - pub fn run(self, data: &Ipld) -> Result { - Ok(match self { - Predicate::True => true, - Predicate::False => false, - Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, - Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, - Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, - Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, - Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, - Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), - Predicate::Not(inner) => !inner.run(data)?, - Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, - Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Forall(xs, p) => xs - .resolve(data)? - .to_vec() - .iter() - .try_fold(true, |acc, ipld| Ok(acc && p.clone().run(ipld)?))?, - Predicate::Exists(xs, p) => { - let pred = p.clone(); - - xs.resolve(data)? - .to_vec() - .iter() - .try_fold(true, |acc, ipld| Ok(acc || pred.clone().run(ipld)?))? - } - }) - } -} - -trait Resolve { +pub trait Resolve { fn resolve(self, ctx: &Ipld) -> Result; } @@ -46,6 +17,12 @@ impl Resolve for Ipld { } } +impl Resolve for ipld::Newtype { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } +} + impl Resolve for ipld::Number { fn resolve(self, _ctx: &Ipld) -> Result { Ok(self) @@ -58,106 +35,12 @@ impl Resolve for Collection { } } -// FIXME Normal form? - impl Resolve for String { fn resolve(self, _ctx: &Ipld) -> Result { Ok(self) } } -impl SelectorOr { - fn resolve(self, ctx: &Ipld) -> Result { - match self { - SelectorOr::Pure(inner) => Ok(inner), - SelectorOr::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - // SelectorOp::This => Ok((ipld, seen_ops)), - SelectorOp::Try(inner) => { - let op: SelectorOp = *inner.clone(); - let ipld: Ipld = SelectorOr::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - SelectorOp::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError { - path: seen_ops - .iter() - .map(|op| (*op).clone()) - .collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }); - } - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError { - path: seen_ops - .iter() - .map(|op| (*op).clone()) - .collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError { - path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::NotAList, - }), - } - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, - )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?.clone(), seen_ops)) - } - SelectorOp::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?.clone(), seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } - } -} - pub trait TryFromIpld: Sized { fn try_from_ipld(ipld: Ipld) -> Result; } @@ -168,6 +51,12 @@ impl TryFromIpld for Ipld { } } +impl TryFromIpld for ipld::Newtype { + fn try_from_ipld(ipld: Ipld) -> Result { + Ok(ipld::Newtype(ipld)) + } +} + impl TryFromIpld for ipld::Number { fn try_from_ipld(ipld: Ipld) -> Result { match ipld { @@ -190,128 +79,22 @@ impl TryFromIpld for String { impl TryFromIpld for Collection { fn try_from_ipld(ipld: Ipld) -> Result { match ipld { - Ipld::List(xs) => Ok(Collection::Array(xs)), - Ipld::Map(xs) => Ok(Collection::Map(xs)), + Ipld::List(xs) => Ok(Collection::Array(xs.into_iter().try_fold( + vec![], + |mut acc, v| { + acc.push(TryFromIpld::try_from_ipld(v)?); + Ok(acc) + }, + )?)), + Ipld::Map(xs) => Ok(Collection::Map(xs.into_iter().try_fold( + BTreeMap::new(), + |mut map, (k, v)| { + let value = TryFromIpld::try_from_ipld(v)?; + map.insert(k, value); + Ok(map) + }, + )?)), _ => Err(SelectorErrorReason::NotACollection), } } } - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SelectorError { - pub path: Vec, - pub reason: SelectorErrorReason, -} - -impl SelectorError { - pub fn from_refs(path_refs: &Vec<&SelectorOp>, reason: SelectorErrorReason) -> SelectorError { - SelectorError { - path: path_refs.iter().map(|op| (*op).clone()).collect(), - reason, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SelectorErrorReason { - IndexOutOfBounds, - KeyNotFound, - NotAList, - NotAMap, - NotACollection, - NotANumber, - NotAString, -} - -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Predicate { - // Booleans - True, - False, - - // Comparison - Equal(SelectorOr, SelectorOr), - - GreaterThan(SelectorOr, SelectorOr), - GreaterThanOrEqual(SelectorOr, SelectorOr), - - LessThan(SelectorOr, SelectorOr), - LessThanOrEqual(SelectorOr, SelectorOr), - - Like(SelectorOr, SelectorOr), - - // Connectives - Not(Box), - And(Box, Box), - Or(Box, Box), - - // Collection iteration - Forall(SelectorOr, Box), // ∀x ∈ xs - Exists(SelectorOr, Box), // ∃x ∈ xs -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOr { - Get(Vec), - Pure(T), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), -} - -impl Collection { - pub fn to_vec(self) -> Vec { - match self { - Collection::Array(xs) => xs, - Collection::Map(xs) => xs.into_values().collect(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Selector(pub Vec); - -impl Serialize for Selector { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0 - .iter() - .fold("".into(), |acc, seg| format!("{}{}", acc, seg.to_string())) - .serialize(serializer) - } -} - -pub fn glob(input: &String, pattern: &String) -> bool { - let mut chars = input.chars(); - let mut like = pattern.chars(); - - loop { - match (chars.next(), like.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; - } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; - } - } - (None, None) => { - return true; - } - } - } -} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs new file mode 100644 index 00000000..14e0ced3 --- /dev/null +++ b/src/delegation/policy/predicate.rs @@ -0,0 +1,144 @@ +use super::{ + collection::Collection, + selector::{or::SelectorOr, SelectorError}, +}; +use crate::{ability::arguments, ipld}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +// FIXME Normal form? +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Predicate { + // Booleans + True, + False, + + // Comparison + Equal(SelectorOr, SelectorOr), + + GreaterThan(SelectorOr, SelectorOr), + GreaterThanOrEqual(SelectorOr, SelectorOr), + + LessThan(SelectorOr, SelectorOr), + LessThanOrEqual(SelectorOr, SelectorOr), + + Like(SelectorOr, SelectorOr), + + // Connectives + Not(Box), + And(Box, Box), + Or(Box, Box), + + // Collection iteration + Every(SelectorOr, Box), // ∀x ∈ xs + Some(SelectorOr, Box), // ∃x ∈ xs +} + +impl Predicate { + pub fn run(self, data: &Ipld) -> Result { + Ok(match self { + Predicate::True => true, + Predicate::False => false, + Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, + Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, + Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, + Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, + Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, + Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Not(inner) => !inner.run(data)?, + Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, + Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, + Predicate::Every(xs, p) => xs + .resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, + Predicate::Some(xs, p) => { + let pred = p.clone(); + + xs.resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? + } + }) + } +} + +pub fn glob(input: &String, pattern: &String) -> bool { + let mut chars = input.chars(); + let mut like = pattern.chars(); + + loop { + match (chars.next(), like.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } + } + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { + return true; + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Predicate { + type Parameters = (); // FIXME? + type Strategy = BoxedStrategy; + + fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { + let leaf = prop_oneof![ + Just(Predicate::True), + Just(Predicate::False), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) + ]; + + let connective = leaf.clone().prop_recursive(8, 16, 4, |inner| { + prop_oneof![ + (inner.clone(), inner.clone()) + .prop_map(|(lhs, rhs)| { Predicate::And(Box::new(lhs), Box::new(rhs)) }), + (inner.clone(), inner.clone()) + .prop_map(|(lhs, rhs)| { Predicate::Or(Box::new(lhs), Box::new(rhs)) }), + ] + }); + + let quantified = leaf.clone().prop_recursive(8, 16, 4, |inner| { + prop_oneof![ + (SelectorOr::arbitrary(), inner.clone()) + .prop_map(|(xs, p)| { Predicate::Every(xs, Box::new(p)) }), + (SelectorOr::arbitrary(), inner.clone()) + .prop_map(|(xs, p)| { Predicate::Some(xs, Box::new(p)) }), + ] + }); + + prop_oneof![leaf, connective, quantified].boxed() + } +} diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 20b1966e..aa0fa677 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -1,7 +1,8 @@ pub mod error; pub mod op; +pub mod or; -use error::ParseError; +use error::{ParseError, SelectorErrorReason}; use nom::{ self, branch::alt, @@ -15,6 +16,7 @@ use nom::{ }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, str::FromStr}; +use thiserror::Error; #[derive(Debug, Clone, PartialEq)] pub struct Selector(Vec); @@ -23,7 +25,7 @@ pub fn parse(input: &str) -> IResult<&str, Selector> { let without_this = many1(op::parse); let with_this = preceded(char('.'), many0(op::parse)); - // NOTE: must try without this first, to disambiguate `.field` from `.` + // NOTE: must try without_this this first, to disambiguate `.field` from `.` let p = map_res(alt((without_this, with_this)), |found| { Ok::(Selector(found)) }); @@ -82,3 +84,22 @@ impl<'de> Deserialize<'de> for Selector { Selector::from_str(&s).map_err(serde::de::Error::custom) } } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +#[error("Selector {selector} encountered runtime error: {reason}")] +pub struct SelectorError { + pub selector: Selector, + pub reason: SelectorErrorReason, +} + +impl SelectorError { + pub fn from_refs( + path_refs: &Vec<&op::SelectorOp>, + reason: SelectorErrorReason, + ) -> SelectorError { + SelectorError { + selector: Selector(path_refs.iter().map(|op| (*op).clone()).collect()), + reason, + } + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index b4e583ac..75a96a58 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -9,3 +9,27 @@ pub enum ParseError { #[error("unknown pattern: {0}")] UnknownPattern(String), } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +pub enum SelectorErrorReason { + #[error("Index out of bounds")] + IndexOutOfBounds, + + #[error("Key not found")] + KeyNotFound, + + #[error("Not a list")] + NotAList, + + #[error("Not a map")] + NotAMap, + + #[error("Not a collection")] + NotACollection, + + #[error("Not a number")] + NotANumber, + + #[error("Not a string")] + NotAString, +} diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/op.rs index 26ca0499..8ff8a47a 100644 --- a/src/delegation/policy/selector/op.rs +++ b/src/delegation/policy/selector/op.rs @@ -14,10 +14,11 @@ use nom::{ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, str::FromStr}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, EnumAsInner)] pub enum SelectorOp { - // FIXME remove #[default] - // This, // . ArrayIndex(i32), // [2] Field(String), // ["key"] (or .key) Values, // .[] @@ -27,7 +28,6 @@ pub enum SelectorOp { impl fmt::Display for SelectorOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - // SelectorOp::This => write!(f, "."), SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), SelectorOp::Field(k) => { if let Some(first) = k.chars().next() { @@ -140,3 +140,19 @@ impl<'de> Deserialize<'de> for SelectorOp { SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for SelectorOp { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + i32::arbitrary().prop_map(|i| SelectorOp::ArrayIndex(i)), + String::arbitrary().prop_map(SelectorOp::Field), + Just(SelectorOp::Values), + // FIXME prop_recursive::lazy(|_| { SelectorOp::arbitrary_with(()).prop_map(SelectorOp::Try) }), + ] + .boxed() + } +} diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs new file mode 100644 index 00000000..74cf2699 --- /dev/null +++ b/src/delegation/policy/selector/or.rs @@ -0,0 +1,113 @@ +use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; +use crate::delegation::policy::ir::TryFromIpld; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOr { + Get(Vec), + Pure(T), +} + +impl SelectorOr { + pub fn resolve(self, ctx: &Ipld) -> Result { + match self { + SelectorOr::Pure(inner) => Ok(inner), + SelectorOr::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + SelectorOp::Try(inner) => { + let op: SelectorOp = *inner.clone(); + let ipld: Ipld = SelectorOr::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + SelectorOp::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )); + }; + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), + } + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for SelectorOr { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_params).prop_map(SelectorOr::Pure), + // FIXME add params that make this actually correspond to data + prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(SelectorOr::Get), + ] + .boxed() + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index b05b4039..130043b2 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,10 +1,8 @@ use super::Store; use crate::{ - // ability::arguments, crypto::varsig, - delegation::{condition::Condition, Delegation}, + delegation::{policy::predicate::Predicate, Delegation}, did::Did, - // proof::{checkable::Checkable, prove::Prove}, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -72,34 +70,29 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - C: Condition, DID: Did + Ord, V: varsig::Header, Enc: Codec + TryFrom + Into, > { - ucans: BTreeMap>, + ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl< - C: Condition + PartialEq, - DID: Did + Ord + Clone, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Store for MemoryStore +impl, Enc: Codec + TryFrom + Into> + Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) @@ -121,10 +114,9 @@ impl< &self, aud: &DID, subject: &Option, - policy: Vec, + policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> - { + ) -> Result)>>, Self::DelegationStoreError> { match self .index .get(subject) // FIXME probably need to rework this after last minbute chanegs diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index e3090ff8..a474fdc1 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,8 +1,7 @@ use crate::{ crypto::varsig, - delegation::{condition::Condition, Delegation}, + delegation::{policy::predicate::Predicate, Delegation}, did::Did, - // proof::checkable::Checkable, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -10,16 +9,10 @@ use std::fmt::Debug; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> -{ +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -27,7 +20,7 @@ pub trait Store< fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -39,15 +32,15 @@ pub trait Store< &self, audience: &DID, subject: &Option, - policy: Vec, + policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError>; + ) -> Result)>>, Self::DelegationStoreError>; fn can_delegate( &self, issuer: DID, audience: &DID, - policy: Vec, + policy: Vec, now: SystemTime, ) -> Result { self.get_chain(audience, &Some(issuer), policy, now) @@ -57,9 +50,9 @@ pub trait Store< fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5e728646..164faa54 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -5,13 +5,9 @@ use super::{ Invocation, }; use crate::{ - ability::{ - arguments, - parse::{ParseAbility, ParseAbilityError, ParsePromised}, - ucan, - }, + ability::{arguments, parse::ParseAbilityError, ucan}, crypto::{signature, varsig, Nonce}, - delegation::{self, condition::Condition}, + delegation, did::Did, invocation::promise, // proof::prove::Prove, @@ -34,11 +30,10 @@ use web_time::SystemTime; pub struct Agent< 'a, T: Resolvable, - C: Condition, DID: Did, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, > { @@ -49,20 +44,19 @@ pub struct Agent< pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, C, V, Enc)>, + marker: PhantomData<(T, V, Enc)>, } -impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> +impl<'a, T, DID, S, P, D, V, Enc> Agent<'a, T, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, - delegation::Payload: Clone, + delegation::Payload: Clone, T: Resolvable + Clone, - C: Condition, DID: Did + Clone, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, { @@ -177,10 +171,9 @@ where now: &SystemTime, ) -> Result< Recipient>, - ReceiveError, + ReceiveError, > where - C: Clone, Enc: From + Into, arguments::Named: From, Invocation: Clone, @@ -291,7 +284,6 @@ pub enum ReceiveError< T: Resolvable, P: promise::Store, DID: Did, - C: fmt::Debug, D, S: Store, V: varsig::Header, @@ -318,7 +310,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - ValidationError(#[source] ValidationError), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 3885afaa..8bf0e2a9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,7 +3,10 @@ use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, - delegation::{self, condition::Condition}, //, ValidationError}, + delegation::{ + self, + policy::{predicate::Predicate, selector::SelectorError}, + }, did::{Did, Verifiable}, time::{Expired, Timestamp}, }; @@ -132,11 +135,11 @@ impl Payload { Ok(()) } - pub fn check( + pub fn check( &self, - proofs: Vec<&delegation::Payload>, + proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError> + ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, @@ -175,9 +178,15 @@ impl Payload { return Err(ValidationError::CommandMismatch(proof.command.clone())); } + let ipld_args = Ipld::from(args.clone()); + for predicate in proof.policy.iter() { - if !predicate.validate(&args) { - return Err(ValidationError::FailedCondition(predicate.clone())); + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); } } @@ -189,8 +198,8 @@ impl Payload { } /// Delegation validation errors. -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum ValidationError { +#[derive(Debug, Clone, PartialEq, Error)] +pub enum ValidationError { #[error("The subject of the delegation is invalid")] InvalidSubject, @@ -206,12 +215,15 @@ pub enum ValidationError { #[error("The command of the delegation does not match the proof: {0:?}")] CommandMismatch(String), - #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), + #[error("The delegation failed a policy predicate: {0:?}")] + FailedPolicy(Predicate), + + #[error(transparent)] + SelectorError(#[from] SelectorError), } impl Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; + const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } impl, DID: Did> From> for arguments::Named { diff --git a/src/ipld/number.rs b/src/ipld/number.rs index a66f10d8..c4e7d08d 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -4,12 +4,15 @@ use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// The union of [`Ipld`] numeric types /// /// This is helpful when comparing different numeric types, such as -/// bounds checking in [`Condition`]s. +/// bounds checking in [`Predicate`]s. /// -/// [`Condition`]: crate::delegation::Condition +/// [`Predicate`]: crate::delegation::policy::predicate::Predicate #[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { @@ -56,3 +59,16 @@ impl From for Number { Number::Float(f) } } + +impl Arbitrary for Number { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + any::().prop_map(Number::Float), + any::().prop_map(Number::Integer), + ] + .boxed() + } +} diff --git a/src/lib.rs b/src/lib.rs index 86efec3b..1c21c44a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -//pub mod proof; pub mod reader; pub mod receipt; pub mod task; @@ -34,6 +33,3 @@ pub mod test_utils; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; - -// FIXME -// show pipe diff --git a/src/proof.rs b/src/proof.rs deleted file mode 100644 index de58a8ff..00000000 --- a/src/proof.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Proof chains, checking, and utilities. - -// pub mod checkable; -// pub mod error; -// pub mod parentful; -// pub mod parentless; -// pub mod parents; -// pub mod prove; -// pub mod same; - -// NOTE must remain *un*exported! -// pub(super) mod internal; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs deleted file mode 100644 index 3e6d2031..00000000 --- a/src/proof/checkable.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Define the hierarchy of an ability (or mark as not having one) - -use super::{prove::Prove, same::CheckSame}; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; - -// FIXME move to Delegatbel? - -/// Plug a type into the delegation checking pipeline -pub trait Checkable: CheckSame + Sized { - /// The type of hierarchy this ability has - /// - /// The only options are [`Parentful`][super::parentful::Parentful] - /// and [`Parentless`][super::parentless::Parentless], - /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From + PartialEq + Into>; -} diff --git a/src/proof/error.rs b/src/proof/error.rs deleted file mode 100644 index f2911c00..00000000 --- a/src/proof/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Standatd error types for delegation checking. - -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -/// An error for when values are unequal. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -#[error("unequal")] -pub struct Unequal(); - -/// A generic error for when two fields are unequal. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum OptionalFieldError { - /// A required field is missing. - /// - /// For example, when its proof has a vaue, but the target does not. - #[error("Field missing")] - Missing, - - /// A field is present but has a different value in its proof - #[error("Field value unequal")] - Unequal, -} - -impl From for OptionalFieldError { - fn from(_: Unequal) -> Self { - OptionalFieldError::Unequal - } -} - -impl TryFrom for Unequal { - type Error = OptionalFieldError; - - fn try_from(e: OptionalFieldError) -> Result { - match e { - OptionalFieldError::Unequal => Ok(Unequal {}), - _ => Err(e), - } - } -} diff --git a/src/proof/internal.rs b/src/proof/internal.rs deleted file mode 100644 index 06fd97fd..00000000 --- a/src/proof/internal.rs +++ /dev/null @@ -1,3 +0,0 @@ -// NOTE: Must not get exported -// FIXME either mark downstream as ok to be provate, OOOOOR just leave this in an internal modukle -pub trait Checker {} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs deleted file mode 100644 index a6515cc8..00000000 --- a/src/proof/parentful.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Utilities for working with abilties that *do* have a delegation hirarchy. - -use super::{ - internal::Checker, - parents::CheckParents, - prove::{Prove, Success}, - same::CheckSame, -}; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use thiserror::Error; - -/// The possible cases for an [ability][crate::ability]'s -/// [Delegation][crate::delegation::Delegation] chain when -/// it has parent abilities (a hierarchy). -/// -/// This type is generally not used directly, but rather is -/// called in the plumbing of the library. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentful { - /// The "top" ability (`*`) - Any, - - /// All possible parents for the ability. - Parents(T::Parents), - - /// The (invokable) ability itself. - This(T), -} - -impl From for Parentful -where - T: CheckParents, -{ - fn from(this: T) -> Self { - Parentful::This(this) - } -} - -/// Error cases when checking proofs (including parents) -#[derive(Debug, Error, PartialEq)] -pub enum ParentfulError { - /// The `cmd` field was more powerful than the proof. - /// - /// i.e. it behaves like moving "down" the delegation chain not "up" - #[error("The `cmd` field was more powerful than the proof")] - CommandEscelation, - - /// The `args` field was more powerful than the proof. - #[error("The `args` field was more powerful than the proof: {0}")] - ArgumentEscelation(ArgErr), - - /// The parents do not prove the ability. - #[error("The parents do not prove the ability: {0}")] - InvalidProofChain(PrfErr), - - /// Comparing parents in a delegation chain failed. - /// - /// The specific comparison error is captured in the `ParErr`. - #[error("Comparing parents in a delegation chain failed: {0}")] - InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least -} - -impl From> for arguments::Named -where - arguments::Named: From + From, -{ - fn from(parentful: Parentful) -> Self { - match parentful { - Parentful::Any => arguments::Named::new(), - Parentful::Parents(parents) => parents.into(), - Parentful::This(this) => this.into(), - } - } -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentful: Parentful) -> Self { - parentful.into() - } -} - -impl + DeserializeOwned + CheckParents> TryFrom for Parentful -where - ::Parents: DeserializeOwned, -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl CheckSame for Parentful -where - T::Parents: CheckSame, -{ - type Error = ParentfulError::Error>; // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => parents - .check_same(their_parents) - .map_err(ParentfulError::InvalidParents), - Parentful::This(this) => this - .check_parent(their_parents) - .map_err(ParentfulError::InvalidProofChain), - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => this - .check_same(that) - .map_err(ParentfulError::ArgumentEscelation), - }, - } - } -} - -impl CheckParents for Parentful -where - T::Parents: CheckSame, -{ - type Parents = Parentful; - type ParentError = ParentfulError::Error>; - - fn check_parent(&self, proof: &Parentful) -> Result<(), Self::ParentError> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => parents - .check_same(their_parents) - .map_err(ParentfulError::InvalidParents), - Parentful::This(this) => this - .check_parent(their_parents) - .map_err(ParentfulError::InvalidProofChain), - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => this - .check_same(that) - .map_err(ParentfulError::ArgumentEscelation), - }, - } - } -} - -impl Checker for Parentful {} - -impl Prove for Parentful -where - T::Parents: CheckSame, -{ - type Error = ParentfulError::Error>; - - fn check(&self, proof: &Parentful) -> Result { - match proof { - Parentful::Any => Ok(Success::ProvenByAny), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => match parents.check_same(their_parents) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::InvalidParents(e)), - }, - Parentful::This(this) => match this.check_parent(their_parents) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::InvalidProofChain(e)), - }, - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => match this.check_same(that) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::ArgumentEscelation(e)), - }, - }, - } - } -} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs deleted file mode 100644 index c9b13264..00000000 --- a/src/proof/parentless.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Utilities for working with abilties that *don't* have a delegation hirarchy -//! -use super::{ - checkable::Checkable, - internal::Checker, - prove::{Prove, Success}, - same::CheckSame, -}; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -/// The possible cases for an [ability][crate::ability]'s -/// [Delegation][crate::delegation::Delegation] chain when -/// it has no parent abilities (no hierarchy). -/// -/// This type is generally not used directly, but rather is -/// called in the plumbing of the library. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentless { - /// The "top" ability (`*`) - Any, - - /// The (invokable) ability itself. - This(T), -} - -impl From for Parentless { - fn from(this: T) -> Self { - Parentless::This(this) - } -} - -impl>> From> for arguments::Named { - fn from(parentless: Parentless) -> Self { - match parentless { - Parentless::Any => arguments::Named::new(), - Parentless::This(this) => this.into(), - } - } -} - -// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? -/// Error cases when checking proofs -#[derive(Debug, Clone, PartialEq)] -pub enum ParentlessError { - /// The `cmd` field was more powerful than the proof. - /// - /// i.e. it behaves like moving "down" the delegation chain not "up" - CommandEscelation, - - /// The `args` field was more powerful than the proof - ArgumentEscelation(T::Error), -} - -// FIXME better name -/// A helper trait to indicate that a type has no parents. -/// -/// This behaves as an alias for `Checkable::>`. -pub trait NoParents {} - -impl>> Checkable for T { - type Hierarchy = Parentless; -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentless: Parentless) -> Self { - parentless.into() - } -} - -impl CheckSame for Parentless { - type Error = ParentlessError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - Parentless::Any => Ok(()), - Parentless::This(that) => match self { - Parentless::Any => Err(ParentlessError::CommandEscelation), - Parentless::This(this) => this - .check_same(that) - .map_err(ParentlessError::ArgumentEscelation), - }, - } - } -} - -impl Checker for Parentless {} - -impl Prove for Parentless { - type Error = ParentlessError; - - fn check(&self, proof: &Parentless) -> Result { - match proof { - Parentless::Any => Ok(Success::ProvenByAny), - Parentless::This(that) => match self { - Parentless::Any => Ok(Success::Proven), - Parentless::This(this) => match this.check_same(that) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentlessError::ArgumentEscelation(e)), - }, - }, - } - } -} diff --git a/src/proof/parents.rs b/src/proof/parents.rs deleted file mode 100644 index 5b68889d..00000000 --- a/src/proof/parents.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Check the parents in an ability hierarchy. - -use super::same::CheckSame; - -/// Check if the parents of a proof are valid. -/// -/// Note that the top ability (`*`) does not need to be handled separately, -/// as the code from [`CheckParents`] will be lifted into -/// [`Parentful`][super::parentful::Parentful], which knows -/// how to check `*`. -pub trait CheckParents: CheckSame { - /// The parents of the hierarchy. - /// - /// Note that `Self` *need not* be included in [`CheckParents::Parents`]. - type Parents; - - /// Error checking against [`CheckParents::Parents`]. - type ParentError; - - // FIXME - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; -} diff --git a/src/proof/prove.rs b/src/proof/prove.rs deleted file mode 100644 index 69e0feaf..00000000 --- a/src/proof/prove.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! High-level proof chain checking. - -use super::internal::Checker; - -// FIXME move to internal? - -/// An internal trait that checks based on the other traits for an ability type. -pub trait Prove: Checker { - type Error; - - // FIXME make the same as the trait name (prove) - fn check(&self, proof: &Self) -> Result; -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Success { - /// Success - Proven, - - /// Special case for success by checking against `*`. - ProvenByAny, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Failure { - /// An error in the command chain. - CommandEscelation, - - /// An error in the argument chain. - ArgumentEscelation(ArgErr), - - /// An error in the proof chain. - InvalidProofChain(ChainErr), - - /// An error in the parents. - InvalidParents(ParentErr), -} diff --git a/src/proof/same.rs b/src/proof/same.rs deleted file mode 100644 index b616a01c..00000000 --- a/src/proof/same.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Check the delegation proof against another instance of the same type - -use super::error::OptionalFieldError; - -/// Trait for checking if a proof of the same type is equally or less restrictive. -/// -/// # Example -/// -/// ```rust -/// # use ucan::proof::same::CheckSame; -/// # use ucan::did; -/// # -/// struct HelloBuilder { -/// wave_at: Option, -/// } -/// -/// enum HelloError { -/// MissingWaveAt, -/// WeDontTalkTo(did::Newtype) -/// } -/// -/// impl CheckSame for HelloBuilder { -/// type Error = HelloError; -/// -/// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -/// if self.wave_at == Some(did::Newtype::try_from("did:example:mallory".to_string()).unwrap()) { -/// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); -/// } -/// -/// if let Some(_) = &proof.wave_at { -/// if self.wave_at != proof.wave_at { -/// return Err(HelloError::MissingWaveAt); -/// } -/// } -/// -/// Ok(()) -/// } -/// } -pub trait CheckSame { - /// Error type describing why a proof was insufficient. - type Error; - - /// Check if the proof is equally or less restrictive than the instance. - /// - /// Delegation must always attenuate. If the proof is more restrictive than the instance, - /// it has violated the delegation chain rules. - fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; -} - -impl CheckSame for Option { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - None => Ok(()), - Some(proof_) => match self { - None => Err(OptionalFieldError::Missing), - Some(self_) => { - if self_.eq(proof_) { - Ok(()) - } else { - Err(OptionalFieldError::Unequal) - } - } - }, - } - } -} diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ddf6582d..ace1e4d5 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -85,7 +85,7 @@ pub struct Payload { } impl Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; + const TAG: &'static str = "ucan/r@1.0.0-rc.1"; } impl Serialize for Payload From 65987cd33bc210c66a9f90be5d5a08a17804cc78 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 4 Mar 2024 22:30:11 -0800 Subject: [PATCH 173/234] Cleanup post-spec updates --- src/ability.rs | 33 --- src/ability/command.rs | 2 +- src/ability/crud.rs | 205 ++++----------- src/ability/crud/any.rs | 139 ----------- src/ability/crud/create.rs | 16 +- src/ability/crud/destroy.rs | 203 +++++---------- src/ability/crud/error.rs | 40 --- src/ability/crud/mutate.rs | 150 ----------- src/ability/crud/parents.rs | 152 ----------- src/ability/crud/read.rs | 83 ++----- src/ability/crud/update.rs | 235 ++---------------- src/ability/js/parentful.rs | 3 - src/ability/js/parentless.rs | 2 - src/ability/msg.rs | 82 +++--- src/ability/msg/any.rs | 88 ------- src/ability/msg/receive.rs | 59 +---- src/ability/msg/send.rs | 52 ++-- src/ability/preset.rs | 132 +++++----- src/ability/ucan.rs | 1 + src/ability/ucan/batch.rs | 18 ++ src/ability/ucan/revoke.rs | 42 ++-- src/ability/wasm/run.rs | 31 +-- src/crypto/signature/envelope.rs | 115 ++++++++- src/delegation.rs | 48 +++- src/delegation/agent.rs | 2 +- src/delegation/delegable.rs | 51 ---- src/delegation/payload.rs | 2 +- src/delegation/policy.rs | 12 +- src/delegation/policy/ir.rs | 100 -------- src/delegation/policy/predicate.rs | 39 ++- src/delegation/policy/selector.rs | 31 +-- .../policy/selector/{op.rs => filter.rs} | 72 +++--- src/delegation/policy/selector/or.rs | 20 +- src/delegation/policy/selector/select.rs | 112 +++++++++ src/delegation/policy/selector/selectable.rs | 62 +++++ src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 3 +- src/invocation.rs | 49 +++- src/invocation/agent.rs | 15 +- src/invocation/payload.rs | 2 +- src/invocation/promise/resolvable.rs | 19 +- src/invocation/promise/store/memory.rs | 7 +- src/invocation/promise/store/traits.rs | 14 +- src/ipld.rs | 2 + src/{delegation/policy => ipld}/collection.rs | 2 +- src/ipld/newtype.rs | 59 ----- src/lib.rs | 1 + src/proof.rs | 38 +++ src/reader.rs | 2 - src/reader/builder.rs | 37 --- src/receipt.rs | 70 +++++- 51 files changed, 971 insertions(+), 1785 deletions(-) delete mode 100644 src/ability/crud/any.rs delete mode 100644 src/ability/crud/error.rs delete mode 100644 src/ability/crud/mutate.rs delete mode 100644 src/ability/crud/parents.rs delete mode 100644 src/ability/msg/any.rs create mode 100644 src/ability/ucan/batch.rs delete mode 100644 src/delegation/delegable.rs delete mode 100644 src/delegation/policy/ir.rs rename src/delegation/policy/selector/{op.rs => filter.rs} (62%) create mode 100644 src/delegation/policy/selector/select.rs create mode 100644 src/delegation/policy/selector/selectable.rs rename src/{delegation/policy => ipld}/collection.rs (100%) create mode 100644 src/proof.rs delete mode 100644 src/reader/builder.rs diff --git a/src/ability.rs b/src/ability.rs index d582fc5c..6b2ae7cb 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,37 +1,4 @@ //! Abilities describe the semantics of what a UCAN is allowed to do. -//! -//! # Top Level Structure -//! -//! They always follow the same format at the top level: -//! -//! | Field | Name | Description | -//! |--------|-----------------------------|----------------------------------| -//! | `cmd` | [Command](command::Command) | Roughly a function name. Determines the shape of the `args`. | -//! | `args` | [Arguments](arguments) | Roughly the function's arguments | -//! -//! # Proof Hierarchy -//! -//! Any UCAN can be proven by the `*` ability. This has been special-cased -//! into the library, and you don't have to worry about it directly when -//! implementing a new ability. -//! -//! Most abilities have no additional parents. If they do, they follow a -//! strict hierararchy. The [CRUD hierarchy](crate::abilities::crud::Any) -//! is a good example. -//! -//! Not all abilities in the hierarchy are invocable: some abstract over -//! multiple `cmd`s (such as [`crud/*`](crate::abilities::crud::Any) for -//! all CRUD actions). This allows for flexibility in adding more abilities -//! under the same hierarchy in the future without having to reissue all of -//! your certificates. -//! -//! # Lifecycle -//! -//! All abilities start as a delegation, which can omit fields (but must -//! stay the same or add more at each delegatoion). When they are invoked, -//! all field much be present. The only exception is promises, where a -//! field may include a promise pointing at another invocation. Once fully -//! resolved ("ready"), they must be validatable against the delegation chain. pub mod pipe; pub mod ucan; diff --git a/src/ability/command.rs b/src/ability/command.rs index 2cd85c3b..040ec2ea 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -9,7 +9,7 @@ //! "aud": "did:example:456", //! "cmd": "/msg/send", // <--- This is the command //! "args": { // ┐ -//! "to": "mailto:alice@example.com", // ├─ These are determined by the command +//! "to": "mailto:alice@example.com", // ├─ The shape of the args is determined by the cmd //! "message": "Hello, World!", // │ //! } // ┘ //! "exp": 1234567890 diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c9d5f777..c821d75a 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -37,20 +37,11 @@ //! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete //! [`Did`]: crate::did::Did -// mod any; -// mod mutate; -// mod parents; - pub mod create; pub mod destroy; -// pub mod error; pub mod read; pub mod update; -// pub use any::Any; -// pub use mutate::Mutate; -// pub use parents::*; - use crate::{ ability::{ arguments, @@ -60,60 +51,65 @@ use crate::{ invocation::promise::Resolvable, ipld, }; +use create::{Create, PromisedCreate}; +use destroy::{Destroy, PromisedDestroy}; use libipld_core::ipld::Ipld; +use read::{PromisedRead, Read}; +use serde::{Deserialize, Serialize}; +use update::{PromisedUpdate, Update}; #[cfg(target_arch = "wasm32")] pub mod js; -#[derive(Debug, Clone, PartialEq)] -pub enum Ready { - Create(create::Create), - Read(read::Ready), - Update(update::Ready), - Destroy(destroy::Ready), +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Crud { + Create(Create), + Read(Read), + Update(Update), + Destroy(Destroy), } -#[derive(Debug, Clone, PartialEq)] -pub enum Promised { - Create(create::PromisedCreate), - Read(read::Promised), - Update(update::Promised), - Destroy(destroy::Promised), +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedCrud { + Create(PromisedCreate), + Read(PromisedRead), + Update(PromisedUpdate), + Destroy(PromisedDestroy), } -impl ParsePromised for Promised { - type PromisedArgsError = (); +impl ParsePromised for PromisedCrud { + type PromisedArgsError = (); // FIXME fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - match create::PromisedCreate::try_parse_promised(cmd, args.clone()) { - Ok(create) => return Ok(Promised::Create(create)), + match PromisedCreate::try_parse_promised(cmd, args.clone()) { + Ok(create) => return Ok(PromisedCrud::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match read::Promised::try_parse_promised(cmd, args.clone()) { - Ok(read) => return Ok(Promised::Read(read)), + match PromisedRead::try_parse_promised(cmd, args.clone()) { + Ok(read) => return Ok(PromisedCrud::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Promised::try_parse_promised(cmd, args.clone()) { - Ok(update) => return Ok(Promised::Update(update)), + match PromisedUpdate::try_parse_promised(cmd, args.clone()) { + Ok(update) => return Ok(PromisedCrud::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match destroy::Promised::try_parse_promised(cmd, args) { - Ok(destroy) => return Ok(Promised::Destroy(destroy)), + match PromisedDestroy::try_parse_promised(cmd, args) { + Ok(destroy) => return Ok(PromisedCrud::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } @@ -124,39 +120,39 @@ impl ParsePromised for Promised { } } -impl ParseAbility for Ready { +impl ParseAbility for Crud { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match create::Create::try_parse(cmd, args.clone()) { - Ok(create) => return Ok(Ready::Create(create)), + match Create::try_parse(cmd, args.clone()) { + Ok(create) => return Ok(Crud::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match read::Ready::try_parse(cmd, args.clone()) { - Ok(read) => return Ok(Ready::Read(read)), + match Read::try_parse(cmd, args.clone()) { + Ok(read) => return Ok(Crud::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Ready::try_parse(cmd, args.clone()) { - Ok(update) => return Ok(Ready::Update(update)), + match Update::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Crud::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match destroy::Ready::try_parse(cmd, args) { - Ok(destroy) => return Ok(Ready::Destroy(destroy)), + match Destroy::try_parse(cmd, args) { + Ok(destroy) => return Ok(Crud::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -166,132 +162,39 @@ impl ParseAbility for Ready { Err(ParseAbilityError::UnknownCommand(cmd.into())) } } -// -// impl Checkable for Builder { -// type Hierarchy = Parentful; -// } -impl ToCommand for Ready { +impl ToCommand for Crud { fn to_command(&self) -> String { match self { - Ready::Create(create) => create.to_command(), - Ready::Read(read) => read.to_command(), - Ready::Update(update) => update.to_command(), - Ready::Destroy(destroy) => destroy.to_command(), + Crud::Create(create) => create.to_command(), + Crud::Read(read) => read.to_command(), + Crud::Update(update) => update.to_command(), + Crud::Destroy(destroy) => destroy.to_command(), } } } -impl ToCommand for Promised { +impl ToCommand for PromisedCrud { fn to_command(&self) -> String { match self { - Promised::Create(create) => create.to_command(), - Promised::Read(read) => read.to_command(), - Promised::Update(update) => update.to_command(), - Promised::Destroy(destroy) => destroy.to_command(), + PromisedCrud::Create(create) => create.to_command(), + PromisedCrud::Read(read) => read.to_command(), + PromisedCrud::Update(update) => update.to_command(), + PromisedCrud::Destroy(destroy) => destroy.to_command(), } } } - -// impl ToCommand for Builder { -// fn to_command(&self) -> String { -// match self { -// Builder::Create(create) => create.to_command(), -// Builder::Read(read) => read.to_command(), -// Builder::Update(update) => update.to_command(), -// Builder::Destroy(destroy) => destroy.to_command(), -// } -// } -// } -// -// impl CheckParents for Builder { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { -// match self { -// Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), -// Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), -// Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), -// Builder::Read(read) => match parents { -// MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), -// _ => Err(()), -// }, -// } -// } -// } -// -// impl From for arguments::Named { -// fn from(builder: Builder) -> Self { -// match builder { -// Builder::Create(create) => create.into(), -// Builder::Read(read) => read.into(), -// Builder::Update(update) => update.into(), -// Builder::Destroy(destroy) => destroy.into(), -// } -// } -// } - -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// match promised { -// Promised::Create(create) => create.into(), -// Promised::Read(read) => read.into(), -// Promised::Update(update) => update.into(), -// Promised::Destroy(destroy) => destroy.into(), -// } -// } -// } - -// impl From for Builder { -// fn from(ready: Ready) -> Self { -// match ready { -// Ready::Create(create) => Builder::Create(create.into()), -// Ready::Read(read) => Builder::Read(read.into()), -// Ready::Update(update) => Builder::Update(update.into()), -// Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), -// } -// } -// } -// -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(builder: Builder) -> Result { -// match builder { -// Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), -// Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), -// Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), -// Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), -// } -// } -// } -// -// impl CheckSame for Builder { -// type Error = (); -// -// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { -// match (self, other) { -// (Builder::Create(a), Builder::Create(b)) => a.check_same(b), -// (Builder::Read(a), Builder::Read(b)) => a.check_same(b), -// (Builder::Update(a), Builder::Update(b)) => a.check_same(b), -// (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), -// _ => Err(()), -// } -// } -// } - -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Crud { + type Promised = PromisedCrud; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedCrud) -> Self { match promised { - Promised::Create(create) => create.into(), - Promised::Read(read) => read.into(), - Promised::Update(update) => update.into(), - Promised::Destroy(destroy) => destroy.into(), + PromisedCrud::Create(create) => create.into(), + PromisedCrud::Read(read) => read.into(), + PromisedCrud::Update(update) => update.into(), + PromisedCrud::Destroy(destroy) => destroy.into(), } } } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs deleted file mode 100644 index c85d4fb2..00000000 --- a/src/ability/crud/any.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! "Any" CRUD ability (superclass of all CRUD abilities) - -use crate::{ - ability::{arguments, command::Command}, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The superclass of all other CRUD abilities. -/// -/// For example, the [`crud::Create`][super::create::Create] ability may -/// be proven by the [`crud::Any`][Any] ability in a delegation chain. -/// -/// It may not be invoked directly, but rather is used as a delegaton proof -/// for other CRUD abilities (see the diagram below). -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of CRUD abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// read("crud/read") -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// readrun{{"invoke"}} -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> read -.-> readrun -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style any stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Any { - /// A an optional path relative to the actor's root. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - -impl Command for Any { - const COMMAND: &'static str = "/crud"; -} - -impl TryFrom> for Any { - type Error = (); - - fn try_from(arguments: arguments::Named) -> Result { - let mut path = None; - - for (key, value) in arguments.iter() { - match key.as_str() { - "path" => { - let some_path = match value { - Ipld::String(s) => Ok(PathBuf::from(s)), - _ => Err(()), - }?; - - path = Some(some_path); - } - _ => return Err(()), - } - } - - Ok(Any { path }) - } -} - -// FIXME pipe example - -impl NoParents for Any {} - -impl CheckSame for Any { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl TryFrom for Any { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From for Ipld { - fn from(builder: Any) -> Self { - builder.into() - } -} - -impl From for arguments::Named { - fn from(any: Any) -> arguments::Named { - let mut named = arguments::Named::new(); - if let Some(path) = any.path { - named.insert( - "path".into(), - path.into_os_string() - .into_string() - .expect("PathBuf should generate a valid path") - .into(), - ); - } - named - } -} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index b018fe38..3051676a 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,14 +1,12 @@ //! Create new resources. -// use super::parents::MutableParents; + use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -42,7 +40,7 @@ use std::path::PathBuf; /// /// style createready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Create { /// An optional path to a sub-resource that is to be created. @@ -86,7 +84,7 @@ pub struct Create { /// /// style createpromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. @@ -204,10 +202,10 @@ impl promise::Resolvable for Create { } impl From for arguments::Named { - fn from(builder: Create) -> Self { + fn from(create: Create) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { + if let Some(path) = create.path { named.insert( "path".to_string(), path.into_os_string() @@ -217,7 +215,7 @@ impl From for arguments::Named { ); } - if let Some(args) = builder.args { + if let Some(args) = create.args { named.insert("args".to_string(), args.into()); } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f7a7bf41..37ef0aa1 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,26 +1,14 @@ //! Destroy a resource. -// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be destroyed. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/destroy` ability. /// @@ -45,21 +33,70 @@ pub struct Generic { /// end /// /// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") +/// destroyready("crud::destroy::Destroy") /// /// top --> any --> mutate --> destroy /// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} /// /// style destroyready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Destroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, } +const COMMAND: &'static str = "/crud/destroy"; + +impl Command for Destroy { + const COMMAND: &'static str = COMMAND; +} + +impl From for arguments::Named { + fn from(ready: Destroy) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = ready.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + named + } +} + +impl TryFrom> for Destroy { + type Error = (); // FIXME + + fn try_from(args: arguments::Named) -> Result { + let mut path = None; + + for (k, ipld) in args { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } + } + _ => return Err(()), + } + } + + Ok(Destroy { path }) + } +} + +impl promise::Resolvable for Destroy { + type Promised = PromisedDestroy; +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/destroy` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). @@ -85,22 +122,22 @@ pub struct Ready { /// end /// /// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") +/// destroyready("crud::destroy::Destroy") /// /// top --> any --> mutate --> destroy /// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} /// /// style destroypromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedDestroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedDestroy { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -129,91 +166,16 @@ impl TryFrom> for Promised { } } - Ok(Promised { path }) + Ok(PromisedDestroy { path }) } } -const COMMAND: &'static str = "/crud/destroy"; - -impl Command for Ready { - const COMMAND: &'static str = COMMAND; -} - -impl Command for Promised { +impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -impl TryFrom> for Ready { - type Error = (); // FIXME - - fn try_from(args: arguments::Named) -> Result { - let mut path = None; - - for (k, ipld) in args { - match k.as_str() { - "path" => { - if let Ipld::String(s) = ipld { - path = Some(PathBuf::from(s)); - } - } - _ => return Err(()), - } - } - - Ok(Ready { path }) - } -} - -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedDestroy) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -227,9 +189,9 @@ impl From for arguments::Named { } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedDestroy { + fn from(r: Destroy) -> PromisedDestroy { + PromisedDestroy { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -237,12 +199,8 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; -} - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedDestroy) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { @@ -252,32 +210,3 @@ impl From for arguments::Named { named } } - -impl From for Ready { - fn from(p: Promised) -> Ready { - Ready { - path: p - .path - .map(|inner_path| inner_path.try_resolve().ok()) - .flatten(), - } - } -} - -impl From for arguments::Named { - fn from(ready: Ready) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path) = ready.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); - } - - named - } -} diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs deleted file mode 100644 index a4103586..00000000 --- a/src/ability/crud/error.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! CRUD-specific errors - -use crate::{ - ability::arguments, - proof::{error::OptionalFieldError, parents::CheckParents, same::CheckSame}, -}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -pub enum ProofError { - #[error("An issue with the path field")] - Path(#[from] OptionalFieldError), - - #[error("An issue with the (inner) arguments field")] - Args(#[from] arguments::NamedError), - - #[error("Proof has `args`, but none were present on delegate")] - MissingProofArgs, -} - -/// Error cases when checking [`MutableParents`] proofs -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum ParentError { - /// Error when comparing `crud/*` to another `crud/*`. - #[error(transparent)] - InvalidAnyProof(::Error), - - /// Error when comparing `crud/mutate` to another `crud/mutate`. - #[error(transparent)] - InvalidMutateProof(::Error), - - /// Error when comparing `crud/*` as a proof for `crud/mutate`. - #[error(transparent)] - InvalidMutateParent(::ParentError), - - /// "Expected `crud/*`, but got `crud/mutate`". - #[error("Expected `crud/*`, but got `crud/mutate`")] - CommandEscelation, -} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs deleted file mode 100644 index a8ca9746..00000000 --- a/src/ability/crud/mutate.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! The delegation superclass for all mutable CRUD actions. - -use crate::{ - ability::{arguments, command::Command}, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, - }, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegation superclass for all mutable CRUD actions. -/// -/// For example, the [`crud::Create`][super::create::Create] ability may -/// be proven by the [`crud::Mutate`][Mutate] ability in a delegation chain. -/// [`crud::Any`][super::Any] is a suitable proof for [`crud::Mutate`][Mutate], but not vice-versa. -/// -/// It may not be invoked directly, but rather is used as a delegaton proof -/// for other CRUD abilities (see the diagram below). -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of mutable CRUD abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style mutate stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Mutate { - /// A an optional path relative to the actor's root. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - -impl Command for Mutate { - const COMMAND: &'static str = "/crud/mutate"; -} - -impl From for Ipld { - fn from(mutate: Mutate) -> Self { - mutate.into() - } -} - -impl TryFrom for Mutate { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl TryFrom> for Mutate { - type Error = (); - - fn try_from(args: arguments::Named) -> Result { - if let Some(Ipld::String(s)) = args.get("path") { - return Ok(Mutate { - path: Some(PathBuf::from(s)), - }); - }; - - Ok(Mutate { path: None }) - } -} - -impl Checkable for Mutate { - type Hierarchy = Parentful; -} - -impl CheckSame for Mutate { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl CheckParents for Mutate { - type Parents = super::Any; - type ParentError = OptionalFieldError; - - fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(path) = &self.path { - let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl From for arguments::Named { - fn from(mutate: Mutate) -> Self { - let mut args = arguments::Named::new(); - - if let Some(path) = mutate.path { - args.insert( - "path".into(), - Ipld::String( - path.into_os_string() - .into_string() - .expect("PathBuf should generate a valid path"), - ), - ); - } - - args - } -} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs deleted file mode 100644 index 18b7cceb..00000000 --- a/src/ability/crud/parents.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Flat types for parent checking. -//! -//! Types here turn recursive checking into a since union to check. -//! This only needs to handle "inner" delegation types, not the topmost `*` -//! ability, or the invocable leaves of a delegation hierarchy. - -use super::error::ParentError; -use crate::{ - ability::{ - arguments, - command::ToCommand, - parse::{ParseAbility, ParseAbilityError}, - }, - proof::{parents::CheckParents, same::CheckSame}, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The union of mutable parents. -/// -/// This is helpful as a flat type to put in [`CheckParents::Parents`]. -/// -/// # Delegation Hierarchy -/// -/// The parents captured here are highlted in the following diagram: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// read("crud/read") -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// readrun{{"invoke"}} -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> read -.-> readrun -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style any stroke:orange; -/// style mutate stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields, untagged)] -pub enum MutableParents { - /// The `crud/*` ability. - Any(super::Any), - - /// The `crud/mutate` ability. - Mutate(super::Mutate), -} - -impl ToCommand for MutableParents { - fn to_command(&self) -> String { - match self { - MutableParents::Any(any) => any.to_command(), - MutableParents::Mutate(mutate) => mutate.to_command(), - } - } -} - -#[derive(Debug, Clone, Error)] -pub enum ParseError { - #[error("Invalid `crud/*` arguments: {0:?}")] - InvalidAnyArgs(>>::Error), - - #[error("Invalid `crud/mutate` arguments: {0:?}")] - InvalidMutateArgs(>>::Error), -} - -impl ParseAbility for MutableParents { - type ArgsErr = ParseError; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result> { - match super::Any::try_parse(cmd, args.clone()) { - Ok(any) => return Ok(MutableParents::Any(any)), - Err(ParseAbilityError::InvalidArgs(e)) => { - return Err(ParseAbilityError::InvalidArgs(ParseError::InvalidAnyArgs( - e, - ))) - } - Err(ParseAbilityError::UnknownCommand(_)) => {} - } - - match super::Any::try_parse(cmd, args.clone()) { - Ok(any) => return Ok(MutableParents::Any(any)), - Err(ParseAbilityError::InvalidArgs(e)) => { - return Err(ParseAbilityError::InvalidArgs( - ParseError::InvalidMutateArgs(e), - )) - } - Err(ParseAbilityError::UnknownCommand(_)) => {} - } - - Err(ParseAbilityError::UnknownCommand(cmd.to_string())) - } -} - -impl CheckSame for MutableParents { - type Error = ParentError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - MutableParents::Mutate(mutate) => match proof { - MutableParents::Mutate(proof_mutate) => mutate - .check_same(proof_mutate) - .map_err(ParentError::InvalidMutateProof), - - MutableParents::Any(proof_any) => mutate - .check_parent(proof_any) - .map_err(ParentError::InvalidMutateParent), - }, - - MutableParents::Any(any) => match proof { - MutableParents::Mutate(_) => Err(ParentError::CommandEscelation), - MutableParents::Any(proof_any) => any - .check_same(proof_any) - .map_err(ParentError::InvalidAnyProof), - }, - } - } -} - -impl From for arguments::Named { - fn from(parents: MutableParents) -> Self { - match parents { - MutableParents::Any(any) => any.into(), - MutableParents::Mutate(mutate) => mutate.into(), - } - } -} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index db25111b..28199c49 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,19 +1,14 @@ //! Read from a resource. -// use super::any as crud; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -// FIXME deserialize instance - #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. /// @@ -34,7 +29,7 @@ use std::path::PathBuf; /// end /// /// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") +/// readready("crud::read::Read") /// /// top --> any --> read /// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} @@ -43,7 +38,7 @@ use std::path::PathBuf; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Read { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, @@ -76,7 +71,7 @@ pub struct Ready { /// end /// /// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") +/// readready("crud::read::Read") /// /// top --> any --> read /// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} @@ -85,7 +80,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedRead { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, @@ -95,7 +90,7 @@ pub struct Promised { pub args: Option>>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRead { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -143,36 +138,30 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedRead { path, args }) } } const COMMAND: &'static str = "/crud/read"; -impl Command for Ready { +impl Command for Read { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedRead { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -// FIXME resolves vs resolvable is confusing - -impl TryFrom for Ready { - type Error = SerdeError; // FIXME +impl TryFrom for Read { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld) } } -impl From for arguments::Named { - fn from(ready: Ready) -> Self { +impl From for arguments::Named { + fn from(ready: Read) -> Self { let mut named = arguments::Named::new(); if let Some(path) = ready.path { @@ -193,10 +182,10 @@ impl From for arguments::Named { } } -impl TryFrom> for Ready { +impl TryFrom> for Read { type Error = (); - fn try_from(arguments: arguments::Named) -> Result { + fn try_from(arguments: arguments::Named) -> Result { let mut path = None; let mut args = None; @@ -216,50 +205,16 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Read { path, args }) } } -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = crud::Any; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// // FIXME check the args, too! -// if let Some(proof_path) = &other.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// -// Ok(()) -// } -// } - -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Read { + type Promised = PromisedRead; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRead) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index e355fa2b..b512fa8f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,18 +1,14 @@ //! Update existing resources. -// use super::parents::MutableParents; + use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; -// FIXME deserialize instance - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -37,59 +33,16 @@ use std::{collections::BTreeMap, path::PathBuf}; /// end /// /// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") +/// updateready("crud::update::Update") /// /// top --> any --> mutate --> update /// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} /// /// style updateready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Ready { - /// An optional path to a sub-resource that is to be updated. - #[serde(default, skip_serializing_if = "Option::is_none")] - path: Option, - - /// Optional arguments to be passed in the update. - #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option>, -} - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for updating existing agents. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/create` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// update("crud/update") -/// end -/// end -/// end -/// -/// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") -/// -/// top --> any --> mutate --> update -/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} -/// -/// style update stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Builder { +pub struct Update { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] path: Option, @@ -124,16 +77,16 @@ pub struct Builder { /// end /// /// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") +/// updateready("crud::update::Update") /// /// top --> any --> mutate --> update /// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} /// /// style updatepromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedUpdate { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] path: Option>, @@ -145,19 +98,15 @@ pub struct Promised { const COMMAND: &'static str = "/crud/update"; -impl Command for Ready { - const COMMAND: &'static str = COMMAND; -} - -impl Command for Builder { +impl Command for Update { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedUpdate { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedUpdate { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -196,15 +145,11 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedUpdate { path, args }) } } -// impl Delegable for Ready { -// type Builder = Builder; -// } - -impl TryFrom> for Ready { +impl TryFrom> for Update { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -231,66 +176,17 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Update { path, args }) } } -impl TryFrom> for Builder { - type Error = (); - - fn try_from(named: arguments::Named) -> Result { - let mut path = None; - let mut args = None; - - for (key, ipld) in named { - match key.as_str() { - "path" => { - if let Ipld::String(s) = ipld { - path = Some(PathBuf::from(s)); - } else { - return Err(()); - } - } - "args" => { - if let Ipld::Map(map) = ipld { - args = Some(arguments::Named(map)); - } else { - return Err(()); - } - } - _ => return Err(()), - } - } - - Ok(Builder { path, args }) - } -} - -impl From for Builder { - fn from(r: Ready) -> Self { - Builder { - path: r.path, - args: r.args, - } - } -} - -impl From for Ready { - fn from(builder: Builder) -> Self { - Ready { - path: builder.path, - args: builder.args, - } - } -} - -impl From for Ipld { - fn from(create: Ready) -> Self { +impl From for Ipld { + fn from(create: Update) -> Self { create.into() } } -impl TryFrom for Ready { +impl TryFrom for Update { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -299,7 +195,7 @@ impl TryFrom for Ready { return Err(()); // FIXME } - Ok(Ready { + Ok(Update { path: map .get("path") .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) @@ -316,54 +212,8 @@ impl TryFrom for Ready { } } -// impl Checkable for Builder { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Builder { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Builder { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedUpdate) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -393,9 +243,9 @@ impl From for arguments::Named { } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedUpdate { + fn from(r: Update) -> PromisedUpdate { + PromisedUpdate { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -405,43 +255,12 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|p| p.try_resolve().ok()), - args: todo!(), // promised.args.and_then(|a| a.try_resolve_option()), - } - } -} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path) = builder.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); - } - - if let Some(args) = builder.args { - named.insert("args".to_string(), args.into()); - } - - named - } +impl promise::Resolvable for Update { + type Promised = PromisedUpdate; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedUpdate) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 1af4654b..16b7b689 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -13,9 +13,6 @@ use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename type WithParents = Reader>; -// Promise = Promise? Ah, nope becuase we need that CID on the promise -// FIXME represent promises (for Promised) and options (for builder) - /// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 27118b85..d9f458c7 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -22,8 +22,6 @@ pub struct ParentlessConfig { check_same: Function, } -// FIXME represent promises (for Promised) and options (for builder) - // NOTE if changed, please update this in the docs for `ParentlessArgs` below #[wasm_bindgen(typescript_custom_section)] pub const PARENTLESS_ARGS: &str = r#" diff --git a/src/ability/msg.rs b/src/ability/msg.rs index e1d90481..59ee1fed 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -13,92 +13,102 @@ use crate::{ ipld, }; use libipld_core::ipld::Ipld; - -// FIXME rename invokable? -#[derive(Debug, Clone, PartialEq)] -pub enum Ready { - Send(send::Ready), - Receive(receive::Receive), +use receive::{PromisedReceive, Receive}; +use send::{PromisedSend, Send}; +use serde::{Deserialize, Serialize}; + +/// A family of abilities for sending and receiving messages. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Msg { + /// The ability for sending messages. + Send(Send), + + /// The ability for receiving messages. + Receive(Receive), } -#[derive(Debug, Clone, PartialEq)] -pub enum Promised { - Send(send::Promised), - Receive(receive::Promised), +/// A promised version of the [`Msg`] ability. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedMsg { + /// The promised ability for sending messages. + Send(PromisedSend), + + /// The promised ability for receiving messages. + Receive(PromisedReceive), } -impl ToCommand for Ready { +impl ToCommand for Msg { fn to_command(&self) -> String { match self { - Ready::Send(send) => send.to_command(), - Ready::Receive(receive) => receive.to_command(), + Msg::Send(send) => send.to_command(), + Msg::Receive(receive) => receive.to_command(), } } } -impl ToCommand for Promised { +impl ToCommand for PromisedMsg { fn to_command(&self) -> String { match self { - Promised::Send(send) => send.to_command(), - Promised::Receive(receive) => receive.to_command(), + PromisedMsg::Send(send) => send.to_command(), + PromisedMsg::Receive(receive) => receive.to_command(), } } } -impl ParsePromised for Promised { +impl ParsePromised for PromisedMsg { type PromisedArgsError = (); fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - if let Ok(send) = send::Promised::try_parse_promised(cmd, args.clone()) { - return Ok(Promised::Send(send)); + if let Ok(send) = PromisedSend::try_parse_promised(cmd, args.clone()) { + return Ok(PromisedMsg::Send(send)); } - if let Ok(receive) = receive::Promised::try_parse_promised(cmd, args) { - return Ok(Promised::Receive(receive)); + if let Ok(receive) = PromisedReceive::try_parse_promised(cmd, args) { + return Ok(PromisedMsg::Receive(receive)); } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedMsg) -> Self { match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), + PromisedMsg::Send(send) => send.into(), + PromisedMsg::Receive(receive) => receive.into(), } } } -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Msg { + type Promised = PromisedMsg; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedMsg) -> Self { match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), + PromisedMsg::Send(send) => send.into(), + PromisedMsg::Receive(receive) => receive.into(), } } } -impl ParseAbility for Ready { +impl ParseAbility for Msg { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - if let Ok(send) = send::Ready::try_parse(cmd, args.clone()) { - return Ok(Ready::Send(send)); + if let Ok(send) = Send::try_parse(cmd, args.clone()) { + return Ok(Msg::Send(send)); } - if let Ok(receive) = receive::Receive::try_parse(cmd, args) { - return Ok(Ready::Receive(receive)); + if let Ok(receive) = Receive::try_parse(cmd, args) { + return Ok(Msg::Receive(receive)); } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs deleted file mode 100644 index 16e8eac8..00000000 --- a/src/ability/msg/any.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! "Any" message ability (superclass of all message abilities) - -use crate::{ - ability::{arguments, command::Command}, - proof::{parentless::NoParents, same::CheckSame}, - url, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -// use url::Url; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of -/// all other message abilities. -/// -/// For example, the [`msg::Receive`][super::receive::Receive] ability may -/// be proven by the [`msg::Any`][Any] ability in a delegation chain. -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("msg/*") -/// -/// subgraph Invokable -/// send("msg/send") -/// rec("msg/receive") -/// end -/// end -/// -/// sendrun{{"invoke"}} -/// recrun{{"invoke"}} -/// -/// top --> any -/// any --> send -.-> sendrun -/// any --> rec -.-> recrun -/// -/// style any stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Any { - pub from: Option, -} - -impl Command for Any { - const COMMAND: &'static str = "/msg"; -} - -impl From for Ipld { - fn from(any: Any) -> Self { - any.into() - } -} - -impl TryFrom for Any { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl NoParents for Any {} - -impl CheckSame for Any { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl From for arguments::Named { - fn from(any: Any) -> arguments::Named { - let mut args = arguments::Named::new(); - - if let Some(from) = any.from { - args.insert("from".into(), from.to_string().into()); - } - - args - } -} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index d730da8c..a58bbed2 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,11 +2,8 @@ use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, - ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, - url, + ipld, url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -47,22 +44,16 @@ pub struct Receive { pub from: Option, } -// FIXME needs promisory version - const COMMAND: &'static str = "/msg/send"; impl Command for Receive { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedReceive { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Receive { -// type Builder = Receive; -// } - impl TryFrom> for Receive { type Error = (); @@ -94,34 +85,6 @@ impl From for arguments::Named { } } -// impl Checkable for Receive { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Receive { -// type Error = (); // FIXME better error -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// self.from.check_same(&proof.from).map_err(|_| ()) -// } -// } -// -// impl CheckParents for Receive { -// type Parents = super::Any; -// type ParentError = ::Error; -// -// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(from) = &self.from { -// if let Some(proof_from) = &proof.from { -// if &from != &proof_from { -// return Err(()); -// } -// } -// } -// -// Ok(()) -// } -// } - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() @@ -136,13 +99,13 @@ impl TryFrom for Receive { } } -#[derive(Debug, Clone, PartialEq)] -pub struct Promised { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PromisedReceive { pub from: Option>, } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedReceive) -> Self { let mut args = arguments::Named::new(); if let Some(from) = promised.from { @@ -161,10 +124,10 @@ impl From for arguments::Named { } impl promise::Resolvable for Receive { - type Promised = Promised; + type Promised = PromisedReceive; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedReceive { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -185,12 +148,12 @@ impl TryFrom> for Promised { } } - Ok(Promised { from }) + Ok(PromisedReceive { from }) } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedReceive) -> Self { let mut args = arguments::Named::new(); if let Some(from) = promised.from { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 66905c68..3329cae5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize}; /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Ready") +/// sendrun("msg::send::Send") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} @@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize}; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Send { /// The recipient of the message pub to: url::Newtype, @@ -73,7 +73,7 @@ pub struct Ready { /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Ready") +/// sendrun("msg::send::Send") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} @@ -82,7 +82,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedSend { /// The recipient of the message pub to: promise::Resolves, @@ -96,14 +96,14 @@ pub struct Promised { pub message: promise::Resolves, } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Send { + type Promised = PromisedSend; } -impl TryFrom> for Ready { +impl TryFrom> for Send { type Error = (); - fn try_from(named: arguments::Named) -> Result { + fn try_from(named: arguments::Named) -> Result { let mut to = None; let mut from = None; let mut message = None; @@ -130,7 +130,7 @@ impl TryFrom> for Ready { } } - Ok(Ready { + Ok(Send { to: to.ok_or(())?, from: from.ok_or(())?, message: message.ok_or(())?, @@ -138,10 +138,10 @@ impl TryFrom> for Ready { } } -impl TryFrom> for Promised { +impl TryFrom> for PromisedSend { type Error = (); - fn try_from(args: arguments::Named) -> Result { + fn try_from(args: arguments::Named) -> Result { let mut to = None; let mut from = None; let mut message = None; @@ -175,7 +175,7 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedSend { to: to.ok_or(())?, from: from.ok_or(())?, message: message.ok_or(())?, @@ -183,8 +183,8 @@ impl TryFrom> for Promised { } } -impl From for arguments::Named { - fn from(p: Promised) -> Self { +impl From for arguments::Named { + fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ ("to".into(), p.to.into()), ("from".into(), p.from.into()), @@ -195,17 +195,17 @@ impl From for arguments::Named { const COMMAND: &'static str = "/msg/send"; -impl Command for Ready { +impl Command for Send { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedSend { const COMMAND: &'static str = COMMAND; } -impl From for Promised { - fn from(r: Ready) -> Self { - Promised { +impl From for PromisedSend { + fn from(r: Send) -> Self { + PromisedSend { to: promise::Resolves::from(Ok(r.to)), from: promise::Resolves::from(Ok(r.from)), message: promise::Resolves::from(Ok(r.message)), @@ -213,19 +213,19 @@ impl From for Promised { } } -impl TryFrom for Ready { - type Error = Promised; +impl TryFrom for Send { + type Error = PromisedSend; - fn try_from(p: Promised) -> Result { + fn try_from(p: PromisedSend) -> Result { match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Ready { to, from, message }), - Err((to, from, message)) => Err(Promised { to, from, message }), + Ok((to, from, message)) => Ok(Send { to, from, message }), + Err((to, from, message)) => Err(PromisedSend { to, from, message }), } } } -impl From for arguments::Named { - fn from(p: Promised) -> Self { +impl From for arguments::Named { + fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ ("to".into(), p.to.into()), ("from".into(), p.from.into()), diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 29e0510f..f3b122d8 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,4 +1,9 @@ -use super::{crud, msg, wasm}; +use super::{ + crud::{Crud, PromisedCrud}, + msg::{Msg, PromisedMsg}, + ucan::revoke::{PromisedRevoke, Revoke}, + wasm::run as wasm, +}; use crate::{ ability::{ arguments, @@ -9,128 +14,127 @@ use crate::{ ipld, }; use libipld_core::ipld::Ipld; - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Ready { - // FIXME UCAN - Crud(crud::Ready), - Msg(msg::Ready), - Wasm(wasm::run::Ready), +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Preset { + Crud(Crud), + Msg(Msg), + Ucan(Revoke), + Wasm(wasm::Run), } -impl ToCommand for Ready { +impl ToCommand for Preset { fn to_command(&self) -> String { match self { - Ready::Crud(ready) => ready.to_command(), - Ready::Msg(ready) => ready.to_command(), - Ready::Wasm(ready) => ready.to_command(), + Preset::Crud(crud) => crud.to_command(), + Preset::Msg(msg) => msg.to_command(), + Preset::Ucan(ucan) => ucan.to_command(), + Preset::Wasm(wasm) => wasm.to_command(), } } } + #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Promised { - Crud(crud::Promised), - Msg(msg::Promised), - Wasm(wasm::run::Promised), +pub enum PromisedPreset { + Crud(PromisedCrud), + Msg(PromisedMsg), + Ucan(PromisedRevoke), + Wasm(wasm::PromisedRun), } -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Preset { + type Promised = PromisedPreset; } -impl ToCommand for Promised { +impl ToCommand for PromisedPreset { fn to_command(&self) -> String { match self { - Promised::Crud(promised) => promised.to_command(), - Promised::Msg(promised) => promised.to_command(), - Promised::Wasm(promised) => promised.to_command(), + PromisedPreset::Crud(promised) => promised.to_command(), + PromisedPreset::Msg(promised) => promised.to_command(), + PromisedPreset::Ucan(promised) => promised.to_command(), + PromisedPreset::Wasm(promised) => promised.to_command(), } } } -impl ParsePromised for Promised { +impl ParsePromised for PromisedPreset { type PromisedArgsError = (); fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - match crud::Promised::try_parse_promised(cmd, args.clone()) { - Ok(promised) => return Ok(Promised::Crud(promised)), - Err(err) => return Err(err), + match PromisedCrud::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Crud(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match msg::Promised::try_parse_promised(cmd, args.clone()) { - Ok(promised) => return Ok(Promised::Msg(promised)), - Err(err) => return Err(err), + match PromisedMsg::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Msg(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match wasm::run::Promised::try_parse_promised(cmd, args) { - Ok(promised) => return Ok(Promised::Wasm(promised)), + match wasm::PromisedRun::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Wasm(promised)), + Err(ParseAbilityError::UnknownCommand(_)) => (), Err(err) => return Err(err), + } + + match PromisedRevoke::try_parse_promised(cmd, args) { + Ok(promised) => return Ok(PromisedPreset::Ucan(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -impl ParseAbility for Ready { +impl ParseAbility for Preset { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match msg::Ready::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Ready::Msg(builder)), - Err(err) => return Err(err), + match Msg::try_parse(cmd, args.clone()) { + Ok(msg) => return Ok(Preset::Msg(msg)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match crud::Ready::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Ready::Crud(builder)), - Err(err) => return Err(err), + match Crud::try_parse(cmd, args.clone()) { + Ok(crud) => return Ok(Preset::Crud(crud)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match wasm::run::Ready::try_parse(cmd, args) { - Ok(builder) => return Ok(Ready::Wasm(builder)), + match wasm::Run::try_parse(cmd, args.clone()) { + Ok(wasm) => return Ok(Preset::Wasm(wasm)), + Err(ParseAbilityError::UnknownCommand(_)) => (), Err(err) => return Err(err), + } + + match Revoke::try_parse(cmd, args) { + Ok(ucan) => return Ok(Preset::Ucan(ucan)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -// impl From for arguments::Named { -// fn from(builder: Builder) -> Self { -// match builder { -// Builder::Crud(builder) => builder.into(), -// Builder::Msg(builder) => builder.into(), -// Builder::Wasm(builder) => builder.into(), -// } -// } -// } -// -// impl From for arguments::Named { -// fn from(parents: Parents) -> Self { -// match parents { -// Parents::Crud(parents) => parents.into(), -// Parents::Msg(parents) => parents.into(), -// } -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedPreset) -> Self { match promised { - Promised::Crud(promised) => promised.into(), - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), + PromisedPreset::Crud(promised) => promised.into(), + PromisedPreset::Msg(promised) => promised.into(), + PromisedPreset::Ucan(promised) => promised.into(), + PromisedPreset::Wasm(promised) => promised.into(), } } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index f2302b1a..0cf8e210 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,3 +1,4 @@ //! Abilities for and about UCANs themselves +pub mod batch; pub mod revoke; diff --git a/src/ability/ucan/batch.rs b/src/ability/ucan/batch.rs new file mode 100644 index 00000000..e068a5f4 --- /dev/null +++ b/src/ability/ucan/batch.rs @@ -0,0 +1,18 @@ +// use crate::{crypto::varsig, delegation::Delegation, did::Did}; +// use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; +// use std::collections::BTreeMap; +// +// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +// pub struct Batch, Enc: Codec + TryFrom + Into> { +// pub batch: Vec>, // FIXME not quite right; would be nice to include meta etc +// } +// +// pub struct Step, Enc: Codec + TryFrom + Into> { +// pub subject: DID, +// pub audience: Option, +// pub ability: A, // FIXME promise version instead? Promised version shoudl be able to promise any field +// pub cause: Option, +// pub metadata: BTreeMap, +// +// pub cap: Vec>, +// } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index f6268fe5..4c3cd4a2 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,10 +4,8 @@ use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; @@ -15,48 +13,48 @@ use std::fmt::Debug; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Ready { +pub struct Revoke { /// The UCAN to revoke pub ucan: Cid, } const COMMAND: &'static str = "/ucan/revoke"; -impl Command for Ready { +impl Command for Revoke { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedRevoke { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Ready { +impl TryFrom> for Revoke { type Error = (); fn try_from(arguments: arguments::Named) -> Result { let ipld: Ipld = arguments.get("ucan").ok_or(())?.clone(); let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; - Ok(Ready { ucan: nt.cid }) + Ok(Revoke { ucan: nt.cid }) } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Revoke { + type Promised = PromisedRevoke; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRevoke) -> Self { arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) } } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Promised { +pub struct PromisedRevoke { pub ucan: promise::Resolves, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRevoke { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -75,31 +73,31 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedRevoke { ucan: ucan.ok_or(())?, }) } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedRevoke { + fn from(r: Revoke) -> PromisedRevoke { + PromisedRevoke { ucan: Ok(r.ucan).into(), } } } -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { +impl From for arguments::Named { + fn from(p: PromisedRevoke) -> arguments::Named { arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) } } -impl TryFrom for Ready { +impl TryFrom for Revoke { type Error = (); - fn try_from(p: Promised) -> Result { - Ok(Ready { + fn try_from(p: PromisedRevoke) -> Result { + Ok(Revoke { ucan: p.ucan.try_resolve().map_err(|_| ())?, }) } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 8d97d8b9..0d291389 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -13,17 +13,18 @@ use serde::{Deserialize, Serialize}; const COMMAND: &'static str = "/wasm/run"; -impl Command for Ready { +impl Command for Run { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +// FIXME autogenerate for resolvable? +impl Command for PromisedRun { const COMMAND: &'static str = COMMAND; } /// The ability to run a Wasm module on the subject's machine #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Ready { +pub struct Run { /// The Wasm module to run pub module: Module, @@ -34,7 +35,7 @@ pub struct Ready { pub args: Vec, } -impl TryFrom> for Ready { +impl TryFrom> for Run { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -65,7 +66,7 @@ impl TryFrom> for Ready { } } - Ok(Ready { + Ok(Run { module: module.ok_or(())?, function: function.ok_or(())?, args: args.ok_or(())?, @@ -73,19 +74,19 @@ impl TryFrom> for Ready { } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Run { + type Promised = PromisedRun; } /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Promised { +pub struct PromisedRun { pub module: promise::Resolves, pub function: promise::Resolves, pub args: promise::Resolves>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRun { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -108,7 +109,7 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedRun { module: module.ok_or(())?, function: function.ok_or(())?, args: args.ok_or(())?, @@ -116,9 +117,9 @@ impl TryFrom> for Promised { } } -impl From for Promised { - fn from(ready: Ready) -> Self { - Promised { +impl From for PromisedRun { + fn from(ready: Run) -> Self { + PromisedRun { module: promise::Resolves::from(Ok(ready.module)), function: promise::Resolves::from(Ok(ready.function)), args: promise::Resolves::from(Ok(ready @@ -130,8 +131,8 @@ impl From for Promised { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRun) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), ("function".into(), promised.function.into()), diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index f4f03f09..69e69e9f 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -10,12 +10,13 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub struct Envelope< T: Verifiable + Capsule, DID: Did, @@ -65,10 +66,10 @@ impl< // FIXME extract into trait? pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode + From, { let codec = self.varsig_header.codec().clone(); - let ipld: Ipld = self.into(); + let ipld = Ipld::from(self); ipld.encode(codec, w) } @@ -171,8 +172,7 @@ impl< pub fn cid(&self) -> Result where Self: Clone, - Ipld: Encode, - T: Into, + Ipld: Encode + From, { let codec = self.varsig_header.codec().clone(); let mut ipld_buffer = vec![]; @@ -187,11 +187,13 @@ impl< } impl< - T: Verifiable + Capsule + Into, + T: Verifiable + Capsule, DID: Did, V: varsig::Header, Enc: Codec + Into + TryFrom, > From> for Ipld +where + Ipld: From, { fn from(envelope: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); @@ -205,14 +207,76 @@ impl< } impl< - T: Verifiable + Capsule + Into, + T: TryFrom + Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > TryFrom for Envelope +{ + type Error = FromIpldError; + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::List(list) = ipld { + if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { + if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { + if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { + Ok(Envelope { + payload: T::try_from(payload.clone()) + .map_err(FromIpldError::CannotParsePayload)?, + + varsig_header: V::try_from(varsig_header.as_slice()) + .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, + + signature: DID::Signature::try_from(sig.as_slice()) + .map_err(|_| FromIpldError::CannotParseSignature)?, + + _phantom: std::marker::PhantomData, + }) + } else { + Err(FromIpldError::InvalidPayloadCapsule) + } + } else { + Err(FromIpldError::InvalidVarsigContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum FromIpldError { + #[error("Invalid signature container")] + InvalidSignatureContainer, + + #[error("Invalid varsig container")] + InvalidVarsigContainer, + + #[error("Cannot parse payload: {0}")] + CannotParsePayload(#[from] E), + + #[error("Cannot parse varsig header")] + CannotParseVarsigHeader, + + #[error("Cannot parse signature")] + CannotParseSignature, + + #[error("Invalid payload capsule")] + InvalidPayloadCapsule, +} + +impl< + T: Verifiable + Capsule, DID: Did, V: varsig::Header, Enc: Codec + Into + TryFrom, > Encode for Envelope where Self: Clone, - Ipld: Encode, + Ipld: Encode + From, { fn encode( &self, @@ -247,3 +311,38 @@ pub enum ValidateError { #[error("Signature verification failed: {0}")] VerifyError(#[from] signature::Error), } + +impl< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Serialize for Envelope +{ + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: Serializer, + { + self.clone().serialize(serializer) + } +} + +impl< + 'de, + T: Verifiable + Capsule + TryFrom, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Deserialize<'de> for Envelope +where + Envelope: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> std::prelude::v1::Result + where + D: Deserializer<'de>, + { + let ipld: Ipld = Deserialize::deserialize(deserializer)?; + Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) + } +} diff --git a/src/delegation.rs b/src/delegation.rs index 852b61b9..a26f2ba9 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,7 +32,8 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use policy::predicate::Predicate; +use policy::Predicate; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use web_time::SystemTime; @@ -162,3 +163,48 @@ impl, Enc: Codec + Into + TryFrom> signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } + +impl, Enc: Codec + TryFrom + Into> TryFrom + for Delegation +where + Payload: TryFrom, +{ + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Delegation) + } +} + +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(delegation: Delegation) -> Self { + delegation.0.into() + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Delegation +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> + for Delegation +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Delegation) + } +} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 1e399e43..e7dc133b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,4 +1,4 @@ -use super::{payload::Payload, policy::predicate::Predicate, store::Store, Delegation}; +use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs deleted file mode 100644 index 9ab7e613..00000000 --- a/src/delegation/delegable.rs +++ /dev/null @@ -1,51 +0,0 @@ -// use crate::{ -// ability::{ -// arguments, -// command::ToCommand, -// parse::{ParseAbility, ParseAbilityError}, -// }, -// proof::checkable::Checkable, -// }; -// use libipld_core::ipld::Ipld; -// -// /// A trait for types that can be delegated. -// /// -// /// Since [`Delegation`]s may omit fields (until [`Invocation`]), -// /// this trait helps associate the delegatable variant to the invocable one. -// /// -// /// [`Delegation`]: crate::delegation::Delegation -// /// [`Invocation`]: crate::invocation::Invocation -// // FIXME NOTE: don't need parse ability, because parse -> builder -> self -// // FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. -// pub trait Delegable: Sized { -// /// A delegation with some arguments filled. -// type Builder: TryInto -// + From -// + Checkable -// + ParseAbility -// + ToCommand -// + Into>; -// -// fn into_command(self) -> String { -// Self::Builder::from(self).to_command() -// } -// -// fn into_named_args(self) -> arguments::Named { -// Self::Builder::from(self).into() -// } -// -// fn try_parse_to_ready( -// command: &str, -// named: arguments::Named, -// ) -> Result::ArgsErr>> { -// let builder = Self::Builder::try_parse(command, named)?; -// builder.try_into().map_err(|err| todo!()) -// } -// -// fn try_from_named( -// named: arguments::Named, -// ) -> Result::ArgsErr>> { -// let builder = Self::Builder::try_parse("", named)?; -// builder.try_into().map_err(|err| todo!()) -// } -// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0b9c33ad..5408bdff 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::policy::predicate::Predicate; +use super::policy::Predicate; use crate::{ capsule::Capsule, crypto::Nonce, diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 25985d64..ae88cdef 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,4 +1,10 @@ -pub mod collection; -pub mod ir; -pub mod predicate; +//! Policy language. +//! +//! The policy language is a simple predicate language extended with [`jq`]-style selectors. +//! +//! [`jq`]: https://stedolan.github.io/jq/ + pub mod selector; + +mod predicate; +pub use predicate::*; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs deleted file mode 100644 index f897fe9e..00000000 --- a/src/delegation/policy/ir.rs +++ /dev/null @@ -1,100 +0,0 @@ -//FIXME rename core -use super::{ - collection::Collection, - selector::{error::SelectorErrorReason, SelectorError}, -}; -use crate::ipld; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -pub trait Resolve { - fn resolve(self, ctx: &Ipld) -> Result; -} - -impl Resolve for Ipld { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for ipld::Newtype { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for ipld::Number { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for Collection { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for String { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -pub trait TryFromIpld: Sized { - fn try_from_ipld(ipld: Ipld) -> Result; -} - -impl TryFromIpld for Ipld { - fn try_from_ipld(ipld: Ipld) -> Result { - Ok(ipld) - } -} - -impl TryFromIpld for ipld::Newtype { - fn try_from_ipld(ipld: Ipld) -> Result { - Ok(ipld::Newtype(ipld)) - } -} - -impl TryFromIpld for ipld::Number { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), - Ipld::Float(f) => Ok(ipld::Number::Float(f)), - _ => Err(SelectorErrorReason::NotANumber), - } - } -} - -impl TryFromIpld for String { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::String(s) => Ok(s), - _ => Err(SelectorErrorReason::NotAString), - } - } -} - -impl TryFromIpld for Collection { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::List(xs) => Ok(Collection::Array(xs.into_iter().try_fold( - vec![], - |mut acc, v| { - acc.push(TryFromIpld::try_from_ipld(v)?); - Ok(acc) - }, - )?)), - Ipld::Map(xs) => Ok(Collection::Map(xs.into_iter().try_fold( - BTreeMap::new(), - |mut map, (k, v)| { - let value = TryFromIpld::try_from_ipld(v)?; - map.insert(k, value); - Ok(map) - }, - )?)), - _ => Err(SelectorErrorReason::NotACollection), - } - } -} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 14e0ced3..6f9533e6 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,8 +1,5 @@ -use super::{ - collection::Collection, - selector::{or::SelectorOr, SelectorError}, -}; -use crate::{ability::arguments, ipld}; +use super::selector::{Select, SelectorError}; +use crate::ipld; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -19,15 +16,15 @@ pub enum Predicate { False, // Comparison - Equal(SelectorOr, SelectorOr), + Equal(Select, Select), - GreaterThan(SelectorOr, SelectorOr), - GreaterThanOrEqual(SelectorOr, SelectorOr), + GreaterThan(Select, Select), + GreaterThanOrEqual(Select, Select), - LessThan(SelectorOr, SelectorOr), - LessThanOrEqual(SelectorOr, SelectorOr), + LessThan(Select, Select), + LessThanOrEqual(Select, Select), - Like(SelectorOr, SelectorOr), + Like(Select, Select), // Connectives Not(Box), @@ -35,8 +32,8 @@ pub enum Predicate { Or(Box, Box), // Collection iteration - Every(SelectorOr, Box), // ∀x ∈ xs - Some(SelectorOr, Box), // ∃x ∈ xs + Every(Select, Box), // ∀x ∈ xs + Some(Select, Box), // ∃x ∈ xs } impl Predicate { @@ -107,17 +104,17 @@ impl Arbitrary for Predicate { let leaf = prop_oneof![ Just(Predicate::True), Just(Predicate::False), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) ]; @@ -132,9 +129,9 @@ impl Arbitrary for Predicate { let quantified = leaf.clone().prop_recursive(8, 16, 4, |inner| { prop_oneof![ - (SelectorOr::arbitrary(), inner.clone()) + (Select::arbitrary(), inner.clone()) .prop_map(|(xs, p)| { Predicate::Every(xs, Box::new(p)) }), - (SelectorOr::arbitrary(), inner.clone()) + (Select::arbitrary(), inner.clone()) .prop_map(|(xs, p)| { Predicate::Some(xs, Box::new(p)) }), ] }); diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index aa0fa677..4ee35a1b 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -1,8 +1,14 @@ -pub mod error; -pub mod op; -pub mod or; +pub mod filter; -use error::{ParseError, SelectorErrorReason}; +mod error; +mod select; +mod selectable; + +pub use error::{ParseError, SelectorErrorReason}; +pub use select::Select; +pub use selectable::Selectable; + +use filter::Filter; use nom::{ self, branch::alt, @@ -19,11 +25,11 @@ use std::{fmt, str::FromStr}; use thiserror::Error; #[derive(Debug, Clone, PartialEq)] -pub struct Selector(Vec); +pub struct Selector(pub Vec); pub fn parse(input: &str) -> IResult<&str, Selector> { - let without_this = many1(op::parse); - let with_this = preceded(char('.'), many0(op::parse)); + let without_this = many1(filter::parse); + let with_this = preceded(char('.'), many0(filter::parse)); // NOTE: must try without_this this first, to disambiguate `.field` from `.` let p = map_res(alt((without_this, with_this)), |found| { @@ -38,9 +44,9 @@ pub fn parse_this(input: &str) -> IResult<&str, Selector> { context("this", p)(input) } -pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { - let p = many1(op::parse); - context("selector ops", p)(input) +pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { + let p = many1(filter::parse); + context("filters", p)(input) } impl fmt::Display for Selector { @@ -93,10 +99,7 @@ pub struct SelectorError { } impl SelectorError { - pub fn from_refs( - path_refs: &Vec<&op::SelectorOp>, - reason: SelectorErrorReason, - ) -> SelectorError { + pub fn from_refs(path_refs: &Vec<&Filter>, reason: SelectorErrorReason) -> SelectorError { SelectorError { selector: Selector(path_refs.iter().map(|op| (*op).clone()).collect()), reason, diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/filter.rs similarity index 62% rename from src/delegation/policy/selector/op.rs rename to src/delegation/policy/selector/filter.rs index 8ff8a47a..b7273bdc 100644 --- a/src/delegation/policy/selector/op.rs +++ b/src/delegation/policy/selector/filter.rs @@ -18,18 +18,18 @@ use std::{fmt, str::FromStr}; use proptest::prelude::*; #[derive(Debug, Clone, PartialEq, EnumAsInner)] -pub enum SelectorOp { - ArrayIndex(i32), // [2] - Field(String), // ["key"] (or .key) - Values, // .[] - Try(Box), // ? +pub enum Filter { + ArrayIndex(i32), // [2] + Field(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? } -impl fmt::Display for SelectorOp { +impl fmt::Display for Filter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), - SelectorOp::Field(k) => { + Filter::ArrayIndex(i) => write!(f, "[{}]", i), + Filter::Field(k) => { if let Some(first) = k.chars().next() { if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { write!(f, ".{}", k) @@ -40,85 +40,85 @@ impl fmt::Display for SelectorOp { write!(f, "[\"{}\"]", k) } } - SelectorOp::Values => write!(f, "[]"), - SelectorOp::Try(inner) => write!(f, "{}?", inner), + Filter::Values => write!(f, "[]"), + Filter::Try(inner) => write!(f, "{}?", inner), } } } -pub fn parse(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse(input: &str) -> IResult<&str, Filter> { let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } -pub fn parse_non_try(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { let p = alt((parse_values, parse_array_index, parse_field)); context("non_try", p)(input) } -pub fn parse_try(input: &str) -> IResult<&str, SelectorOp> { - let p = map_res(terminated(parse_non_try, tag("?")), |found: SelectorOp| { - Ok::(SelectorOp::Try(Box::new(found))) +pub fn parse_try(input: &str) -> IResult<&str, Filter> { + let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { + Ok::(Filter::Try(Box::new(found))) }); context("try", p)(input) } -pub fn parse_array_index(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { let num = map_opt(tag("-"), |s: &str| { let (_rest, matched) = digit1::<&str, ()>(s).ok()?; matched.parse::().ok() }); let array_index = map_res(delimited(char('['), num, char(']')), |idx| { - Ok::(SelectorOp::ArrayIndex(idx)) + Ok::(Filter::ArrayIndex(idx)) }); context("array_index", array_index)(input) } -pub fn parse_values(input: &str) -> IResult<&str, SelectorOp> { - context("values", tag("[]"))(input).map(|(rest, _)| (rest, SelectorOp::Values)) +pub fn parse_values(input: &str) -> IResult<&str, Filter> { + context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } -pub fn parse_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_field(input: &str) -> IResult<&str, Filter> { let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } -pub fn parse_dot_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); context("dot_field", p)(input) } -pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { - Ok::(SelectorOp::Field(found.to_string())) + Ok::(Filter::Field(found.to_string())) }); context("dot_field", p)(input) } -pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); - Ok::(SelectorOp::Field(key)) + Ok::(Filter::Field(key)) }); context("dot_field", p)(input) } -pub fn parse_delim_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { let field = String::from_iter(found); - Ok::(SelectorOp::Field(field)) + Ok::(Filter::Field(field)) }); context("delimited_field", p)(input) } -impl FromStr for SelectorOp { +impl FromStr for Filter { type Err = nom::Err; fn from_str(s: &str) -> Result { @@ -128,30 +128,30 @@ impl FromStr for SelectorOp { } } } -impl Serialize for SelectorOp { +impl Serialize for Filter { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } -impl<'de> Deserialize<'de> for SelectorOp { +impl<'de> Deserialize<'de> for Filter { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; - SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) + Filter::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) } } #[cfg(feature = "test_utils")] -impl Arbitrary for SelectorOp { +impl Arbitrary for Filter { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { prop_oneof![ - i32::arbitrary().prop_map(|i| SelectorOp::ArrayIndex(i)), - String::arbitrary().prop_map(SelectorOp::Field), - Just(SelectorOp::Values), - // FIXME prop_recursive::lazy(|_| { SelectorOp::arbitrary_with(()).prop_map(SelectorOp::Try) }), + i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), + String::arbitrary().prop_map(Filter::Field), + Just(Filter::Values), + // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), ] .boxed() } diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs index 74cf2699..f94bc514 100644 --- a/src/delegation/policy/selector/or.rs +++ b/src/delegation/policy/selector/or.rs @@ -1,5 +1,5 @@ use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; -use crate::delegation::policy::ir::TryFromIpld; +use crate::delegation::policy::ir::Selectable; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -7,16 +7,16 @@ use serde::{Deserialize, Serialize}; use proptest::prelude::*; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOr { +pub enum Select { Get(Vec), Pure(T), } -impl SelectorOr { +impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { - SelectorOr::Pure(inner) => Ok(inner), - SelectorOr::Get(ops) => { + Select::Pure(inner) => Ok(inner), + Select::Get(ops) => { ops.iter() .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { seen_ops.push(op); @@ -24,7 +24,7 @@ impl SelectorOr { match op { SelectorOp::Try(inner) => { let op: SelectorOp = *inner.clone(); - let ipld: Ipld = SelectorOr::Get::(vec![op]) + let ipld: Ipld = Select::Get::(vec![op]) .resolve(ctx) .unwrap_or(Ipld::Null); @@ -90,7 +90,7 @@ impl SelectorOr { } }) .and_then(|(ipld, ref path)| { - T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) }) } } @@ -98,15 +98,15 @@ impl SelectorOr { } #[cfg(feature = "test_utils")] -impl Arbitrary for SelectorOr { +impl Arbitrary for Select { type Parameters = T::Parameters; type Strategy = BoxedStrategy; fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { prop_oneof![ - T::arbitrary_with(t_params).prop_map(SelectorOr::Pure), + T::arbitrary_with(t_params).prop_map(Select::Pure), // FIXME add params that make this actually correspond to data - prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(SelectorOr::Get), + prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(Select::Get), ] .boxed() } diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs new file mode 100644 index 00000000..e6064911 --- /dev/null +++ b/src/delegation/policy/selector/select.rs @@ -0,0 +1,112 @@ +use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Select { + Get(Vec), + Pure(T), +} + +impl Select { + pub fn resolve(self, ctx: &Ipld) -> Result { + match self { + Select::Pure(inner) => Ok(inner), + Select::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + Filter::Try(inner) => { + let op: Filter = *inner.clone(); + let ipld: Ipld = Select::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + Filter::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )); + }; + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), + } + }; + + Ok((result?, seen_ops)) + } + Filter::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + Filter::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Select { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_params).prop_map(Select::Pure), + // FIXME add params that make this actually correspond to data + prop::collection::vec(Filter::arbitrary(), 1..10).prop_map(Select::Get), + ] + .boxed() + } +} diff --git a/src/delegation/policy/selector/selectable.rs b/src/delegation/policy/selector/selectable.rs new file mode 100644 index 00000000..9fa22186 --- /dev/null +++ b/src/delegation/policy/selector/selectable.rs @@ -0,0 +1,62 @@ +use super::error::SelectorErrorReason; +use crate::ipld; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +pub trait Selectable: Sized { + fn try_select(ipld: Ipld) -> Result; +} + +impl Selectable for Ipld { + fn try_select(ipld: Ipld) -> Result { + Ok(ipld) + } +} + +impl Selectable for ipld::Newtype { + fn try_select(ipld: Ipld) -> Result { + Ok(ipld::Newtype(ipld)) + } +} + +impl Selectable for ipld::Number { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), + Ipld::Float(f) => Ok(ipld::Number::Float(f)), + _ => Err(SelectorErrorReason::NotANumber), + } + } +} + +impl Selectable for String { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::String(s) => Ok(s), + _ => Err(SelectorErrorReason::NotAString), + } + } +} + +impl Selectable for ipld::Collection { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::List(xs) => Ok(ipld::Collection::Array(xs.into_iter().try_fold( + vec![], + |mut acc, v| { + acc.push(Selectable::try_select(v)?); + Ok(acc) + }, + )?)), + Ipld::Map(xs) => Ok(ipld::Collection::Map(xs.into_iter().try_fold( + BTreeMap::new(), + |mut map, (k, v)| { + let value = Selectable::try_select(v)?; + map.insert(k, value); + Ok(map) + }, + )?)), + _ => Err(SelectorErrorReason::NotACollection), + } + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 130043b2..34a4c5c5 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,7 +1,7 @@ use super::Store; use crate::{ crypto::varsig, - delegation::{policy::predicate::Predicate, Delegation}, + delegation::{policy::Predicate, Delegation}, did::Did, }; use libipld_core::{cid::Cid, codec::Codec}; diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index a474fdc1..364c0093 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,6 +1,6 @@ use crate::{ crypto::varsig, - delegation::{policy::predicate::Predicate, Delegation}, + delegation::{policy::Predicate, Delegation}, did::Did, }; use libipld_core::{cid::Cid, codec::Codec}; @@ -8,7 +8,6 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; diff --git a/src/invocation.rs b/src/invocation.rs index a878bebc..aeba3097 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -32,6 +32,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; +use serde::{Deserialize, Serialize}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. @@ -56,14 +57,14 @@ pub struct Invocation< /// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Invocation< - ability::preset::Ready, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, >; pub type PresetPromised = Invocation< - ability::preset::Promised, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, @@ -180,15 +181,47 @@ impl, Enc: Codec + TryFrom + Into> } } -impl, Enc: Codec + TryFrom + Into> - Invocation +impl, Enc: Codec + TryFrom + Into> + From> for Ipld { + fn from(invocation: Invocation) -> Self { + invocation.0.into() + } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl, Enc: Codec + TryFrom + Into> TryFrom + for Invocation +where + Payload: TryFrom, { - fn from(invocation: Invocation) -> Self { - invocation.0.into() + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Invocation) + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Invocation +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, A, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> + Deserialize<'de> for Invocation +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Invocation) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 164faa54..71bfa770 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -5,12 +5,11 @@ use super::{ Invocation, }; use crate::{ - ability::{arguments, parse::ParseAbilityError, ucan}, + ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{signature, varsig, Nonce}, delegation, did::Did, invocation::promise, - // proof::prove::Prove, time::Timestamp, }; use libipld_core::{ @@ -37,10 +36,16 @@ pub struct Agent< V: varsig::Header, Enc: Codec + Into + TryFrom, > { + /// The agent's [`DID`]. pub did: &'a DID, + /// A [`delegation::Store`][delegation::store::Store]. pub delegation_store: &'a mut D, + + /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: &'a mut S, + + /// A [`promise::Store`] for the agent's unresolved promises. pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, @@ -196,7 +201,7 @@ where let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); self.unresolved_promise_index - .put( + .put_waiting( promised.cid()?, waiting_on.into_iter().collect::>(), ) @@ -237,9 +242,9 @@ where // FIXME return type ) -> Result, ()> where - T: From, + T: From, { - let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); + let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { vec![] } else { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 8bf0e2a9..6293e8f2 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -5,7 +5,7 @@ use crate::{ crypto::Nonce, delegation::{ self, - policy::{predicate::Predicate, selector::SelectorError}, + policy::{selector::SelectorError, Predicate}, }, did::{Did, Verifiable}, time::{Expired, Timestamp}, diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index e9b351ad..c65c70da 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -2,7 +2,7 @@ use crate::{ ability::{ arguments, command::ToCommand, - parse::{ParseAbility, ParseAbilityError, ParsePromised}, + parse::{ParseAbility, ParsePromised}, }, invocation::promise::Pending, ipld, @@ -11,9 +11,6 @@ use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeSet, fmt}; use thiserror::Error; -// FIXME rename "Unresolved" -// FIXME better name - /// A trait for [`Delegable`]s that can be deferred (by promises). /// /// FIXME exmaples @@ -29,18 +26,6 @@ pub trait Resolvable: Sized + ParseAbility + ToCommand { + ParsePromised // TryFrom> + Into>; - // fn into_promised(self) -> Self::Promised - // where - // ::PromisedArgsError: fmt::Debug, - // { - // // FIXME In no way efficient... override where possible, or just cut the impl - // let cmd = &builder.to_command(); - // let named_ipld: arguments::Named = builder.into(); - // let promised_ipld: arguments::Named = named_ipld.into(); - // ::Promised::try_parse_promised(cmd, promised_ipld) - // .expect("promise to always be possible from a ready ability") - // } - /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> where @@ -102,6 +87,6 @@ pub enum ResolveError { #[error("The promise is still has arguments waiting to be resolved")] StillWaiting(Pending), - #[error("The resolved promise was unable to reify a Builder")] + #[error("The resolved promise was unable to reify an ability from IPLD")] ConversionError, } diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 43ea8ee4..3a221e41 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -14,7 +14,7 @@ pub struct MemoryStore { impl Store for MemoryStore { type PromiseStoreError = Infallible; - fn put( + fn put_waiting( &mut self, invocation: Cid, waiting_on: Vec, @@ -25,7 +25,10 @@ impl Store for MemoryStore { Ok(()) } - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError> { + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseStoreError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 0bee796f..02d80686 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -5,10 +5,14 @@ use std::collections::BTreeSet; pub trait Store { type PromiseStoreError; - // NOTE put_waiting - fn put(&mut self, invocation: Cid, waiting_on: Vec) - -> Result<(), Self::PromiseStoreError>; + fn put_waiting( + &mut self, + invocation: Cid, + waiting_on: Vec, + ) -> Result<(), Self::PromiseStoreError>; - // NOTE get waiting - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError>; + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseStoreError>; } diff --git a/src/ipld.rs b/src/ipld.rs index 2c3a79db..59116dac 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -7,6 +7,7 @@ //! [`Ipld`]: libipld_core::ipld::Ipld // mod enriched; +mod collection; mod newtype; mod number; mod promised; @@ -14,6 +15,7 @@ mod promised; pub mod cid; // pub use enriched::Enriched; +pub use collection::Collection; pub use newtype::Newtype; pub use number::Number; pub use promised::*; diff --git a/src/delegation/policy/collection.rs b/src/ipld/collection.rs similarity index 100% rename from src/delegation/policy/collection.rs rename to src/ipld/collection.rs index 1cab0451..1478c805 100644 --- a/src/delegation/policy/collection.rs +++ b/src/ipld/collection.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use crate::ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; #[cfg(feature = "test_utils")] diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f202599b..88132b2b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -43,65 +43,6 @@ use super::cid; #[serde(transparent)] pub struct Newtype(pub Ipld); -// impl Eq for Newtype {} -// -// impl PartialOrd for Newtype { -// fn partial_cmp(&self, other: &Self) -> Option { -// match (&self.0, &other.0) { -// (Ipld::Null, Ipld::Null) => Some(Ordering::Equal), -// (Ipld::Null, _) => Some(Ordering::Less), -// -// // -// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bool(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Float(lhs_f), Ipld::Float(rhs_f)) => { -// OrderedFloat(*lhs_f).partial_cmp(&OrderedFloat(*rhs_f)) -// } -// (lhs @ Ipld::Float(_), rhs) => Some(Ordering::Less), -// -// // -// (Ipld::Integer(lhs), Ipld::Integer(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Integer(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::String(lhs), Ipld::String(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::String(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Bytes(lhs), Ipld::Bytes(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bytes(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Link(lhs), Ipld::Link(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Link(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::List(lhs), Ipld::List(rhs)) => { -// let result = lhs.iter().zip(rhs).try_fold((), |acc, (l, r)| { -// match Newtype(*l).partial_cmp(&Newtype(*r)) { -// Some(Ordering::Equal) => Ok(()), -// Some(other) => Err(other), -// None => Err(Ordering::Less), -// } -// }); -// -// match result { -// Ok(()) => Some(Ordering::Equal), -// Err(comp) => Some(comp), -// } -// } -// (Ipld::List(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Map(lhs), Ipld::Map(rhs)) => None, -// (Ipld::Map(_), _) => Some(Ordering::Less), -// } -// } -// } - impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/lib.rs b/src/lib.rs index 1c21c44a..571310bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; +pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 00000000..126ff504 --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,38 @@ +use crate::{crypto::varsig, delegation::Delegation, did::Did}; +use libipld_core::{cid::Cid, codec::Codec, link::Link}; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Proof, Enc: Codec + TryFrom + Into> { + pub prf: Vec>>, +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Proof +{ + fn serialize(&self, serializer: S) -> Result { + let chain = self + .prf + .iter() + .map(|link| link.to_string()) + .collect::>(); + + chain.serialize(serializer) + } +} + +impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> + for Proof +{ + fn deserialize>(deserializer: D) -> Result { + let prf = Vec::::deserialize(deserializer)? + .into_iter() + .map(|s| { + let cid: Cid = s.try_into().map_err(serde::de::Error::custom)?; + Ok(cid.into()) + }) + .collect::, _>>()?; + + Ok(Proof { prf }) + } +} diff --git a/src/reader.rs b/src/reader.rs index a108640f..2a4e12c5 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2,10 +2,8 @@ //! //! See the [`Reader`] struct for more information. -mod builder; mod generic; mod promised; -pub use builder::Builder; pub use generic::Reader; pub use promised::Promised; diff --git a/src/reader/builder.rs b/src/reader/builder.rs deleted file mode 100644 index e772e239..00000000 --- a/src/reader/builder.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::Reader; -use crate::ability::arguments; -use serde::{Deserialize, Serialize}; - -/// A helper newtype that marks a value as being a [`Delegable::Builder`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Builder}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Builder(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -impl>> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Builder) - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|b| b.0) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index f771b90f..5ec60a84 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -5,9 +5,6 @@ //! - [`Store`] is the storage interface for [`Receipt`]s. //! - [`Responds`] associates the response success type to an [Ability][crate::ability]. -// FIXME consider "assertion"? -// - mod payload; mod responds; @@ -20,26 +17,24 @@ pub use store::Store; use crate::{ ability, crypto::{signature, varsig}, - did, + did::{self, Did}, }; use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; +use serde::{Deserialize, Serialize}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt< - T: Responds, - DID: did::Did, - V: varsig::Header, - C: Codec + Into + TryFrom, ->(pub signature::Envelope, DID, V, C>); +pub struct Receipt, C: Codec + Into + TryFrom>( + pub signature::Envelope, DID, V, C>, +); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt< - ability::preset::Ready, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, @@ -65,3 +60,56 @@ impl, Enc: Codec + Into self.0.varsig_encode(w) } } + +impl, Enc: Codec + TryFrom + Into> + did::Verifiable for Receipt +{ + fn verifier(&self) -> &DID { + &self.0.verifier() + } +} + +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(invocation: Receipt) -> Self { + invocation.0.into() + } +} + +impl, Enc: Codec + TryFrom + Into> + TryFrom for Receipt +where + Payload: TryFrom, +{ + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Receipt) + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Receipt +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T: Responds, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> + Deserialize<'de> for Receipt +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Receipt) + } +} From b156a3a1a1d5d00edb148ea8c5d04a5ab725be1e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 4 Mar 2024 23:37:19 -0800 Subject: [PATCH 174/234] Going through FIXMEs --- Cargo.toml | 1 - examples/counterparts.rs | 2 +- src/ability/arguments/named.rs | 51 +--- src/ability/crud.rs | 34 ++- src/ability/crud/create.rs | 21 +- src/ability/crud/destroy.rs | 16 +- src/ability/crud/read.rs | 21 +- src/ability/crud/update.rs | 58 +++- src/ability/dynamic.rs | 25 -- src/ability/js/config.rs | 120 ++++++++ src/ability/js/parentful.rs | 90 +----- src/ability/js/parentless.rs | 140 ---------- src/ability/preset.rs | 34 ++- src/ability/wasm/run.rs | 2 - src/crypto/nonce.rs | 1 - src/crypto/p521.rs | 4 +- src/crypto/rs256.rs | 6 +- src/crypto/varsig/header/rs256.rs | 18 +- src/delegation.rs | 5 - src/delegation/policy/selector/or.rs | 113 -------- src/invocation/payload.rs | 1 - src/ipld/newtype.rs | 14 +- src/ipld/promised.rs | 39 ++- src/url.rs | 1 - tests/conformance.rs | 391 --------------------------- 25 files changed, 339 insertions(+), 869 deletions(-) create mode 100644 src/ability/js/config.rs delete mode 100644 src/ability/js/parentless.rs delete mode 100644 src/delegation/policy/selector/or.rs delete mode 100644 tests/conformance.rs diff --git a/Cargo.toml b/Cargo.toml index a3b4fcd2..73c08fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,6 @@ thiserror = "1.0" aquamarine = { version = "0.5", optional = true } # FIXME actually use? async-signature = "0.4.0" -# FIXME see if possible to push aquamarkine into dev dependencies? # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/counterparts.rs b/examples/counterparts.rs index a7a4289a..6ad86f4d 100644 --- a/examples/counterparts.rs +++ b/examples/counterparts.rs @@ -1,6 +1,6 @@ use std::error::Error; -// TODO what is this? +// FIXME use? pub fn main() -> Result<(), Box> { println!("Alien Shore!"); Ok(()) diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 7b5baa5f..fc844416 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,4 +1,7 @@ -use crate::{invocation::promise::Pending, ipld}; +use crate::{ + invocation::promise::{Pending, Resolves}, + ipld, +}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -125,7 +128,6 @@ impl FromIterator<(String, T)> for Named { } } -// FIXME move to common ipld module? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] pub enum FromIpldError { NotAMap, @@ -220,49 +222,6 @@ impl TryFrom for Named { } } -use crate::invocation::promise::Resolves; - -// impl> TryFrom> for Named { -// type Error = (); -// -// fn try_from(named: Named) -> Result { -// let btree = named -// .0 -// .into_iter() -// .map(|(k, v)| { -// let ipld = v.try_into().map_err(|_| ())?; -// Ok((k, ipld)) -// }) -// .collect::>()?; -// -// Ok(Named(btree)) -// } -// } -// the trait `From>` is not implemented for `Named` - -// impl From> for Named> { -// fn from(named: Named) -> Named> { -// named -// .into_iter() -// .map(|(k, v)| (k, promise::PromiseOk::Fulfilled(v).into())) -// .collect() -// } -// } -// impl> TryFrom> for Named> { -// type Error = Named; -// -// fn try_from(named: Named) -> Result>, Self::Error> { -// named -// .into_iter() -// .try_fold(Named::new(), |mut btree, (k, v)| { -// let ipld = v.try_into().map_err(|_| named.clone())?; -// btree.insert(k, promise::PromiseOk::Fulfilled(ipld).into()); -// Ok(btree) -// }) -// } -// } - -// FIXME abstract over both of these? impl From> for Named> { fn from(named: Named) -> Named> { let btree: BTreeMap> = named @@ -306,7 +265,7 @@ where fn try_from(resolves: Resolves>) -> Result { resolves - .clone() // FIXME could be a pretty heavy clone + .clone() .try_resolve()? .into_iter() .try_fold(Named::new(), |mut btree, (k, v)| { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c821d75a..0571b77f 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -56,6 +56,7 @@ use destroy::{Destroy, PromisedDestroy}; use libipld_core::ipld::Ipld; use read::{PromisedRead, Read}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use update::{PromisedUpdate, Update}; #[cfg(target_arch = "wasm32")] @@ -78,7 +79,7 @@ pub enum PromisedCrud { } impl ParsePromised for PromisedCrud { - type PromisedArgsError = (); // FIXME + type PromisedArgsError = InvalidArgs; fn try_parse_promised( cmd: &str, @@ -86,32 +87,32 @@ impl ParsePromised for PromisedCrud { ) -> Result> { match PromisedCreate::try_parse_promised(cmd, args.clone()) { Ok(create) => return Ok(PromisedCrud::Create(create)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Create(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedRead::try_parse_promised(cmd, args.clone()) { Ok(read) => return Ok(PromisedCrud::Read(read)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Read(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedUpdate::try_parse_promised(cmd, args.clone()) { Ok(update) => return Ok(PromisedCrud::Update(update)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Update(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedDestroy::try_parse_promised(cmd, args) { Ok(destroy) => return Ok(PromisedCrud::Destroy(destroy)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Destroy(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } @@ -120,6 +121,21 @@ impl ParsePromised for PromisedCrud { } } +#[derive(Debug, Clone, PartialEq, Error)] +pub enum InvalidArgs { + #[error("Invalid args for create: {0}")] + Create(create::FromPromisedArgsError), + + #[error("Invalid args for read: {0}")] + Read(read::FromPromisedArgsError), + + #[error("Invalid args for update: {0}")] + Update(update::FromPromisedArgsError), + + #[error("Invalid args for destroy: {0}")] + Destroy(destroy::FromPromisedArgsError), +} + impl ParseAbility for Crud { type ArgsErr = (); diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 3051676a..bd5b159f 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. @@ -107,7 +108,7 @@ impl Command for PromisedCreate { } impl TryFrom> for PromisedCreate { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -130,7 +131,7 @@ impl TryFrom> for PromisedCreate { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, "args" => { @@ -147,10 +148,10 @@ impl TryFrom> for PromisedCreate { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -158,6 +159,18 @@ impl TryFrom> for PromisedCreate { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl TryFrom> for Create { type Error = (); diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 37ef0aa1..5b5dfbc3 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/destroy` ability. @@ -138,7 +139,7 @@ pub struct PromisedDestroy { } impl TryFrom> for PromisedDestroy { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -160,9 +161,9 @@ impl TryFrom> for PromisedDestroy { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -170,6 +171,15 @@ impl TryFrom> for PromisedDestroy { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 28199c49..35c925fd 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. @@ -91,7 +92,7 @@ pub struct PromisedRead { } impl TryFrom> for PromisedRead { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -114,7 +115,7 @@ impl TryFrom> for PromisedRead { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, "args" => { @@ -131,10 +132,10 @@ impl TryFrom> for PromisedRead { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -142,6 +143,18 @@ impl TryFrom> for PromisedRead { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + const COMMAND: &'static str = "/crud/read"; impl Command for Read { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index b512fa8f..dbe4a4c8 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. @@ -107,7 +108,7 @@ impl Command for PromisedUpdate { } impl TryFrom> for PromisedUpdate { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(named: arguments::Named) -> Result { let mut path = None; @@ -121,7 +122,7 @@ impl TryFrom> for PromisedUpdate { } Ok(ipld) => match ipld { Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), - _ => return Err(()), + other => return Err(FromPromisedArgsError::PathBodyNotAString(other)), }, }, @@ -129,19 +130,22 @@ impl TryFrom> for PromisedUpdate { ipld::Promised::Map(map) => { args = Some(promise::Resolves::new(arguments::Named(map))) } - ipld::Promised::WaitOk(cid) => { + ipld::Promised::WaitOk(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - ipld::Promised::WaitErr(cid) => { + ipld::Promised::WaitErr(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - ipld::Promised::WaitAny(cid) => { + ipld::Promised::WaitAny(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), }, - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(key)), } } @@ -149,6 +153,18 @@ impl TryFrom> for PromisedUpdate { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Path body is not a string")] + PathBodyNotAString(Ipld), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl TryFrom> for Update { type Error = (); @@ -212,8 +228,10 @@ impl TryFrom for Update { } } -impl From for arguments::Named { - fn from(promised: PromisedUpdate) -> Self { +impl TryFrom for arguments::Named { + type Error = FromPromisedUpdateError; + + fn try_from(promised: PromisedUpdate) -> Result { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -228,21 +246,33 @@ impl From for arguments::Named { "args".to_string(), args_res .try_resolve() - .expect("FIXME") + .map_err(FromPromisedUpdateError::UnresolvedArgs)? .iter() .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); // FIXME double check - Some(map) + map.insert(k.clone(), Ipld::try_from(v.clone())?); // FIXME double check + Ok(map) }) - .expect("FIXME") + .map_err(FromPromisedUpdateError::ArgsPending)? .into(), ); } - named + Ok(named) } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedUpdateError { + #[error("Unresolved args")] + UnresolvedArgs(Resolves>), + + #[error("Args pending")] + ArgsPending(>::Error), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl From for PromisedUpdate { fn from(r: Update) -> PromisedUpdate { PromisedUpdate { diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 441c1565..a24bb103 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -5,7 +5,6 @@ use super::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError}, }; -// use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -18,8 +17,6 @@ use js_sys; // NOTE the lack of checking functions! -// FIXME make a NOTE somewhere that Hierarchy is availavle on ability/js/... - /// A "dynamic" ability with the bare minimum of statics /// ///

@@ -118,28 +115,6 @@ impl TryFrom for Dynamic { } } -// impl CheckSame for Dynamic { -// type Error = String; // FIXME better err -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.cmd != proof.cmd { -// return Err("Command mismatch".into()); -// } -// -// self.args.0.iter().try_for_each(|(k, v)| { -// if let Some(proof_v) = proof.args.get(k) { -// if v != proof_v { -// return Err("arguments::Named mismatch".into()); -// } -// } else { -// return Err("arguments::Named mismatch".into()); -// } -// -// Ok(()) -// }) -// } -// } - impl From for Ipld { fn from(dynamic: Dynamic) -> Self { dynamic.into() diff --git a/src/ability/js/config.rs b/src/ability/js/config.rs new file mode 100644 index 00000000..2f42905b --- /dev/null +++ b/src/ability/js/config.rs @@ -0,0 +1,120 @@ +//! JavaScript interface for abilities that *do* require a parent hierarchy + +use crate::{ + ability::{arguments, command::ToCommand, dynamic}, + reader::Reader, +}; +use js_sys::{Function, JsString, Map}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +// FIXME rename +type WithParents = Reader>; + +// FIXME just make into a general config? + +/// The configuration object that expresses an ability (with parents) from JS +#[derive(Debug, Clone, PartialEq, Default)] +#[wasm_bindgen(getter_with_clone)] +pub struct ParentfulConfig { + pub command: String, + + #[wasm_bindgen(js_name = isNonceMeaningful)] + pub is_nonce_meaningful: bool, + + #[wasm_bindgen(js_name = validateShape)] + pub validate_shape: Function, +} + +// NOTE if changed, please update this in the docs for `ParentfulArgs` below +#[wasm_bindgen(typescript_custom_section)] +const PARENTFUL_ARGS: &str = r#" +interface ParentfulArgs { + command: string, + isNonceMeaningful: boolean, + validateShape: Function, +} +"#; + +#[wasm_bindgen] +extern "C" { + /// Named constructor arguments for `ParentfulConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentfulArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// } + /// ``` + #[wasm_bindgen(typescript_type = "ParentfulArgs")] + pub type ParentfulArgs; + + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] + pub fn command(this: &ParentfulArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] + pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] + pub fn validate_shape(this: &ParentfulArgs) -> Function; +} + +#[wasm_bindgen] +impl ParentfulConfig { + /// Construct a new `ParentfulConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentfulConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// } + /// } + /// ); + /// ``` + #[wasm_bindgen(constructor)] + pub fn new(js_obj: ParentfulArgs) -> Result { + Ok(ParentfulConfig { + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + }) + } +} + +impl From for dynamic::Dynamic { + fn from(js: WithParents) -> Self { + dynamic::Dynamic { + cmd: js.env.command, + args: js.val, + } + } +} + +impl ToCommand for ParentfulConfig { + fn to_command(&self) -> String { + self.command.clone() + } +} + +impl Checkable for WithParents { + type Hierarchy = Parentful; +} diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 16b7b689..2f42905b 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -2,7 +2,6 @@ use crate::{ ability::{arguments, command::ToCommand, dynamic}, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, reader::Reader, }; use js_sys::{Function, JsString, Map}; @@ -13,6 +12,8 @@ use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename type WithParents = Reader>; +// FIXME just make into a general config? + /// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] @@ -24,12 +25,6 @@ pub struct ParentfulConfig { #[wasm_bindgen(js_name = validateShape)] pub validate_shape: Function, - - #[wasm_bindgen(js_name = checkSame)] - pub check_same: Function, - - #[wasm_bindgen(skip)] - pub check_parent: BTreeMap, } // NOTE if changed, please update this in the docs for `ParentfulArgs` below @@ -39,8 +34,6 @@ interface ParentfulArgs { command: string, isNonceMeaningful: boolean, validateShape: Function, - checkSame: Function, - checkParent: Map } "#; @@ -59,8 +52,6 @@ extern "C" { /// command: string, /// isNonceMeaningful: boolean, /// validateShape: Function, - /// checkSame: Function, - /// checkParent: Map /// } /// ``` #[wasm_bindgen(typescript_type = "ParentfulArgs")] @@ -77,14 +68,6 @@ extern "C" { /// Parser validator #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentfulArgs) -> Function; - - /// Validate an instance against a candidate proof of the same shape - #[wasm_bindgen(js_name = checkSame)] - pub fn check_same(this: &ParentfulArgs) -> Function; - - /// Validate an instance against a candidate proof containing a parent - #[wasm_bindgen(js_name = checkParent)] - pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] @@ -103,18 +86,7 @@ impl ParentfulConfig { /// return true; /// } /// return false; - /// }, - /// checkSame: (proof, args) => { - /// if (proof.to === args.to && proof.message === args.message) { - /// return true; - /// } - /// return false; - /// }, - /// checkParent: new Map([ - /// ["msg/*", (proof, args) => { - /// proof.to === args.to && proof.message === args.message - /// }] - /// ]) + /// } /// } /// ); /// ``` @@ -124,29 +96,6 @@ impl ParentfulConfig { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), - check_same: check_same(&js_obj), - check_parent: { - let mut btree = BTreeMap::new(); - let mut acc = Ok(()); - - check_parent(&js_obj).for_each(&mut |value, key| { - // |value, key| is correct ------^^^^^^^^^^^^ - if let Ok(_) = &acc { - match key.as_string() { - None => acc = Err(JsString::from("Key is not a string")), // FIXME better err - Some(str_key) => match value.dyn_ref::() { - None => acc = Err("Value is not a function".into()), - Some(f) => { - btree.insert(str_key, f.clone()); - acc = Ok(()); - } - }, - } - } - }); - - acc.map(|_| btree)? - }, }) } } @@ -160,39 +109,6 @@ impl From for dynamic::Dynamic { } } -// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for WithParents { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this = wasm_bindgen::JsValue::NULL; - self.env - .check_same - .call2( - &this, - &self.val.clone().into(), - &arguments::Named::from(proof.clone()).into(), - ) - .map(|_| ()) - } -} - -impl CheckParents for WithParents { - type Parents = dynamic::Dynamic; - type ParentError = JsValue; - - fn check_parent(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - if let Some(handler) = self.env.check_parent.get(&parent.cmd) { - let this = wasm_bindgen::JsValue::NULL; - handler - .call2(&this, &self.val.clone().into(), &parent.args.clone().into()) - .map(|_| ()) // FIXME - } else { - Err(JsValue::from("No handler for parent")) - } - } -} - impl ToCommand for ParentfulConfig { fn to_command(&self) -> String { self.command.clone() diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs deleted file mode 100644 index d9f458c7..00000000 --- a/src/ability/js/parentless.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! JavaScript interface for abilities that *do not* require a parent hierarchy - -use crate::{ - ability::{arguments, command::ToCommand, dynamic}, - proof::{parentless::NoParents, same::CheckSame}, - reader::Reader, -}; -use js_sys::Function; -use libipld_core::ipld::Ipld; -use wasm_bindgen::prelude::*; - -// FIXME rename -type WithoutParents = Reader>; - -/// The configuration object that expresses an ability (without parents) from JS -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct ParentlessConfig { - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, -} - -// NOTE if changed, please update this in the docs for `ParentlessArgs` below -#[wasm_bindgen(typescript_custom_section)] -pub const PARENTLESS_ARGS: &str = r#" -interface ParentlessArgs { - command: string, - isNonceMeaningful: boolean, - validateShape: Function, - checkSame: Function, -} -"#; - -#[wasm_bindgen] -extern "C" { - /// Named constructor arguments for `ParentlessConfig` - /// - /// This forms the basis for configuring an ability. - /// These values will be used at runtime to perform - /// checks on the ability (e.g. during delegation), - /// for indexing, and storage (among others). - /// - /// ```typescript - /// // TypeScript - /// interface ParentlessArgs { - /// command: string, - /// isNonceMeaningful: boolean, - /// validateShape: Function, - /// checkSame: Function, - /// } - /// ``` - #[wasm_bindgen(typescript_type = "ParentlessArgs")] - pub type ParentlessArgs; - - /// Get the [`Command`][crate::ability::command::Command] string - #[wasm_bindgen(js_name = command)] - pub fn command(this: &ParentlessArgs) -> String; - - /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) - #[wasm_bindgen(js_name = isNonceMeaningful)] - pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; - - /// Parser validator - #[wasm_bindgen(js_name = validateShape)] - pub fn validate_shape(this: &ParentlessArgs) -> Function; - - /// Validate an instance against a candidate proof of the same shape - #[wasm_bindgen(js_name = checkSame)] - pub fn check_same(this: &ParentlessArgs) -> Function; -} - -#[wasm_bindgen] -impl ParentlessConfig { - /// Construct a new `ParentlessConfig` from JavaScript - /// - /// # Examples - /// - /// ```javascript - /// // JavaScript - /// const msgSendConfig = new ParentlessConfig({ - /// command: "msg/send", - /// isNonceMeaningful: true, - /// validateShape: (args) => { - /// if (args.to && args.message && args.length() === 2) { - /// return true; - /// } - /// return false; - /// }, - /// checkSame: (proof, args) => { - /// proof.to === args.to && proof.message === args.message - /// }, - /// } - /// ); - /// ``` - #[wasm_bindgen(constructor)] - pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { - ParentlessConfig { - command: command(&js_obj), - is_nonce_meaningful: is_nonce_meaningful(&js_obj), - validate_shape: validate_shape(&js_obj), - check_same: check_same(&js_obj), - } - } -} - -impl From for dynamic::Dynamic { - fn from(js: WithoutParents) -> Self { - dynamic::Dynamic { - cmd: js.env.command, - args: js.val, - } - } -} - -// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for WithoutParents { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this = wasm_bindgen::JsValue::NULL; - self.env - .check_same - .call2( - &this, - &self.val.clone().into(), - &arguments::Named::from(proof.clone()).into(), - ) - .map(|_| ()) - } -} - -impl ToCommand for ParentlessConfig { - fn to_command(&self) -> String { - self.command.clone() - } -} - -impl NoParents for ParentlessConfig {} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index f3b122d8..34f74d90 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,5 +1,5 @@ use super::{ - crud::{Crud, PromisedCrud}, + crud::{self, Crud, PromisedCrud}, msg::{Msg, PromisedMsg}, ucan::revoke::{PromisedRevoke, Revoke}, wasm::run as wasm, @@ -15,6 +15,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Preset { @@ -59,7 +60,7 @@ impl ToCommand for PromisedPreset { } impl ParsePromised for PromisedPreset { - type PromisedArgsError = (); + type PromisedArgsError = ParsePromisedError; fn try_parse_promised( cmd: &str, @@ -68,31 +69,50 @@ impl ParsePromised for PromisedPreset { match PromisedCrud::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Crud(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(err) => { + return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Crud( + err, + ))) + } } match PromisedMsg::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Msg(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Msg)), } match wasm::PromisedRun::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Wasm(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Wasm)), } match PromisedRevoke::try_parse_promised(cmd, args) { Ok(promised) => return Ok(PromisedPreset::Ucan(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Ucan)), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } +#[derive(Debug, Clone, Error)] +pub enum ParsePromisedError { + #[error("Crud error: {0}")] + Crud(ParseAbilityError), + + #[error("Msg error")] + Msg, // FIXME + + #[error("Wasm error")] + Wasm, // FIXME + + #[error("Ucan error")] + Ucan, // FIXME +} + impl ParseAbility for Preset { type ArgsErr = (); @@ -102,7 +122,7 @@ impl ParseAbility for Preset { ) -> Result> { match Msg::try_parse(cmd, args.clone()) { Ok(msg) => return Ok(Preset::Msg(msg)), - Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(ParseAbilityError::UnknownCommand(_)) => (), // FIXME Err(err) => return Err(err), } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 0d291389..2aad7530 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,10 +3,8 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index fc83d952..e53472c5 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -2,7 +2,6 @@ //! //! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce -// FIXME use enum_as_inner more? use enum_as_inner::EnumAsInner; use getrandom::getrandom; use libipld_core::{ diff --git a/src/crypto/p521.rs b/src/crypto/p521.rs index 79d09d29..e5b13a37 100644 --- a/src/crypto/p521.rs +++ b/src/crypto/p521.rs @@ -1,8 +1,10 @@ use p521; +use serde::{Deserialize, Serialize}; use signature::Verifier; use std::fmt; -#[derive(Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); impl fmt::Debug for VerifyingKey { diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index dae3a982..de6205cd 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -4,7 +4,7 @@ use rsa; use signature::{SignatureEncoding, Signer, Verifier}; /// The verifying/public key for RS256. -#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); impl PartialEq for VerifyingKey { @@ -22,7 +22,7 @@ impl Verifier for VerifyingKey { } /// The signing/secret key for RS256. -#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); impl Signer for SigningKey { @@ -32,7 +32,7 @@ impl Signer for SigningKey { } /// The signature for RS256. -#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub rsa::pkcs1v15::Signature); impl SignatureEncoding for Signature { diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs index 92f03155..a175af8e 100644 --- a/src/crypto/varsig/header/rs256.rs +++ b/src/crypto/varsig/header/rs256.rs @@ -1,6 +1,7 @@ use super::Header; use crate::crypto::rs256::{Signature, VerifyingKey}; use libipld_core::codec::Codec; +use thiserror::Error; #[derive(Clone, Debug, PartialEq)] pub struct Rs256Header { @@ -8,22 +9,33 @@ pub struct Rs256Header { } impl> TryFrom<&[u8]> for Rs256Header { - type Error = (); // FIXME + type Error = ParseFromBytesError<>::Error>; fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { - let codec = C::try_from(codec_info).map_err(|_| ())?; + let codec = + C::try_from(codec_info).map_err(ParseFromBytesError::CodecPrefixError)?; + return Ok(Rs256Header { codec }); } } } - Err(()) + Err(ParseFromBytesError::InvalidHeader) } } +#[derive(Debug, PartialEq, Clone, Error)] +pub enum ParseFromBytesError { + #[error("Invalid header")] + InvalidHeader, + + #[error("Codec prefix error: {0}")] + CodecPrefixError(#[from] C), +} + impl> From> for Vec { fn from(rs: Rs256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); diff --git a/src/delegation.rs b/src/delegation.rs index a26f2ba9..d67e7f28 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -40,9 +40,6 @@ use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. -/// -/// # Examples -/// FIXME #[derive(Clone, Debug, PartialEq)] pub struct Delegation, Enc: Codec + TryFrom + Into>( pub signature::Envelope, DID, V, Enc>, @@ -63,8 +60,6 @@ impl, Enc: Codec + TryFrom + Into> Ca pub type Preset = Delegation; -// FIXME checkable -> provable? - impl, Enc: Codec + Into + TryFrom> Delegation { diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs deleted file mode 100644 index f94bc514..00000000 --- a/src/delegation/policy/selector/or.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; -use crate::delegation::policy::ir::Selectable; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Select { - Get(Vec), - Pure(T), -} - -impl Select { - pub fn resolve(self, ctx: &Ipld) -> Result { - match self { - Select::Pure(inner) => Ok(inner), - Select::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - SelectorOp::Try(inner) => { - let op: SelectorOp = *inner.clone(); - let ipld: Ipld = Select::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - SelectorOp::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )); - }; - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAList, - )), - } - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, - )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?, seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } - } -} - -#[cfg(feature = "test_utils")] -impl Arbitrary for Select { - type Parameters = T::Parameters; - type Strategy = BoxedStrategy; - - fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_params).prop_map(Select::Pure), - // FIXME add params that make this actually correspond to data - prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(Select::Get), - ] - .boxed() - } -} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6293e8f2..f64a64c9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -153,7 +153,6 @@ impl Payload { } proofs.into_iter().try_fold(&self.issuer, |iss, proof| { - // FIXME extract step function? if *iss != proof.audience { return Err(ValidationError::InvalidSubject.into()); } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 88132b2b..dc20cb14 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,6 +1,7 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -14,7 +15,6 @@ use proptest::prelude::*; #[cfg(feature = "test_utils")] use super::cid; -// FIXME push into the submodules /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. @@ -73,16 +73,20 @@ impl From for Newtype { } impl TryFrom for PathBuf { - type Error = (); // FIXME + type Error = NotAString; fn try_from(wrapped: Newtype) -> Result { match wrapped.0 { Ipld::String(s) => Ok(PathBuf::from(s)), - _ => Err(()), + ipld => Err(NotAString(ipld)), } } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +#[error("Ipld variant is not a string")] +pub struct NotAString(pub Ipld); + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result @@ -90,13 +94,12 @@ impl Newtype { JsError: From<>::Error>, { match Newtype::try_from(js_val) { - Err(_err) => Err(JsError::new("can't convert")), // FIXME + Err(_err) => Err(JsError::new("can't convert")), Ok(nt) => nt.0.try_into().map_err(JsError::from), } } } -// FIXME testme #[cfg(target_arch = "wasm32")] impl From for JsValue { fn from(wrapped: Newtype) -> Self { @@ -132,7 +135,6 @@ impl From for JsValue { } } -// FIXME testme #[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = (); // FIXME diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 52a0c907..781045a9 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -6,7 +6,7 @@ use crate::{ use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use std::{collections::BTreeMap, fmt, path::PathBuf}; /// A recursive data structure whose leaves may be [`Ipld`] or promises. /// @@ -72,6 +72,43 @@ impl Promised { } } +impl fmt::Display for Promised { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + Promised::Null => write!(f, "null"), + Promised::Bool(b) => write!(f, "{}", b), + Promised::Integer(i) => write!(f, "{}", i), + Promised::Float(fl) => write!(f, "{}", fl), + Promised::String(s) => write!(f, "{}", s), + Promised::Bytes(b) => write!(f, "{:?}", b), + Promised::Link(cid) => write!(f, "{}", cid), + Promised::WaitOk(cid) => write!(f, "await/ok: {}", cid), + Promised::WaitErr(cid) => write!(f, "await/err: {}", cid), + Promised::WaitAny(cid) => write!(f, "await/*: {}", cid), + Promised::List(list) => { + write!(f, "[")?; + for (i, promised) in list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", promised)?; + } + write!(f, "]") + } + Promised::Map(map) => { + write!(f, "{{")?; + for (i, (k, v)) in map.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}: {}", k, v)?; + } + write!(f, "}}") + } + } + } +} + impl From for Promised { fn from(ipld: Ipld) -> Promised { match ipld { diff --git a/src/url.rs b/src/url.rs index bb8ca202..6cca29b2 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,6 +1,5 @@ //! URL utilities. -// use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/tests/conformance.rs b/tests/conformance.rs deleted file mode 100644 index 8e24da1f..00000000 --- a/tests/conformance.rs +++ /dev/null @@ -1,391 +0,0 @@ -// use libipld_core::{ipld::Ipld, raw::RawCodec}; -// use serde::{Deserialize, Serialize}; -// use std::{collections::HashMap, fs::File, io::BufReader}; - -// use ucan::{ -// capability::DefaultCapabilityParser, -// did_verifier::DidVerifierMap, -// store::{self, Store}, -// ucan::Ucan, -// DefaultFact, -// }; -// -// trait TestTask { -// fn run(&self, name: &str, report: &mut TestReport); -// } -// -// #[derive(Debug, Default)] -// struct TestReport { -// num_tests: usize, -// successes: Vec, -// failures: Vec, -// } -// -// #[derive(Debug)] -// struct TestFailure { -// name: String, -// error: String, -// } -// -// impl TestReport { -// fn register_success(&mut self, name: &str) { -// self.num_tests += 1; -// self.successes.push(name.to_string()); -// } -// -// fn register_failure(&mut self, name: &str, error: String) { -// self.num_tests += 1; -// self.failures.push(TestFailure { -// name: name.to_string(), -// error, -// }); -// } -// -// fn finish(&self) { -// for success in &self.successes { -// println!("✅ {}", success); -// } -// -// for failure in &self.failures { -// println!("❌ {}: {}", failure.name, failure.error); -// } -// -// println!( -// "{} tests, {} successes, {} failures", -// self.num_tests, -// self.successes.len(), -// self.failures.len() -// ); -// -// if !self.failures.is_empty() { -// panic!(); -// } -// } -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestFixture { -// name: String, -// #[serde(flatten)] -// test_case: TestCase, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// #[serde(tag = "task", rename_all = "camelCase")] -// enum TestCase { -// Verify(VerifyTest), -// Refute(RefuteTest), -// Build(BuildTest), -// ToCID(ToCidTest), -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct VerifyTest { -// inputs: TestInputsTokenAndProofs, -// assertions: TestAssertions, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct RefuteTest { -// inputs: TestInputsTokenAndProofs, -// assertions: TestAssertions, -// errors: Vec, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTest { -// inputs: BuildTestInputs, -// outputs: BuildTestOutputs, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct ToCidTest { -// inputs: ToCidTestInputs, -// outputs: ToCidTestOutputs, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestInputsTokenAndProofs { -// token: String, -// proofs: HashMap, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertions { -// header: TestAssertionsHeader, -// payload: TestAssertionsPayload, -// signature: String, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertionsHeader { -// alg: Option, -// typ: Option, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertionsPayload { -// ucv: Option, -// iss: Option, -// aud: Option, -// exp: Option, -// // TODO: CAP -// // TODO: FCT -// prf: Option>, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTestInputs { -// version: Option, -// issuer_base64_key: String, -// signature_scheme: String, -// audience: Option, -// not_before: Option, -// expiration: Option, -// // TODO CAPABILITIES -// // TODO FACTS -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTestOutputs { -// token: String, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct ToCidTestInputs { -// token: String, -// hasher: String, -// } -// -// // #[derive(Debug, Serialize, Deserialize)] -// // -// // struct ToCidTestOutputs { -// // cid: String, -// // } -// // -// // impl TestTask for VerifyTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let mut store = store::InMemoryStore::::default(); -// // let did_verifier_map = DidVerifierMap::default(); -// // -// // for (_cid, token) in self.inputs.proofs.iter() { -// // store -// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) -// // .unwrap(); -// // } -// // -// // let Ok(ucan) = Ucan::::from_str(&self.inputs.token) -// // else { -// // report.register_failure(name, "failed to parse token".to_string()); -// // -// // return; -// // }; -// // -// // if let Some(alg) = &self.assertions.header.alg { -// // if ucan.algorithm() != alg { -// // report.register_failure( -// // name, -// // format!( -// // "expected algorithm to be {}, but was {}", -// // alg, -// // ucan.algorithm() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(typ) = &self.assertions.header.typ { -// // if ucan.typ() != typ { -// // report.register_failure( -// // name, -// // format!("expected type to be {}, but was {}", typ, ucan.typ()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(ucv) = &self.assertions.payload.ucv { -// // if ucan.version() != ucv { -// // report.register_failure( -// // name, -// // format!("expected version to be {}, but was {}", ucv, ucan.version()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(iss) = &self.assertions.payload.iss { -// // if ucan.issuer() != iss { -// // report.register_failure( -// // name, -// // format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(aud) = &self.assertions.payload.aud { -// // if ucan.audience() != aud { -// // report.register_failure( -// // name, -// // format!( -// // "expected audience to be {}, but was {}", -// // aud, -// // ucan.audience() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // -// // if ucan.expires_at() != self.assertions.payload.exp { -// // report.register_failure( -// // name, -// // format!( -// // "expected expiration to be {:?}, but was {:?}", -// // self.assertions.payload.exp, -// // ucan.expires_at() -// // ), -// // ); -// // -// // return; -// // } -// // -// // if ucan -// // .proofs() -// // .map(|f| f.iter().map(|c| c.to_string()).collect()) -// // != self.assertions.payload.prf -// // { -// // report.register_failure( -// // name, -// // format!( -// // "expected proofs to be {:?}, but was {:?}", -// // self.assertions.payload.prf, -// // ucan.proofs() -// // ), -// // ); -// // -// // return; -// // } -// // -// // let Ok(signature) = serde_json::to_value(ucan.signature()) else { -// // report.register_failure(name, "failed to serialize signature".to_string()); -// // -// // return; -// // }; -// // -// // let Some(signature) = signature.as_str() else { -// // report.register_failure(name, "expected signature to be a string".to_string()); -// // -// // return; -// // }; -// // -// // if signature != self.assertions.signature { -// // report.register_failure( -// // name, -// // format!( -// // "expected signature to be {}, but was {}", -// // self.assertions.signature, signature -// // ), -// // ); -// // -// // return; -// // } -// // -// // if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { -// // report.register_failure(name, err.to_string()); -// // -// // return; -// // } -// // } -// // } -// // -// // impl TestTask for RefuteTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let mut store = store::InMemoryStore::::default(); -// // let did_verifier_map = DidVerifierMap::default(); -// // -// // for (_cid, token) in self.inputs.proofs.iter() { -// // store -// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) -// // .unwrap(); -// // } -// // -// // if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) -// // { -// // if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { -// // report.register_failure( -// // &name, -// // "expected token to fail validation, but it passed".to_string(), -// // ); -// // -// // return; -// // } -// // } -// // } -// // } -// // -// // impl TestTask for BuildTest { -// // fn run(&self, _: &str, _: &mut TestReport) { -// // //TODO: can't assert on signature because of canonicalization issues -// // } -// // } -// // -// // impl TestTask for ToCidTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let ucan = -// // Ucan::::from_str(&self.inputs.token).unwrap(); -// // let hasher = match self.inputs.hasher.as_str() { -// // "SHA2-256" => multihash::Code::Sha2_256, -// // "BLAKE3-256" => multihash::Code::Blake3_256, -// // _ => panic!(), -// // }; -// // -// // let Ok(cid) = ucan.to_cid(Some(hasher)) else { -// // report.register_failure(&name, "failed to convert to CID".to_string()); -// // -// // return; -// // }; -// // -// // if cid.to_string() != self.outputs.cid { -// // report.register_failure( -// // &name, -// // format!( -// // "expected CID to be {}, but was {}", -// // self.outputs.cid, -// // cid.to_string() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // } -// // -// // #[test] -// // fn ucan_0_10_0_conformance_tests() { -// // let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); -// // let reader = BufReader::new(fixtures_file); -// // let fixtures: Vec = serde_json::from_reader(reader).unwrap(); -// // -// // let mut report = TestReport::default(); -// // -// // for fixture in fixtures { -// // match fixture.test_case { -// // TestCase::Verify(test) => test.run(&fixture.name, &mut report), -// // TestCase::Refute(test) => test.run(&fixture.name, &mut report), -// // TestCase::Build(test) => test.run(&fixture.name, &mut report), -// // TestCase::ToCID(test) => test.run(&fixture.name, &mut report), -// // }; -// // -// // report.register_success(&fixture.name); -// // } -// // -// // report.finish(); -// // } From 6055dd2362d136ae8c800bb62d323ca7d56b8586 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 12:03:43 -0800 Subject: [PATCH 175/234] Ahead of ripping out devshell for blst --- flake.lock | 30 +++++++++++++++--------------- flake.nix | 10 ++++++++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index 51184076..8cb8586e 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1705332421, - "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", + "lastModified": 1708939976, + "narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=", "owner": "numtide", "repo": "devshell", - "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec", + "rev": "5ddecd67edbd568ebe0a55905273e56cc82aabe3", "type": "github" }, "original": { @@ -42,11 +42,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { @@ -57,11 +57,11 @@ }, "nixos-unstable": { "locked": { - "lastModified": 1705556346, - "narHash": "sha256-2+ZUEFCKlctTsut81S84xkCccMsZLLX7DA/U3xZ3BqY=", + "lastModified": 1709558755, + "narHash": "sha256-hx4FIbk4X4ve1oiHLOj+VE6dzO4CtXBR5RSU6kaq34M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cefcf19e1c6d4255b2aede5535d04064f6917e9b", + "rev": "207107bbc7d6d19a8b2c36a088d3756d03490243", "type": "github" }, "original": { @@ -88,11 +88,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1705458851, - "narHash": "sha256-uQvEhiv33Zj/Pv364dTvnpPwFSptRZgVedDzoM+HqVg=", + "lastModified": 1709569716, + "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8bf65f17d8070a0a490daf5f1c784b87ee73982c", + "rev": "617579a787259b9a6419492eaac670a5f7663917", "type": "github" }, "original": { @@ -120,11 +120,11 @@ ] }, "locked": { - "lastModified": 1705544242, - "narHash": "sha256-LIi5jGx7kwJjodpJlnQY+X/PZspRpbDO2ypNSmHwOGQ=", + "lastModified": 1709604635, + "narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ff3e4b3ee418009886848d48e4ba236a2f9de789", + "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bee92820..79dc3ef2 100644 --- a/flake.nix +++ b/flake.nix @@ -30,8 +30,14 @@ (import rust-overlay) ]; - pkgs = import nixpkgs {inherit system overlays;}; - unstable = import nixos-unstable {inherit system overlays;}; + pkgs = import nixpkgs { + inherit system overlays; + config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; + }; + unstable = import nixos-unstable { + inherit system overlays; + config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; + }; rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { extensions = [ From cd22a59bfe7e2991041a8089e664aa4fb91d3f98 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 12:12:27 -0800 Subject: [PATCH 176/234] Begn conversion --- flake.nix | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/flake.nix b/flake.nix index 79dc3ef2..f8222ad5 100644 --- a/flake.nix +++ b/flake.nix @@ -93,21 +93,20 @@ node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; + + scripts = [ + ]; + in rec { formatter = pkgs.alejandra; # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - # FIXME This explicitely does not get pulled in under devshell - # stdenv = pkgs.clangStdenv; + stdenv = pkgs.clangStdenv; - devShells.default = pkgs.devshell.mkShell { + devShells.default = pkgs.mkShell { name = "ucan"; - imports = [ - ./pre-commit.nix - ]; - - packages = with pkgs; + nativeBuildInputs = with pkgs; [ direnv rust-toolchain @@ -121,14 +120,30 @@ ] ++ format-pkgs ++ cargo-installs + ++ scripts ++ lib.optionals stdenv.isDarwin darwin-installs; - env = [ - { - name = "RUSTC_WRAPPER"; - value = "${pkgs.sccache}/bin/sccache"; - } - ]; + shellHook = '' + [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg + + # + export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" + + # Setup local Kubo config + if [ ! -e ./.ipfs ]; then + ipfs --repo-dir ./.ipfs --offline init + fi + + unset SOURCE_DATE_EPOCH + + # Run Kubo / IPFS + echo -e "To run Kubo as a local IPFS node, use the following command:" + echo -e "ipfs --repo-dir ./.ipfs --offline daemon" + '' + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' + # See https://github.com/nextest-rs/nextest/issues/267 + export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" + export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; + ''; commands = [ # Release From f5e9cc55c1fe697fa26abd48542afb175832d169 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 21:04:39 -0800 Subject: [PATCH 177/234] Recover command menu --- Cargo.toml | 58 ++++--- flake.lock | 85 ++++++++-- flake.nix | 377 ++++++++++++++++---------------------------- pre-commit.nix | 7 - src/ipld/newtype.rs | 3 + src/ipld/number.rs | 1 + 6 files changed, 240 insertions(+), 291 deletions(-) delete mode 100644 pre-commit.nix diff --git a/Cargo.toml b/Cargo.toml index 73c08fbc..397845d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,48 +31,46 @@ name = "counterparts" path = "examples/counterparts.rs" [dependencies] -getrandom = { version = "0.2", features = ["js", "rdrand"] } -nonempty = { version = "0.9" } -proptest = { version = "1.1", optional = true } -regex = "1.10" -unsigned-varint = "0.7.2" -# Crypto -blst = { version = "0.3.11", optional = true, default-features = false } -ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } -ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } -k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } -p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } -signature = { version = "2.1.0", features = ["alloc"] } +# Docs +aquamarine = { version = "0.5", optional = true } # Encoding base64 = "0.21" + +# Crypto +blst = { version = "0.3.11", optional = true, default-features = false } bs58 = "0.5" -serde = { version = "1.0.188", features = ["derive"] } -serde_derive = "1.0" -nom = "7.1" # Web Stack did_url = "0.1" -url = { version = "2.5", features = ["serde"] } -web-time = "0.2.3" +ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } +ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } + +# Code Convenience +enum-as-inner = "0.6" +getrandom = { version = "0.2", features = ["js", "rdrand"] } +k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } # Interplanetary Stack libipld = { version = "0.16", optional = true } -libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" +libipld-core = { version = "0.16", features = ["serde-codec"] } multihash = { version = "0.18" } - -# Code Convenience -enum-as-inner = "0.6" +nom = "7.1" +nonempty = { version = "0.9" } +p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } +proptest = { version = "1.1", optional = true } +rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } +serde = { version = "1.0.188", features = ["derive"] } +serde_derive = "1.0" +signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" - -# Docs -aquamarine = { version = "0.5", optional = true } - +unsigned-varint = "0.7.2" +url = { version = "2.5", features = ["serde"] } +web-time = "0.2.3" # FIXME actually use? async-signature = "0.4.0" # FIXME also have a wasi target @@ -82,7 +80,7 @@ js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" wasm-bindgen-derive = "0.2" -# FIXME? wasm-bindgen-futures = { version = "0.4" } +# wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] @@ -111,7 +109,7 @@ default = [ "ability-preset", # FIXME temp while developing - "test_utils", + # "test_utils", ] test_utils = ["dep:proptest", "dep:libipld"] diff --git a/flake.lock b/flake.lock index 8cb8586e..ca8b1cb2 100644 --- a/flake.lock +++ b/flake.lock @@ -1,10 +1,29 @@ { "nodes": { - "devshell": { + "command-utils": { "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" }, + "locked": { + "lastModified": 1709698716, + "narHash": "sha256-l1o+s6MQo5Z/0+0wNmjO5z0qRk5iGJPpFnPM48a5Sfg=", + "owner": "expede", + "repo": "nix-command-utils", + "rev": "2cdf5fe375f8206f10dd86d5f79ef1e2fadc5968", + "type": "github" + }, + "original": { + "owner": "expede", + "repo": "nix-command-utils", + "type": "github" + } + }, + "devshell": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, "locked": { "lastModified": 1708939976, "narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=", @@ -23,6 +42,23 @@ "inputs": { "systems": "systems" }, + "locked": { + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { "lastModified": 1701680307, "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", @@ -37,9 +73,9 @@ "type": "github" } }, - "flake-utils_2": { + "flake-utils_3": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1709126324, @@ -71,6 +107,21 @@ } }, "nixpkgs": { + "locked": { + "lastModified": 1709569716, + "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "617579a787259b9a6419492eaac670a5f7663917", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.11", + "type": "indirect" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1704161960, "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", @@ -86,7 +137,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1709569716, "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", @@ -103,10 +154,11 @@ }, "root": { "inputs": { + "command-utils": "command-utils", "devshell": "devshell", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixos-unstable": "nixos-unstable", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "rust-overlay": "rust-overlay" } }, @@ -120,11 +172,11 @@ ] }, "locked": { - "lastModified": 1709604635, - "narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=", + "lastModified": 1709691047, + "narHash": "sha256-2Vwx1FLufoMEcOS8KAwP8H83IP3Hw6ZPrIDHkSXrFCY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d", + "rev": "d55139f3061cdf2c8f5f7bc8d49e884826e6a4ea", "type": "github" }, "original": { @@ -162,6 +214,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f8222ad5..b0935515 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,8 @@ nixpkgs.url = "nixpkgs/nixos-23.11"; nixos-unstable.url = "nixpkgs/nixos-unstable-small"; + command-utils.url = "github:expede/nix-command-utils"; + flake-utils.url = "github:numtide/flake-utils"; devshell.url = "github:numtide/devshell"; @@ -22,6 +24,7 @@ nixos-unstable, nixpkgs, rust-overlay, + command-utils } @ inputs: flake-utils.lib.eachDefaultSystem ( system: let @@ -32,11 +35,10 @@ pkgs = import nixpkgs { inherit system overlays; - config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; }; + unstable = import nixos-unstable { inherit system overlays; - config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; }; rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { @@ -94,18 +96,123 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; - scripts = [ - ]; + cmd = command-utils.cmd.${system}; - in rec { - formatter = pkgs.alejandra; + release = { + "release:host" = cmd "Build release for ${system}" + "${cargo} build --release"; + + "release:wasm:web" = cmd "Build release for wasm32-unknown-unknown with web bindings" + "${wasm-pack} build --release --target=web"; + + "release:wasm:nodejs" = cmd "Build release for wasm32-unknown-unknown with Node.js bindgings" + "${wasm-pack} build --release --target=nodejs"; + }; + + build = { + "build:host" = cmd "Build for ${system}" + "${cargo} build"; + + "build:wasm:web" = cmd "Build for wasm32-unknown-unknown with web bindings" + "${wasm-pack} build --dev --target=web"; + + "build:wasm:nodejs" = cmd "Build for wasm32-unknown-unknown with Node.js bindgings" + "${wasm-pack} build --dev --target=nodejs"; + + "build:node" = cmd "Build JS-wrapped Wasm library" + "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; + + "build:wasi" = cmd "Build for Wasm32-WASI" + "${cargo} build --target wasm32-wasi"; + }; + + bench = { + "bench" = cmd "Run benchmarks, including test utils" + "${cargo} bench --features test_utils"; + + # FIXME align with `bench`? + "bench:host" = cmd "Run host Criterion benchmarks" + "${cargo} criterion"; + + "bench:host:open" = cmd "Open host Criterion benchmarks in browser" + "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; + }; + + lint = { + "lint" = cmd "Run Clippy" + "${cargo} clippy"; + + "lint:pedantic" = cmd "Run Clippy pedantically" + "${cargo} clippy -- -W clippy::pedantic"; + + "lint:fix" = cmd "Apply non-pendantic Clippy suggestions" + "${cargo} clippy --fix"; + }; + + watch = { + "watch:build:host" = cmd "Rebuild host target on save" + "${cargo} watch --clear"; + + "watch:build:wasm" = cmd "Rebuild Wasm target on save" + "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; - # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; + "watch:lint" = cmd "Lint on save" + "${cargo} watch --clear --exec clippy"; + "watch:lint:pedantic" = cmd "Pedantic lint on save" + "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; + + "watch:test:host" = cmd "Run all host tests on save" + "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; + + "watch:test:wasm" = cmd "Run all Wasm tests on save" + "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + }; + + test = { + "test:all" = cmd "Run Cargo tests" + "test:host && test:docs && test:wasm"; + + "test:host" = cmd "Run Cargo tests for host target" + "${cargo} test"; + + "test:wasm" = cmd "Run wasm-pack tests on all targets" + "test:wasm:node && test:wasm:chrome"; + + "test:wasm:node" = cmd "Run wasm-pack tests in Node.js" + "${wasm-pack} test --node"; + + "test:wasm:chrome" = cmd "Run wasm-pack tests in headless Chrome" + "${wasm-pack} test --headless --chrome"; + + "test:docs" = cmd "Run Cargo doctests" + "${cargo} test --doc --features=mermaid_docs"; + }; + + docs = { + "docs:build:host" = cmd "Refresh the docs" + "${cargo} doc --features=mermaid_docs"; + + "docs:build:wasm" = cmd "Refresh the docs with the wasm32-unknown-unknown target" + "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; + + "docs:open:host" = cmd "Open refreshed docs" + "${cargo} doc --features=mermaid_docs --open"; + + "docs:open:wasm" = cmd "Open refreshed docs" + "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; + }; + + command_menu = command-utils.commands.${system} + (release // build // bench // lint // watch // test // docs); + + in rec { devShells.default = pkgs.mkShell { name = "ucan"; + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + stdenv = pkgs.clangStdenv; + nativeBuildInputs = with pkgs; [ direnv @@ -113,254 +220,34 @@ self.packages.${system}.irust (pkgs.hiPrio pkgs.rust-bin.nightly.latest.rustfmt) + pre-commit + pkgs.wasm-pack chromedriver protobuf unstable.nodejs_20 unstable.nodePackages.pnpm + + command_menu ] ++ format-pkgs ++ cargo-installs - ++ scripts ++ lib.optionals stdenv.isDarwin darwin-installs; - shellHook = '' - [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg - - # - export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" - - # Setup local Kubo config - if [ ! -e ./.ipfs ]; then - ipfs --repo-dir ./.ipfs --offline init - fi - - unset SOURCE_DATE_EPOCH - - # Run Kubo / IPFS - echo -e "To run Kubo as a local IPFS node, use the following command:" - echo -e "ipfs --repo-dir ./.ipfs --offline daemon" - '' + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' - # See https://github.com/nextest-rs/nextest/issues/267 - export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" - export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; - ''; - - commands = [ - # Release - { - name = "release"; - help = "[DEFAULT] Release (optimized build) for current host target"; - category = "release"; - command = "release:host"; - } - { - name = "release:host"; - help = "Release for current host target"; - category = "release"; - command = "${cargo} build --release"; - } - { - name = "release:wasm:web"; - help = "Release for current host target"; - category = "release"; - command = "${wasm-pack} build --release --target=web"; - } - { - name = "release:wasm:nodejs"; - help = "Release for current host target"; - category = "release"; - command = "${wasm-pack} build --release --target=nodejs"; - } - # Build - { - name = "build"; - help = "[DEFAULT] Build for current host target"; - category = "build"; - command = "build:host"; - } - { - name = "build:host"; - help = "Build for current host target"; - category = "build"; - command = "${cargo} build"; - } - { - name = "build:wasm:web"; - help = "Build for wasm32-unknown-unknown with web bindings"; - category = "build"; - command = "${wasm-pack} build --dev --target=web"; - } - { - name = "build:wasm:nodejs"; - help = "Build for wasm32-unknown-unknown with Node.js bindgings"; - category = "build"; - command = "${wasm-pack} build --dev --target=nodejs"; - } - { - name = "build:node"; - help = "Build JS-wrapped Wasm library"; - category = "build"; - command = "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; - } - { - name = "build:wasi"; - help = "Build for WASI"; - category = "build"; - command = "${cargo} build --target wasm32-wasi"; - } - # Bench - { - name = "bench"; - help = "Run benchmarks, including test utils"; - category = "dev"; - command = "${cargo} bench --features test_utils"; - } - # FIXME align with `bench`? - { - name = "bench:host"; - help = "Run host Criterion benchmarks"; - category = "dev"; - command = "${cargo} criterion"; - } - { - name = "bench:host:open"; - help = "Open host Criterion benchmarks in browser"; - category = "dev"; - command = "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; - } - # Lint - { - name = "lint"; - help = "Run Clippy"; - category = "dev"; - command = "${cargo} clippy"; - } - { - name = "lint:pedantic"; - help = "Run Clippy pedantically"; - category = "dev"; - command = "${cargo} clippy -- -W clippy::pedantic"; - } - { - name = "lint:fix"; - help = "Apply non-pendantic Clippy suggestions"; - category = "dev"; - command = "${cargo} clippy --fix"; - } - # Watch - { - name = "watch:build:host"; - help = "Rebuild host target on save"; - category = "watch"; - command = "${cargo} watch --clear"; - } - { - name = "watch:build:wasm"; - help = "Rebuild host target on save"; - category = "watch"; - command = "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; - } - { - name = "watch:lint"; - help = "Lint on save"; - category = "watch"; - command = "${cargo} watch --clear --exec clippy"; - } - { - name = "watch:lint:pedantic"; - help = "Pedantic lint on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; - } - { - name = "watch:test:host"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; - } - { - name = "watch:test:wasm"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; - } - # Test - { - name = "test:all"; - help = "Run Cargo tests"; - category = "test"; - command = "test:host && test:docs && test:wasm"; - } - { - name = "test:host"; - help = "Run Cargo tests for host target"; - category = "test"; - command = "${cargo} test"; - } - { - name = "test:wasm"; - help = "Run wasm-pack tests on all targets"; - category = "test"; - command = "test:wasm:node && test:wasm:chrome"; - } - { - name = "test:wasm:nodejs"; - help = "Run wasm-pack tests in Node.js"; - category = "test"; - command = "${wasm-pack} test --node"; - } - { - name = "test:wasm:chrome"; - help = "Run wasm-pack tests in headless Chrome"; - category = "test"; - command = "${wasm-pack} test --headless --chrome"; - } - { - name = "test:docs"; - help = "Run Cargo doctests"; - category = "test"; - command = "${cargo} test --doc --features=mermaid_docs"; - } - # Docs - { - name = "docs"; - help = "[DEFAULT]: Open refreshed docs"; - category = "dev"; - command = "docs:open:host"; - } - { - name = "docs:build:host"; - help = "Refresh the docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs"; - } - { - name = "docs:build:wasm"; - help = "Refresh the docs with the wasm32-unknown-unknown target"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; - } - { - name = "docs:open:host"; - help = "Open refreshed docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --open"; - } - { - name = "docs:open:wasm"; - help = "Open refreshed docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; - } - { - name = "docs:wasm:open"; - help = "Open refreshed docs for wasm32-unknown-unknown"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown --open"; - } - ]; + shellHook = '' + [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg + + export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" + unset SOURCE_DATE_EPOCH + '' + + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' + # See https://github.com/nextest-rs/nextest/issues/267 + export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" + export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; + ''; }; + formatter = pkgs.alejandra; + packages.irust = pkgs.rustPlatform.buildRustPackage rec { pname = "irust"; version = "1.71.19"; diff --git a/pre-commit.nix b/pre-commit.nix deleted file mode 100644 index 83e370ba..00000000 --- a/pre-commit.nix +++ /dev/null @@ -1,7 +0,0 @@ -{pkgs, ...}: let - pc = "${pkgs.pre-commit}/bin/pre-commit"; -in { - config.devshell.startup.pre-commit.text = '' - [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) - ''; -} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index dc20cb14..1127820b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -15,6 +15,9 @@ use proptest::prelude::*; #[cfg(feature = "test_utils")] use super::cid; +#[cfg(target_arch = "wasm32")] +use super::cid; + /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. diff --git a/src/ipld/number.rs b/src/ipld/number.rs index c4e7d08d..15e3bd0b 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -60,6 +60,7 @@ impl From for Number { } } +#[cfg(feature = "test_utils")] impl Arbitrary for Number { type Parameters = (); type Strategy = BoxedStrategy; From 454117001607b60c24f2b3d435b5c1d89e6cc721 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 22:35:35 -0800 Subject: [PATCH 178/234] Plugging various FIXMEs --- flake.lock | 6 +- src/ability/arguments.rs | 2 +- src/ability/arguments/named.rs | 17 ++- src/ability/crud/destroy.rs | 15 ++- src/ability/crud/js.rs | 22 +--- src/ability/crud/update.rs | 25 +++- src/ability/dynamic.rs | 2 +- src/ability/parse.rs | 2 - src/ability/wasm/module.rs | 3 +- src/crypto/signature/envelope.rs | 5 - src/crypto/varsig/header/preset.rs | 4 +- src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 5 +- src/did/key.rs | 2 +- src/did/key/traits.rs | 5 +- src/did/key/verifier.rs | 181 ++++++++++++++++++++--------- src/did/preset.rs | 26 ++++- src/ipld.rs | 4 +- src/ipld/newtype.rs | 1 + src/time/timestamp.rs | 2 +- 20 files changed, 209 insertions(+), 122 deletions(-) diff --git a/flake.lock b/flake.lock index ca8b1cb2..a4fa5849 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1709698716, - "narHash": "sha256-l1o+s6MQo5Z/0+0wNmjO5z0qRk5iGJPpFnPM48a5Sfg=", + "lastModified": 1709701816, + "narHash": "sha256-Kwv17invnVzrNrm5fK3Bt6ISJqfXguCx6vc3JDOQtCE=", "owner": "expede", "repo": "nix-command-utils", - "rev": "2cdf5fe375f8206f10dd86d5f79ef1e2fadc5968", + "rev": "12056907b5194b82060fd8bc6ea11c9fdffb5f25", "type": "github" }, "original": { diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 6137a358..d82e6b0b 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -2,7 +2,7 @@ mod named; -pub use named::{Named, NamedError}; +pub use named::*; use crate::{invocation::promise::Resolves, ipld}; use libipld_core::ipld::Ipld; diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index fc844416..16cee2c0 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,6 +1,6 @@ use crate::{ invocation::promise::{Pending, Resolves}, - ipld, + ipld, ability::crud::update::TryFromIpldError, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -211,17 +211,26 @@ impl From> for JsValue { #[cfg(target_arch = "wasm32")] impl TryFrom for Named { - type Error = (); // FIXME + type Error = TryFromJsValueError; fn try_from(js: JsValue) -> Result { match T::try_from(js) { - Err(()) => Err(()), // FIXME surface that we can't parse at all + Err(()) => Err(TryFromJsValueError::NotIpld), Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + Ok(_wrong_ipld) => Err(TryFromJsValueError::NotAMap), } } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum TryFromJsValueError { + #[error("Not a map")] + NotAMap, + + #[error("Not Ipld")] + NotIpld +} + impl From> for Named> { fn from(named: Named) -> Named> { let btree: BTreeMap> = named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 5b5dfbc3..20e43f12 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -74,7 +74,7 @@ impl From for arguments::Named { } impl TryFrom> for Destroy { - type Error = (); // FIXME + type Error = TryFromArgsError; fn try_from(args: arguments::Named) -> Result { let mut path = None; @@ -84,9 +84,11 @@ impl TryFrom> for Destroy { "path" => { if let Ipld::String(s) = ipld { path = Some(PathBuf::from(s)); + } else { + return Err(TryFromArgsError::NotAPathBuf); } } - _ => return Err(()), + s => return Err(TryFromArgsError::InvalidField(s.into())), } } @@ -94,6 +96,15 @@ impl TryFrom> for Destroy { } } +#[derive(Error, Debug, PartialEq, Clone, Serialize, Deserialize)] +pub enum TryFromArgsError { + #[error("Path value is not a PathBuf")] + NotAPathBuf, + + #[error("Invalid map key {0}")] + InvalidField(String) +} + impl promise::Resolvable for Destroy { type Promised = PromisedDestroy; } diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index 77a7879c..57a56bff 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -1,28 +1,8 @@ //! JavaScript bindings for the CRUD abilities. -use super::{read, Any}; +use super::read; use wasm_bindgen::prelude::*; -/// DOCS? -#[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); - -// FIXME macro this away -#[wasm_bindgen] -impl CrudAny { - pub fn into_js(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() - } - - pub fn try_from_js(js: JsValue) -> Result { - ipld::Newtype::try_from_js(js).map(CrudAny) - } - - pub fn to_command(&self) -> String { - self.to_command() - } -} - #[wasm_bindgen] pub struct CrudRead(#[wasm_bindgen(skip)] pub read::Ready); diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dbe4a4c8..00a85b6f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -203,31 +203,46 @@ impl From for Ipld { } impl TryFrom for Update { - type Error = (); // FIXME + type Error = TryFromIpldError; fn try_from(ipld: Ipld) -> Result { if let Ipld::Map(map) = ipld { if map.len() > 2 { - return Err(()); // FIXME + return Err(TryFromIpldError::TooManyKeys); } Ok(Update { path: map .get("path") - .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) + .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(TryFromIpldError::InvalidPath)) .transpose()?, args: map .get("args") - .map(|ipld| ipld.clone().try_into().map_err(|_| ())) + .map(|ipld| arguments::Named::::try_from(ipld.clone()).map_err(|_| TryFromIpldError::InvalidArgs)) .transpose()?, }) } else { - Err(()) // FIXME + Err(TryFromIpldError::NotAMap) } } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum TryFromIpldError { + #[error("Not a map")] + NotAMap, + + #[error("Too many keys")] + TooManyKeys, + + #[error("Invalid path: {0}")] + InvalidPath(ipld::newtype::NotAString), + + #[error("Invalid args: not a map")] + InvalidArgs +} + impl TryFrom for arguments::Named { type Error = FromPromisedUpdateError; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a24bb103..e5cbe65e 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -107,7 +107,7 @@ impl TryFrom for Dynamic { Ok(Dynamic { cmd, - args: arguments::Named(btree), // FIXME kill clone + args: arguments::Named(btree), }) } else { Err(JsValue::NULL) // FIXME diff --git a/src/ability/parse.rs b/src/ability/parse.rs index c5212977..73f9745a 100644 --- a/src/ability/parse.rs +++ b/src/ability/parse.rs @@ -4,8 +4,6 @@ use libipld_core::ipld::Ipld; use std::fmt; use thiserror::Error; -// FIXME definitely needs a better name -// pub trait ParseAbility: TryFrom> { pub trait ParseAbility: Sized { type ArgsErr: fmt::Debug; diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index 18ed34db..c17726d7 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -8,13 +8,12 @@ use serde::{Deserialize, Serialize}; /// Ways to represent a Wasm module in a `wasm/run` payload. #[derive(Debug, Clone, PartialEq)] pub enum Module { - // FIXME serialize both as URLs /// The raw bytes of the Wasm module /// /// Encodes as a `data:` URL Inline(Vec), - /// A link to the Wasm module + /// A [`Cid`] link to the Wasm module Remote(Link>), } diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 69e69e9f..b0f3aa51 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -63,7 +63,6 @@ impl< } } - // FIXME extract into trait? pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where Ipld: Encode + From, @@ -83,10 +82,6 @@ impl< /// # Errors /// /// * [`SignError`] - the payload can't be encoded or the signature fails. - /// - /// # Example - /// - /// FIXME pub fn try_sign( signer: &DID::Signer, varsig_header: V, diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index 4548682b..e658a285 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -9,8 +9,8 @@ pub enum Preset { Es512(es512::Es512Header), Rs256(rs256::Rs256Header), Rs512(rs512::Rs512Header), - // FIXME BLS? - // FIXME Es384 + // FIXME BLS? needs varsig specs + // FIXME Es384 needs varsig specs } impl From for Vec { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 34a4c5c5..c13cc828 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -114,7 +114,7 @@ impl, Enc: Codec + TryFrom + &self, aud: &DID, subject: &Option, - policy: Vec, + policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { match self diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 364c0093..1969e01f 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -13,9 +13,6 @@ pub trait Store, Enc: Codec + TryFrom + In fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; - // FIXME add a variant that calculated the CID from the capsulre header? - // FIXME that means changing the name to insert_by_cid or similar - // FIXME rename put fn insert( &mut self, cid: Cid, @@ -23,7 +20,7 @@ pub trait Store, Enc: Codec + TryFrom + In ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation - // sore invocation + // store invocation // just... move to invocation fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; diff --git a/src/did/key.rs b/src/did/key.rs index 068c3fc3..e2859621 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -6,4 +6,4 @@ mod verifier; pub mod traits; pub use signature::Signature; -pub use verifier::Verifier; +pub use verifier::*; diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs index 59c3f304..7a1eb9f1 100644 --- a/src/did/key/traits.rs +++ b/src/did/key/traits.rs @@ -1,3 +1,5 @@ +/// A trait aligning signatures with keys. + use crate::crypto::{bls12381, es512, rs256, rs512}; use ::p521 as ext_p521; use ed25519_dalek; @@ -6,9 +8,8 @@ use p256; use p384; // FIXME -// also: e.g. HSM +// also: e.g. HSM? -// FIXME when name conflict gone pub trait DidKey: signature::Verifier { const BASE58_PREFIX: &'static str; diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index d645c559..93951256 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -2,6 +2,10 @@ use super::Signature; use enum_as_inner::EnumAsInner; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; use std::{fmt::Display, str::FromStr}; +use serde::{Serialize, Deserialize}; +use thiserror::Error; +use blst::BLST_ERROR; +use signature as sig; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -145,7 +149,7 @@ impl Display for Verifier { rsa2048_key .0 .to_pkcs1_der() - .expect("RSA key to encode") // FIXME? + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail .as_bytes() ) .into_string() @@ -158,7 +162,7 @@ impl Display for Verifier { rsa4096_key .0 .to_pkcs1_der() - .expect("RSA key to encode") // FIXME? + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail .as_bytes() ) .into_string() @@ -178,82 +182,143 @@ impl Display for Verifier { } impl FromStr for Verifier { - type Err = String; // FIXME + type Err = FromStrError; - // FIXME needs tests fn from_str(s: &str) -> Result { if s.len() < 32 { // Smallest key size - return Err("invalid did:key".to_string()); + return Err(FromStrError::TooShort); } - if let ("did:key:z", more) = s.split_at(9) { - let bytes = more.as_bytes(); - match bytes.split_at(2) { - ([0xed, _], _) => { - let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) - .map_err(|e| e.to_string())?; + match s.split_at(9) { + ("did:key:z", more) => { + let bytes = more.as_bytes(); + match bytes.split_at(2) { + ([0xed, _], _) => { + let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + .map_err(FromStrError::CannotParseEdDsa)?; - return Ok(Verifier::EdDsa(vk)); - } - ([0xe7, _], _) => { - let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::Es256k(vk)); - } - ([0x12, 0x00], key_bytes) => { - let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::P256(vk)); - } - ([0x12, 0x01], key_bytes) => { - let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::P384(vk)); - } - ([0x12, 0x05], key_bytes) => match key_bytes.len() { - 2048 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) - .map_err(|e| e.to_string())?; + return Ok(Verifier::EdDsa(vk)); + } + ([0xe7, _], _) => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + .map_err(FromStrError::CannotParseEs256k)?; - return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + return Ok(Verifier::Es256k(vk)); } - 4096 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) - .map_err(|e| e.to_string())?; + ([0x12, 0x00], key_bytes) => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP256)?; - return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + return Ok(Verifier::P256(vk)); } - _ => return Err("invalid did:key".to_string()), - }, - ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { - 48 => { - let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) - .map_err(|_| "Failed BLS MinPk deserialization")?; - - return Ok(Verifier::BlsMinPk(pk)); + ([0x12, 0x01], key_bytes) => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP384)?; + + return Ok(Verifier::P384(vk)); } - 96 => { - let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) - .map_err(|_| "Failed BLS MinSig deserialization")?; + ([0x12, 0x02], key_bytes) => { + let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP521)?; - return Ok(Verifier::BlsMinSig(pk)); + return Ok(Verifier::P521(es512::VerifyingKey(vk))); + } + ([0x12, 0x05], key_bytes) => match key_bytes.len() { + 2048 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(FromStrError::CannotParseRs256)?; + + return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + } + 4096 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(FromStrError::CannotParseRs512)?; + + return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + } + word => return Err(FromStrError::NotADidKey(word)), + }, + ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 48 => { + let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + .map_err(FromStrError::CannotParseBlsMinPk)?; + + return Ok(Verifier::BlsMinPk(pk)); + } + 96 => { + let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + .map_err(FromStrError::CannotParseBlsMinSig)?; + + return Ok(Verifier::BlsMinSig(pk)); + } + word => return Err(FromStrError::UnexpectedPrefix([word].into())), + }, + (word, _) => { + return Err(FromStrError::UnexpectedPrefix(word.iter().map(|u| u.clone().into()).collect())); } - _ => return Err("invalid did:key".to_string()), - }, - _ => { - return Err("invalid did:key".to_string()); } + }, + + (s, _) => { + return Err(FromStrError::UnexpectedPrefix(s.to_string().chars().map(|u| u as usize).collect())); } - } else { - return Err("invalid did:key".to_string()); } } } +#[derive(Debug, Error)] +pub enum FromStrError { + #[error("not a did:key prefix: {0}")] + NotADidKey(usize), + + #[error("unexpected prefix: {0:?}")] + UnexpectedPrefix(Vec), + + #[error("key too short")] + TooShort, + + #[error("cannot parse EdDSA key: {0}")] + CannotParseEdDsa(sig::Error), + + #[error("cannot parse ES256K key: {0}")] + CannotParseEs256k(sig::Error), + + #[error("cannot parse P-256 key: {0}")] + CannotParseP256(sig::Error), + + #[error("cannot parse P-384 key: {0}")] + CannotParseP384(sig::Error), + + #[error("cannot parse P-521 key: {0}")] + CannotParseP521(sig::Error), + + #[error("cannot parse RS256 key: {0}")] + CannotParseRs256(rsa::pkcs1::Error), + + #[error("cannot parse RS512 key: {0}")] + CannotParseRs512(rsa::pkcs1::Error), + + #[error("cannot parse BLS min pk key: {0:?}")] + CannotParseBlsMinPk(BLST_ERROR), + + #[error("cannot parse BLS min sig key: {0:?}")] + CannotParseBlsMinSig(BLST_ERROR), +} + +impl Serialize for Verifier { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Verifier { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Verifier::from_str(&s).map_err(serde::de::Error::custom) + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Verifier { type Parameters = (); diff --git a/src/did/preset.rs b/src/did/preset.rs index 0808afd8..371210c2 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -1,16 +1,18 @@ use super::key; use enum_as_inner::EnumAsInner; +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, str::FromStr}; /// The set of [`Did`] types that ship with this library ("presets"). -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Verifier { /// `did:key` DIDs. Key(key::Verifier), - // Dns(did_url::DID), + // + // FIXME Dns(did_url::DID), } -// FIXME serialize with did:key etc - impl signature::Verifier for Verifier { fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { match self { @@ -18,3 +20,19 @@ impl signature::Verifier for Verifier { } } } + +impl Display for Verifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Verifier::Key(verifier) => verifier.fmt(f), + } + } +} + +impl FromStr for Verifier { + type Err = key::FromStrError; + + fn from_str(s: &str) -> Result { + key::Verifier::from_str(s).map(Verifier::Key) + } +} diff --git a/src/ipld.rs b/src/ipld.rs index 59116dac..61b2e2bb 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,15 +6,13 @@ //! //! [`Ipld`]: libipld_core::ipld::Ipld -// mod enriched; mod collection; -mod newtype; mod number; mod promised; pub mod cid; +pub mod newtype; -// pub use enriched::Enriched; pub use collection::Collection; pub use newtype::Newtype; pub use number::Number; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 1127820b..43a1f480 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -2,6 +2,7 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use thiserror::Error; +use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index d8659c29..a43eb858 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -39,7 +39,7 @@ impl Timestamp { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + if time.duration_since(UNIX_EPOCH).map_err(|_| OutOfRangeError{ tried: time })?.as_secs() > 0x1FFFFFFFFFFFFF { Err(OutOfRangeError { tried: time }) } else { Ok(Timestamp { time }) From abce1e3345fdfd43cdf45cc96f3213ea43de16da Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 12:10:34 -0800 Subject: [PATCH 179/234] Save before fixing promises --- src/ability/crud/create.rs | 8 +--- src/ability/pipe.rs | 4 +- src/delegation.rs | 79 +++++++++++++++------------------- src/did/key.rs | 2 + src/did/key/signature.rs | 1 - src/did/key/signer.rs | 86 ++++++++++++++++++++++++++++++++++++++ src/did/key/verifier.rs | 15 +++++++ src/did/preset.rs | 37 ++++++++++++++++ src/did/traits.rs | 1 - src/invocation.rs | 67 ++++++++++++----------------- src/invocation/promise.rs | 7 ++-- 11 files changed, 206 insertions(+), 101 deletions(-) create mode 100644 src/did/key/signer.rs diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index bd5b159f..2fa7f800 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -219,13 +219,7 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = create.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); + named.insert("path".to_string(), path.display().to_string().into()); } if let Some(args) = create.args { diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 55a80555..a41d9c01 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -7,7 +7,7 @@ pub struct Pipe, Enc: Codec + TryFrom + In } pub enum Cap, Enc: Codec + TryFrom + Into> { - Chain(delegation::Chain), + Proof(delegation::Proof), Literal(Ipld), } @@ -17,6 +17,6 @@ pub struct PromisedPipe, Enc: Codec + TryFrom, Enc: Codec + TryFrom + Into> { - Chain(delegation::Chain), + Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/delegation.rs b/src/delegation.rs index d67e7f28..992341c6 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -41,28 +41,35 @@ use web_time::SystemTime; /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. #[derive(Clone, Debug, PartialEq)] -pub struct Delegation, Enc: Codec + TryFrom + Into>( - pub signature::Envelope, DID, V, Enc>, -); +pub struct Delegation< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(signature::Envelope, DID, V, C>); +// FIXME rename proofs? #[derive(Clone, Debug, PartialEq)] -pub struct Chain, Enc: Codec + TryFrom + Into>( - Vec>, -); - -impl, Enc: Codec + TryFrom + Into> Capsule - for Chain +pub struct Proof< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(Vec>); + +impl, C: Codec + TryFrom + Into> Capsule + for Proof { - const TAG: &'static str = "ucan/chain"; + const TAG: &'static str = "ucan/prf"; } -/// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = - Delegation; +impl, C: Codec + Into + TryFrom> Delegation { + pub fn new( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Delegation { + Delegation(signature::Envelope::new(varsig_header, signature, payload)) + } -impl, Enc: Codec + Into + TryFrom> - Delegation -{ /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.0.payload.issuer @@ -117,7 +124,7 @@ impl, Enc: Codec + Into + TryFrom> pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode, { self.0.varsig_encode(w) } @@ -126,14 +133,14 @@ impl, Enc: Codec + Into + TryFrom> &self.0.signature } - pub fn codec(&self) -> &Enc { + pub fn codec(&self) -> &C { self.varsig_header().codec() } pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, - Ipld: Encode, + signature::Envelope, DID, V, C>: Clone + Encode, + Ipld: Encode, { self.0.cid() } @@ -141,7 +148,7 @@ impl, Enc: Codec + Into + TryFrom> pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, - Ipld: Encode, + Ipld: Encode, { self.0.validate_signature() } @@ -152,35 +159,15 @@ impl, Enc: Codec + Into + TryFrom> payload: Payload, ) -> Result where - Ipld: Encode, + Ipld: Encode, Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl, Enc: Codec + TryFrom + Into> TryFrom - for Delegation -where - Payload: TryFrom, -{ - type Error = , DID, V, Enc> as TryFrom>::Error; - - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Delegation) - } -} - -impl, Enc: Codec + TryFrom + Into> - From> for Ipld -{ - fn from(delegation: Delegation) -> Self { - delegation.0.into() - } -} - -impl, Enc: Codec + TryFrom + Into> Serialize - for Delegation +impl, C: Codec + TryFrom + Into> Serialize + for Delegation { fn serialize(&self, serializer: S) -> Result where @@ -190,8 +177,8 @@ impl, Enc: Codec + TryFrom + Into> Se } } -impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> - for Delegation +impl<'de, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> + for Delegation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, diff --git a/src/did/key.rs b/src/did/key.rs index e2859621..6be3a7bb 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -2,8 +2,10 @@ mod signature; mod verifier; +mod signer; pub mod traits; pub use signature::Signature; pub use verifier::*; +pub use signer::*; diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs index 7cbc715e..1392eec6 100644 --- a/src/did/key/signature.rs +++ b/src/did/key/signature.rs @@ -1,5 +1,4 @@ use enum_as_inner::EnumAsInner; -// FIXME use serde::{Deserialize, Serialize}; #[cfg(feature = "eddsa")] use ed25519_dalek; diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs new file mode 100644 index 00000000..6bc8584e --- /dev/null +++ b/src/did/key/signer.rs @@ -0,0 +1,86 @@ +use enum_as_inner::EnumAsInner; +use super::Signature; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + + + +/// Signature types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Signer { + /// `EdDSA` signature. + #[cfg(feature = "eddsa")] + EdDsa(ed25519_dalek::SigningKey), + + // /// `ES256K` (`secp256k1`) signature. + // #[cfg(feature = "es256k")] + // Es256k(k256::ecdsa::Signer), + + // /// `P-256` signature. + // #[cfg(feature = "es256")] + // P256(p256::ecdsa::Signer), + + // /// `P-384` signature. + // #[cfg(feature = "es384")] + // P384(p384::ecdsa::Signer), + + // /// `P-521` signature. + // #[cfg(feature = "es512")] + // P521(ext_p521::ecdsa::Signer), + + // /// `RS256` signature. + // #[cfg(feature = "rs256")] + // Rs256(rs256::Signer), + + // /// `RS512` signature. + // #[cfg(feature = "rs512")] + // Rs512(rs512::Signer), + + // /// `BLS 12-381` signature for the "min pub key" variant. + // #[cfg(feature = "bls")] + // BlsMinPk(bls12381::min_pk::Signer), + + // /// `BLS 12-381` signature for the "min sig" variant. + // #[cfg(feature = "bls")] + // BlsMinSig(bls12381::min_sig::Signer), + + // /// An unknown signature type. + // /// + // /// This is primarily for parsing, where reification is delayed + // /// until the DID method is known. + // Unknown(Vec), +} + +impl signature::Signer for Signer { + fn try_sign(&self, msg: &[u8]) -> Result { + match self { + #[cfg(feature = "eddsa")] + Signer::EdDsa(signer) => { + let sig = signer.sign(msg); + Ok(Signature::EdDsa(sig)) + } + } + } +} diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 93951256..20b021eb 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -6,6 +6,7 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; use blst::BLST_ERROR; use signature as sig; +use did_url::DID; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -319,6 +320,20 @@ impl<'de> Deserialize<'de> for Verifier { } } +impl From for DID { + fn from(v: Verifier) -> Self { + DID::parse(&v.to_string()).expect("verifier to be a valid DID") + } +} + +impl TryFrom for Verifier { + type Error = FromStrError; + + fn try_from(did: DID) -> Result { + Verifier::from_str(&did.to_string()) + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Verifier { type Parameters = (); diff --git a/src/did/preset.rs b/src/did/preset.rs index 371210c2..aff0d261 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -1,4 +1,6 @@ use super::key; +use super::Did; +use did_url::DID; use enum_as_inner::EnumAsInner; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; @@ -13,6 +15,41 @@ pub enum Verifier { // FIXME Dns(did_url::DID), } +impl From for DID { + fn from(verifier: Verifier) -> Self { + match verifier { + Verifier::Key(verifier) => verifier.into(), + } + } +} + +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +pub enum Signer { + Key(key::Signer), + // FIXME Dns(did_url::DID), +} + +impl Did for Verifier { + type Signature = key::Signature; + type Signer = Signer; +} + +impl TryFrom for Verifier { + type Error = key::FromStrError; + + fn try_from(did: DID) -> Result { + key::Verifier::try_from(did).map(Verifier::Key) + } +} + +impl signature::Signer for Signer { + fn try_sign(&self, message: &[u8]) -> Result { + match self { + Signer::Key(signer) => signer.try_sign(message), + } + } +} + impl signature::Verifier for Verifier { fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { match self { diff --git a/src/did/traits.rs b/src/did/traits.rs index bd903ad4..448186be 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,4 +1,3 @@ -// use super::Newtype; use did_url::DID; use std::fmt; diff --git a/src/invocation.rs b/src/invocation.rs index aeba3097..4c9e9f8a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -50,30 +50,15 @@ use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] pub struct Invocation< A, - DID: did::Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); - -/// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Invocation< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -pub type PresetPromised = Invocation< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -impl, Enc: Codec + TryFrom + Into> - Invocation + DID: did::Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(pub signature::Envelope, DID, V, C>); + +impl, C: Codec + TryFrom + Into> + Invocation where - Ipld: Encode, + Ipld: Encode, { pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { Invocation(signature::Envelope::new(varsig_header, signature, payload)) @@ -81,7 +66,7 @@ where pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode, { self.0.varsig_encode(w) } @@ -114,7 +99,7 @@ where &self.0.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { @@ -141,14 +126,14 @@ where self.payload().check_time(now) } - pub fn codec(&self) -> &Enc { + pub fn codec(&self) -> &C { self.varsig_header().codec() } pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone, - Ipld: Encode, + signature::Envelope, DID, V, C>: Clone, + Ipld: Encode, { self.0.cid() } @@ -157,7 +142,7 @@ where signer: &DID::Signer, varsig_header: V, payload: Payload, - ) -> Result, signature::SignError> + ) -> Result, signature::SignError> where Payload: Clone, { @@ -173,36 +158,36 @@ where } } -impl, Enc: Codec + TryFrom + Into> - did::Verifiable for Invocation +impl, C: Codec + TryFrom + Into> did::Verifiable + for Invocation { fn verifier(&self) -> &DID { &self.0.verifier() } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl, C: Codec + TryFrom + Into> + From> for Ipld { - fn from(invocation: Invocation) -> Self { + fn from(invocation: Invocation) -> Self { invocation.0.into() } } -impl, Enc: Codec + TryFrom + Into> TryFrom - for Invocation +impl, C: Codec + TryFrom + Into> TryFrom + for Invocation where Payload: TryFrom, { - type Error = , DID, V, Enc> as TryFrom>::Error; + type Error = , DID, V, C> as TryFrom>::Error; fn try_from(ipld: Ipld) -> Result { signature::Envelope::try_from(ipld).map(Invocation) } } -impl, Enc: Codec + TryFrom + Into> Serialize - for Invocation +impl, C: Codec + TryFrom + Into> Serialize + for Invocation { fn serialize(&self, serializer: S) -> Result where @@ -212,8 +197,8 @@ impl, Enc: Codec + TryFrom + Into> } } -impl<'de, A, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> - Deserialize<'de> for Invocation +impl<'de, A, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> + for Invocation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 4bc943a2..73907a97 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -25,14 +25,15 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] #[serde(untagged)] pub enum Promise { - /// The `await/ok` promise + /// The `ucan/await/ok` promise Ok(PromiseOk), - /// The `await/err` promise + /// The `ucan/await/err` promise Err(PromiseErr), - /// The `await/*` promise + /// The `ucan/await/*` promise Any(PromiseAny), + // Tagged `ucan/await` } impl From> for Promise { From 4db9cf593c4ec2c99cb9c4552bfb83cdf7201a67 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 15:06:43 -0800 Subject: [PATCH 180/234] Hugely fixup the promise hierarchy --- src/ability/arguments.rs | 3 +- src/ability/arguments/named.rs | 26 +-- src/ability/crud/create.rs | 46 ++-- src/ability/crud/destroy.rs | 46 ++-- src/ability/crud/read.rs | 32 +-- src/ability/crud/update.rs | 85 +++---- src/ability/msg.rs | 16 +- src/ability/msg/receive.rs | 29 +-- src/ability/msg/send.rs | 42 ++-- src/ability/ucan/revoke.rs | 9 +- src/ability/wasm/run.rs | 30 ++- src/invocation/promise.rs | 36 +-- src/invocation/promise/any.rs | 286 +++++++---------------- src/invocation/promise/resolves.rs | 352 ----------------------------- src/ipld/newtype.rs | 2 +- src/ipld/promised.rs | 134 ++++------- 16 files changed, 280 insertions(+), 894 deletions(-) delete mode 100644 src/invocation/promise/resolves.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index d82e6b0b..7d504812 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -4,10 +4,11 @@ mod named; pub use named::*; -use crate::{invocation::promise::Resolves, ipld}; +use crate::ipld; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +// FIXME just remove? // FIXME move under invoc::promise? // pub type Promised = Resolves>; // diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 16cee2c0..f8a3500f 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,6 +1,7 @@ use crate::{ - invocation::promise::{Pending, Resolves}, - ipld, ability::crud::update::TryFromIpldError, + // ability::crud::update::TryFromIpldError, + invocation::promise::{self, Pending}, + ipld, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -228,17 +229,14 @@ pub enum TryFromJsValueError { NotAMap, #[error("Not Ipld")] - NotIpld + NotIpld, } -impl From> for Named> { - fn from(named: Named) -> Named> { - let btree: BTreeMap> = named +impl From> for Named> { + fn from(named: Named) -> Named> { + let btree: BTreeMap> = named .into_iter() - .map(|(k, v)| { - let promised: ipld::Promised = v.into(); - (k, Resolves::new(promised)) - }) + .map(|(k, v)| (k, promise::Any::from_ipld(v))) .collect(); Named(btree) @@ -266,13 +264,13 @@ impl TryFrom> for Named { } } -impl TryFrom>> for Named +impl TryFrom>> for Named where Ipld: TryFrom, { - type Error = Resolves>; + type Error = promise::Any>; - fn try_from(resolves: Resolves>) -> Result { + fn try_from(resolves: promise::Any>) -> Result { resolves .clone() .try_resolve()? @@ -282,7 +280,7 @@ where btree.insert(k, ipld); Ok(btree) }) - .map_err(|_: ()| resolves) + .map_err(|_: ()| resolves) // FIXME } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 2fa7f800..86df7070 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, }; use libipld_core::ipld::Ipld; @@ -90,11 +90,11 @@ pub struct Create { pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, /// Optional arguments for creation. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>>, + pub args: Option>>, } const COMMAND: &str = "/crud/create"; @@ -118,18 +118,16 @@ impl TryFrom> for PromisedCreate { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -137,17 +135,11 @@ impl TryFrom> for PromisedCreate { "args" => { args = match prom { ipld::Promised::Map(map) => { - Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) - } - ipld::Promised::WaitOk(cid) => { - Some(promise::PromiseOk::Pending(cid).into()) - } - ipld::Promised::WaitErr(cid) => { - Some(promise::PromiseErr::Pending(cid).into()) - } - ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + Some(promise::Any::Resolved(arguments::Named(map)).into()) } + ipld::Promised::WaitOk(cid) => Some(promise::Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => Some(promise::Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => Some(promise::Any::PendingAny(cid)), _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } @@ -201,11 +193,11 @@ impl TryFrom> for Create { impl From for PromisedCreate { fn from(r: Create) -> PromisedCreate { PromisedCreate { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + path: r.path.map(|inner_path| promise::Any::Resolved(inner_path)), - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + args: r + .args + .map(|inner_args| promise::Any::Resolved(inner_args.into())), } } } @@ -234,12 +226,12 @@ impl From for arguments::Named { fn from(promised: PromisedCreate) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + if let Some(path_prom) = promised.path { + named.insert("path".to_string(), path_prom.to_promised_ipld()); } - if let Some(args) = promised.args { - named.insert("args".to_string(), args.into()); + if let Some(args_prom) = promised.args { + named.insert("args".to_string(), args_prom.to_promised_ipld()); } named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 20e43f12..1f2516f5 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -102,7 +102,7 @@ pub enum TryFromArgsError { NotAPathBuf, #[error("Invalid map key {0}")] - InvalidField(String) + InvalidField(String), } impl promise::Resolvable for Destroy { @@ -146,7 +146,7 @@ impl promise::Resolvable for Destroy { pub struct PromisedDestroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, } impl TryFrom> for PromisedDestroy { @@ -159,18 +159,16 @@ impl TryFrom> for PromisedDestroy { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -195,27 +193,27 @@ impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } -impl From for arguments::Named { - fn from(promised: PromisedDestroy) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: PromisedDestroy) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path_res) = promised.path { +// named.insert( +// "path".to_string(), +// path_res.map(|p| ipld::Newtype::from(p).0).into(), +// ); +// } +// +// named +// } +// } impl From for PromisedDestroy { fn from(r: Destroy) -> PromisedDestroy { PromisedDestroy { path: r .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + .map(|inner_path| promise::Any::Resolved(inner_path).into()), } } } @@ -225,7 +223,7 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + named.insert("path".to_string(), path.to_promised_ipld()); } named diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 35c925fd..1289a45a 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -84,11 +84,11 @@ pub struct Read { pub struct PromisedRead { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, /// Optional arguments to modify the read request. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>>, + pub args: Option>>, } impl TryFrom> for PromisedRead { @@ -102,18 +102,16 @@ impl TryFrom> for PromisedRead { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -121,17 +119,11 @@ impl TryFrom> for PromisedRead { "args" => { args = match prom { ipld::Promised::Map(map) => { - Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) - } - ipld::Promised::WaitOk(cid) => { - Some(promise::PromiseOk::Pending(cid).into()) - } - ipld::Promised::WaitErr(cid) => { - Some(promise::PromiseErr::Pending(cid).into()) - } - ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + Some(promise::Any::Resolved(arguments::Named(map)).into()) } + ipld::Promised::WaitOk(cid) => Some(promise::Any::PendingOk(cid).into()), + ipld::Promised::WaitErr(cid) => Some(promise::Any::PendingErr(cid).into()), + ipld::Promised::WaitAny(cid) => Some(promise::Any::PendingAny(cid).into()), _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } @@ -231,11 +223,11 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { - named.insert("path".to_string(), path_res.into()); + named.insert("path".to_string(), path_res.to_promised_ipld()); } if let Some(args_res) = promised.args { - named.insert("args".to_string(), args_res.into()); + named.insert("args".to_string(), args_res.to_promised_ipld()); } named diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 00a85b6f..ebf0c281 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, }; use libipld_core::ipld::Ipld; @@ -90,11 +90,11 @@ pub struct Update { pub struct PromisedUpdate { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] - path: Option>, + path: Option>, /// Optional arguments to be passed in the update. #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option>>, + args: Option>>, } const COMMAND: &'static str = "/crud/update"; @@ -121,26 +121,19 @@ impl TryFrom> for PromisedUpdate { path = Some(pending.into()); } Ok(ipld) => match ipld { - Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), + Ipld::String(s) => path = Some(promise::Any::Resolved(PathBuf::from(s))), other => return Err(FromPromisedArgsError::PathBodyNotAString(other)), }, }, "args" => match prom { ipld::Promised::Map(map) => { - args = Some(promise::Resolves::new(arguments::Named(map))) + args = Some(promise::Any::Resolved(arguments::Named(map))) } - ipld::Promised::WaitOk(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); - } - ipld::Promised::WaitErr(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); - } - ipld::Promised::WaitAny(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); + ipld::Promised::WaitOk(cid) => args = Some(promise::Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => args = Some(promise::Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => { + args = Some(promise::Any::PendingAny(cid)); } _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), }, @@ -214,12 +207,19 @@ impl TryFrom for Update { Ok(Update { path: map .get("path") - .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(TryFromIpldError::InvalidPath)) + .map(|ipld| { + (ipld::Newtype(ipld.clone())) + .try_into() + .map_err(TryFromIpldError::InvalidPath) + }) .transpose()?, args: map .get("args") - .map(|ipld| arguments::Named::::try_from(ipld.clone()).map_err(|_| TryFromIpldError::InvalidArgs)) + .map(|ipld| { + arguments::Named::::try_from(ipld.clone()) + .map_err(|_| TryFromIpldError::InvalidArgs) + }) .transpose()?, }) } else { @@ -240,46 +240,13 @@ pub enum TryFromIpldError { InvalidPath(ipld::newtype::NotAString), #[error("Invalid args: not a map")] - InvalidArgs -} - -impl TryFrom for arguments::Named { - type Error = FromPromisedUpdateError; - - fn try_from(promised: PromisedUpdate) -> Result { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .try_resolve() - .map_err(FromPromisedUpdateError::UnresolvedArgs)? - .iter() - .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone())?); // FIXME double check - Ok(map) - }) - .map_err(FromPromisedUpdateError::ArgsPending)? - .into(), - ); - } - - Ok(named) - } + InvalidArgs, } #[derive(Error, Debug, PartialEq, Clone)] pub enum FromPromisedUpdateError { #[error("Unresolved args")] - UnresolvedArgs(Resolves>), + UnresolvedArgs(promise::Any>), #[error("Args pending")] ArgsPending(>::Error), @@ -291,11 +258,11 @@ pub enum FromPromisedUpdateError { impl From for PromisedUpdate { fn from(r: Update) -> PromisedUpdate { PromisedUpdate { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + path: r.path.map(|inner_path| promise::Any::Resolved(inner_path)), - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + args: r + .args + .map(|inner_args| promise::Any::Resolved(inner_args.into())), } } } @@ -309,11 +276,11 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + named.insert("path".to_string(), path.to_promised_ipld()); } if let Some(args) = promised.args { - named.insert("args".to_string(), args.into()); + named.insert("args".to_string(), args.to_promised_ipld()); } named diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 59ee1fed..2ad81030 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -74,14 +74,14 @@ impl ParsePromised for PromisedMsg { } } -impl From for arguments::Named { - fn from(promised: PromisedMsg) -> Self { - match promised { - PromisedMsg::Send(send) => send.into(), - PromisedMsg::Receive(receive) => receive.into(), - } - } -} +// impl From for arguments::Named { +// fn from(promised: PromisedMsg) -> Self { +// match promised { +// PromisedMsg::Send(send) => send.into(), +// PromisedMsg::Receive(receive) => receive.into(), +// } +// } +// } impl Resolvable for Msg { type Promised = PromisedMsg; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index a58bbed2..2d59ea0b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -101,26 +101,7 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedReceive { - pub from: Option>, -} - -impl From for arguments::Named { - fn from(promised: PromisedReceive) -> Self { - let mut args = arguments::Named::new(); - - if let Some(from) = promised.from { - match from { - promise::Resolves::Ok(from) => { - args.insert("from".into(), from.into()); - } - promise::Resolves::Err(from) => { - args.insert("from".into(), from.into()); - } - } - } - - args - } + pub from: Option>, } impl promise::Resolvable for Receive { @@ -137,9 +118,9 @@ impl TryFrom> for PromisedReceive { match key.as_str() { "from" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - from = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + from = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => from = Some(pending.into()), _ => return Err(()), @@ -157,7 +138,7 @@ impl From for arguments::Named { let mut args = arguments::Named::new(); if let Some(from) = promised.from { - let _ = ipld::Promised::from(from).with_resolved(|ipld| { + let _ = from.to_promised_ipld().with_resolved(|ipld| { args.insert("from".into(), ipld.into()); }); } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 3329cae5..b37fe78c 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -84,16 +84,16 @@ pub struct Send { #[serde(deny_unknown_fields)] pub struct PromisedSend { /// The recipient of the message - pub to: promise::Resolves, + pub to: promise::Any, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: promise::Resolves, + pub from: promise::Any, /// The main body of the message - pub message: promise::Resolves, + pub message: promise::Any, } impl promise::Resolvable for Send { @@ -150,24 +150,24 @@ impl TryFrom> for PromisedSend { match key.as_str() { "to" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - to = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + to = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => to = Some(pending.into()), _ => return Err(()), }, "from" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - from = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + from = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => from = Some(pending.into()), _ => return Err(()), }, "message" => match Ipld::try_from(prom) { - Ok(Ipld::String(s)) => message = Some(promise::Resolves::from(Ok(s))), + Ok(Ipld::String(s)) => message = Some(promise::Any::Resolved(s)), Err(pending) => to = Some(pending.into()), _ => return Err(()), }, @@ -206,9 +206,9 @@ impl Command for PromisedSend { impl From for PromisedSend { fn from(r: Send) -> Self { PromisedSend { - to: promise::Resolves::from(Ok(r.to)), - from: promise::Resolves::from(Ok(r.from)), - message: promise::Resolves::from(Ok(r.message)), + to: promise::Any::Resolved(r.to), + from: promise::Any::Resolved(r.from), + message: promise::Any::Resolved(r.message), } } } @@ -217,9 +217,13 @@ impl TryFrom for Send { type Error = PromisedSend; fn try_from(p: PromisedSend) -> Result { - match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Send { to, from, message }), - Err((to, from, message)) => Err(PromisedSend { to, from, message }), + match p { + PromisedSend { + to: promise::Any::Resolved(to), + from: promise::Any::Resolved(from), + message: promise::Any::Resolved(message), + } => Ok(Send { to, from, message }), + _ => Err(p), } } } @@ -227,9 +231,9 @@ impl TryFrom for Send { impl From for arguments::Named { fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ - ("to".into(), p.to.into()), - ("from".into(), p.from.into()), - ("message".into(), p.message.into()), + ("to".into(), p.to.to_promised_ipld()), + ("from".into(), p.from.to_promised_ipld()), + ("message".into(), p.message.to_promised_ipld()), ]) } } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 4c3cd4a2..4505fc77 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -16,6 +16,7 @@ use std::fmt::Debug; pub struct Revoke { /// The UCAN to revoke pub ucan: Cid, + // FIXME pub witness } const COMMAND: &'static str = "/ucan/revoke"; @@ -44,14 +45,14 @@ impl promise::Resolvable for Revoke { impl From for arguments::Named { fn from(promised: PromisedRevoke) -> Self { - arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) + arguments::Named::from_iter([("ucan".into(), Ipld::from(promised.ucan).into())]) } } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedRevoke { - pub ucan: promise::Resolves, + pub ucan: promise::Any, } impl TryFrom> for PromisedRevoke { @@ -64,7 +65,7 @@ impl TryFrom> for PromisedRevoke { match k.as_str() { "ucan" => match Ipld::try_from(prom) { Ok(Ipld::Link(cid)) => { - ucan = Some(promise::Resolves::new(cid)); + ucan = Some(promise::Any::Resolved(cid)); } Err(pending) => ucan = Some(pending.into()), _ => return Err(()), @@ -82,7 +83,7 @@ impl TryFrom> for PromisedRevoke { impl From for PromisedRevoke { fn from(r: Revoke) -> PromisedRevoke { PromisedRevoke { - ucan: Ok(r.ucan).into(), + ucan: promise::Any::Resolved(r.ucan), } } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 2aad7530..dcbe97de 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -79,9 +79,9 @@ impl promise::Resolvable for Run { /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedRun { - pub module: promise::Resolves, - pub function: promise::Resolves, - pub args: promise::Resolves>, + pub module: promise::Any, + pub function: promise::Any, + pub args: promise::Any>, } impl TryFrom> for PromisedRun { @@ -94,11 +94,11 @@ impl TryFrom> for PromisedRun { for (key, prom) in named { match key.as_str() { - "module" => module = Some(prom.try_into().map_err(|_| ())?), - "function" => function = Some(prom.try_into().map_err(|_| ())?), + "module" => module = Some(prom.to_promise_any().map_err(|_| ())?), + "function" => function = Some(prom.to_promise_any_string()?), "args" => { if let ipld::Promised::List(list) = prom.into() { - args = Some(promise::Resolves::new(list)); + args = Some(promise::Any::Resolved(list)); } else { return Err(()); } @@ -116,15 +116,11 @@ impl TryFrom> for PromisedRun { } impl From for PromisedRun { - fn from(ready: Run) -> Self { + fn from(run: Run) -> Self { PromisedRun { - module: promise::Resolves::from(Ok(ready.module)), - function: promise::Resolves::from(Ok(ready.function)), - args: promise::Resolves::from(Ok(ready - .args - .iter() - .map(|ipld| ipld.clone().into()) - .collect())), + module: promise::Any::Resolved(run.module), + function: promise::Any::Resolved(run.function), + args: promise::Any::Resolved(run.args.iter().map(|ipld| ipld.clone().into()).collect()), } } } @@ -132,9 +128,9 @@ impl From for PromisedRun { impl From for arguments::Named { fn from(promised: PromisedRun) -> Self { arguments::Named::from_iter([ - ("module".into(), promised.module.into()), - ("function".into(), promised.function.into()), - ("args".into(), promised.args.into()), + ("module".into(), promised.module.to_promised_ipld()), + ("function".into(), promised.function.to_promised_ipld()), + ("args".into(), promised.args.to_promised_ipld()), ]) } } diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 73907a97..baacb12b 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -5,51 +5,39 @@ mod err; mod ok; mod pending; mod resolvable; -mod resolves; pub mod store; // FIXME pub mod js; -pub use any::PromiseAny; +pub use any::Any; pub use err::PromiseErr; pub use ok::PromiseOk; pub use pending::Pending; pub use resolvable::*; -pub use resolves::Resolves; pub use store::Store; use enum_as_inner::EnumAsInner; +use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; /// Top-level union of all UCAN Promise options #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -#[serde(untagged)] pub enum Promise { /// The `ucan/await/ok` promise - Ok(PromiseOk), + Ok(T), /// The `ucan/await/err` promise - Err(PromiseErr), + Err(E), - /// The `ucan/await/*` promise - Any(PromiseAny), - // Tagged `ucan/await` -} + /// The `ucan/await/ok` promise + PendingOk(Cid), -impl From> for Promise { - fn from(p_ok: PromiseOk) -> Self { - Promise::Ok(p_ok) - } -} + /// The `ucan/await/err` promise + PendingErr(Cid), -impl From> for Promise { - fn from(p_err: PromiseErr) -> Self { - Promise::Err(p_err) - } -} + /// The `ucan/await/*` promise + PendingAny(Cid), -impl From> for Promise { - fn from(p_any: PromiseAny) -> Self { - Promise::Any(p_any) - } + /// The `ucan/await` promise + PendingTagged(Cid), } diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 1825df10..657c1366 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -1,237 +1,111 @@ -use super::{err::PromiseErr, ok::PromiseOk}; -use crate::{ability::arguments, ipld::cid}; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::{Deserializer, Error, MapAccess, Visitor}, - Deserialize, Serialize, -}; -use std::fmt; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. -/// -/// Unlike [`Resolves`][super::Resolves]: -/// -/// 1. The branches may be of different types -/// 2. The underlying value is _left wrapped_ in `{"ok": T}` or `{"err": T}` capsules -/// -/// FIXME example -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -#[serde(untagged)] -pub enum PromiseAny { - /// The fulfilled (resolved) value. - Fulfilled(#[serde(rename = "ucan/ok")] T), - - /// The failure state of a promise. - Rejected(#[serde(rename = "ucan/err")] E), - - /// A deferred value and its associated [`Selector`]. - /// - /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] - /// and substitute into the value. - Pending(#[serde(rename = "await/*")] Cid), -} - -impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Deserialize<'de> for PromiseAny { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - struct PromiseAnyVisitor(std::marker::PhantomData<(T, E)>); - - impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Visitor<'de> for PromiseAnyVisitor { - type Value = PromiseAny; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a promise") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: MapAccess<'de>, - { - if map.size_hint() != Some(1) { - return Err(serde::de::Error::custom("expected a single key")); - } - - let key = map - .next_key::()? - .ok_or(Error::invalid_length(0, &"expected exactly 1 key"))?; - - match key.as_str() { - "ucan/ok" => { - let val = map.next_value()?; - return Ok(PromiseAny::Fulfilled(val)); - } - - "ucan/err" => { - let err = map.next_value()?; - return Ok(PromiseAny::Rejected(err)); - } - - "await/*" => { - let cid = map.next_value()?; - return Ok(PromiseAny::Pending(cid)); - } - - _ => return Err(serde::de::Error::custom("expected a valid PromiseAny")), - } - } - } - - deserializer.deserialize_map(PromiseAnyVisitor(std::marker::PhantomData)) - } +use crate::ipld; +use super::pending::Pending; +use enum_as_inner::EnumAsInner; +use libipld_core::cid::Cid; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +// FIXME +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +pub enum Any { + /// The `ucan/await/ok` promise + Resolved(T), + + /// The `ucan/await/ok` promise + PendingOk(Cid), + + /// The `ucan/await/err` promise + PendingErr(Cid), + + /// The `ucan/await/*` promise + PendingAny(Cid), } -impl PromiseAny { - pub fn map(self, f: F) -> PromiseAny - where - F: FnOnce(T) -> U, - { +impl Any { + pub fn try_resolve(self) -> Result> { match self { - PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(f(val)), - PromiseAny::Rejected(err) => PromiseAny::Rejected(err), - PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + Any::Resolved(value) => Ok(value), + _ => Err(self), } } - pub fn map_err(self, f: F) -> PromiseAny + pub fn from_ipld(ipld: Ipld) -> Self where - F: FnOnce(E) -> X, + T: From, { - match self { - PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(val), - PromiseAny::Rejected(err) => PromiseAny::Rejected(f(err)), - PromiseAny::Pending(cid) => PromiseAny::Pending(cid), - } - } -} - -impl From> for Ipld { - fn from(p: PromiseAny) -> Ipld { - p.into() - } -} + match ipld { + Ipld::Map(ref map) => { + if let Some(Ipld::Link(cid)) = map.get("ucan/await/ok") { + return Any::PendingOk(cid.clone()); + } -impl TryFrom for PromiseAny -where - T: for<'de> Deserialize<'de>, - E: for<'de> Deserialize<'de>, -{ - type Error = SerdeError; + if let Some(Ipld::Link(cid)) = map.get("ucan/await/err") { + return Any::PendingErr(cid.clone()); + } - fn try_from(ipld: Ipld) -> Result, Self::Error> { - ipld_serde::from_ipld(ipld) - } -} + if let Some(Ipld::Link(cid)) = map.get("ucan/await/*") { + return Any::PendingAny(cid.clone()); + } -impl, E: Into> From> for arguments::Named { - fn from(p: PromiseAny) -> arguments::Named { - match p { - PromiseAny::Fulfilled(val) => { - arguments::Named::from_iter([("ucan/ok".into(), val.into())]) - } - PromiseAny::Rejected(err) => { - arguments::Named::from_iter([("ucan/err".into(), err.into())]) + Any::Resolved(ipld.into()) } - PromiseAny::Pending(cid) => { - arguments::Named::from_iter([("await/*".into(), cid.into())]) - } - } - } -} - -impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom> - for PromiseAny -{ - type Error = (); // FIXME - - fn try_from(args: arguments::Named) -> Result, Self::Error> { - if args.len() != 1 { - return Err(()); - } - - if let Some(ipld) = args.get("ucan/ok") { - return Ok(PromiseAny::Fulfilled(ipld.try_into().map_err(|_| ())?)); - } - - if let Some(ipld) = args.get("ucan/err") { - return Ok(PromiseAny::Rejected(ipld.try_into().map_err(|_| ())?)); - } - - if let Some(ipld) = args.get("await/*") { - return match cid::Newtype::try_from(ipld) { - Ok(nt) => Ok(PromiseAny::Pending(nt.cid)), - Err(_) => Err(()), - }; + other => Any::Resolved(other.into()), } - - Err(()) } -} -impl From> for PromiseAny { - fn from(p_ok: PromiseOk) -> PromiseAny { - match p_ok { - PromiseOk::Fulfilled(val) => PromiseAny::Fulfilled(val), - PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + pub fn to_promised_ipld(self) -> ipld::Promised + where + T: Into, + { + match self { + Any::Resolved(value) => value.into(), + Any::PendingOk(cid) => ipld::Promised::WaitOk(cid), + Any::PendingErr(cid) => ipld::Promised::WaitErr(cid), + Any::PendingAny(cid) => ipld::Promised::WaitAny(cid), } } } -impl From> for PromiseAny { - fn from(p_err: PromiseErr) -> PromiseAny { - match p_err { - PromiseErr::Rejected(err) => PromiseAny::Rejected(err), - PromiseErr::Pending(cid) => PromiseAny::Pending(cid), +impl From for Any { + fn from(pending: Pending) -> Any { + match pending { + Pending::Ok(cid) => Any::PendingOk(cid), + Pending::Err(cid) => Any::PendingErr(cid), + Pending::Any(cid) => Any::PendingAny(cid), } } } -impl TryFrom> for PromiseOk { - type Error = (); // FIXME - - fn try_from(p_any: PromiseAny) -> Result, Self::Error> { - match p_any { - PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), - PromiseAny::Rejected(_err) => Err(()), - PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), +impl> From> for Ipld { + fn from(promise: Any) -> Ipld { + match promise { + Any::Resolved(val) => val.into(), + Any::PendingOk(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/ok".to_string(), + cid.into(), + )])), + Any::PendingErr(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/err".to_string(), + cid.into(), + )])), + Any::PendingAny(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/*".to_string(), + cid.into(), + )])), } } } -impl TryFrom> for PromiseErr { - type Error = (); // FIXME +impl> TryFrom for Any { + type Error = >::Error; - fn try_from(p_any: PromiseAny) -> Result, Self::Error> { - match p_any { - PromiseAny::Fulfilled(_val) => Err(()), - PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), - PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), + fn try_from(promised: ipld::Promised) -> Result, Self::Error> { + match promised { + ipld::Promised::WaitOk(cid) => Ok(Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => Ok(Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => Ok(Any::PendingAny(cid)), + other => Ok(Any::Resolved(T::try_from(other)?)), } } } - -#[cfg(feature = "test_utils")] -impl Arbitrary - for PromiseAny -where - T::Strategy: 'static, - T::Parameters: 'static, - E::Strategy: 'static, - E::Parameters: 'static, -{ - type Parameters = (T::Parameters, E::Parameters); - type Strategy = BoxedStrategy; - - fn arbitrary_with((t_args, e_args): Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_args).prop_map(PromiseAny::Fulfilled), - E::arbitrary_with(e_args).prop_map(PromiseAny::Rejected), - cid::Newtype::arbitrary().prop_map(|nt| PromiseAny::Pending(nt.cid)), - ] - .boxed() - } -} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs deleted file mode 100644 index 1dbec8ca..00000000 --- a/src/invocation/promise/resolves.rs +++ /dev/null @@ -1,352 +0,0 @@ -use super::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. -/// -/// Unlike [`PromiseAny`][super::PromiseAny]: -/// -/// 1. Both branches of this promise resolve to the same type -/// 2. The underlying value is unwrapped from the `{"ok": T}` or `{"err": T}` capsules -/// -/// FIXME example -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Resolves { - Ok(PromiseOk), - Err(PromiseErr), -} - -impl Resolves> { - // FIXME Helpful for serde, maybe extract to a trait? - pub fn resolved_none(&self) -> bool { - match self { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(None) => true, - _ => false, - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(None) => true, - _ => false, - }, - } - } - - pub fn try_resolve_option(self) -> Option { - match self { - Resolves::Ok(p_ok) => p_ok.try_resolve().ok()?, - Resolves::Err(p_err) => p_err.try_resolve().ok()?, - } - } -} - -impl Resolves { - pub fn new(val: T) -> Self { - Resolves::Ok(PromiseOk::Fulfilled(val)) - } - - pub fn try_resolve(self) -> Result> { - match self { - Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), - Resolves::Err(p_err) => p_err.try_resolve().map_err(Resolves::Err), - } - } - - // FIXME replace with variadic macro? - // FIXME docs - pub fn try_resolve_2( - t: Resolves, - u: Resolves, - ) -> Result<(T, U), (Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - { - if t.is_ready() && u.is_ready() { - Ok((t.try_resolve().unwrap(), u.try_resolve().unwrap())) - } else { - Err((t, u)) - } - } - - pub fn try_resolve_3( - t: Resolves, - u: Resolves, - v: Resolves, - ) -> Result<(T, U, V), (Resolves, Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - )) - } else { - Err((t, u, v)) - } - } - - pub fn try_resolve_4( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - ) -> Result<(T, U, V, W), (Resolves, Resolves, Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w)) - } - } - - pub fn try_resolve_5( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - x: Resolves, - ) -> Result< - (T, U, V, W, X), - ( - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - ), - > - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - X: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() && x.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - x.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w, x)) - } - } - - pub fn try_resolve_6( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - x: Resolves, - y: Resolves, - ) -> Result< - (T, U, V, W, X, Y), - ( - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - ), - > - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - X: fmt::Debug, - Y: fmt::Debug, - { - if [ - t.is_ready(), - u.is_ready(), - v.is_ready(), - w.is_ready(), - x.is_ready(), - y.is_ready(), - ] - .iter() - .all(|x| *x) - { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - x.try_resolve().unwrap(), - y.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w, x, y)) - } - } - - pub fn is_ready(&self) -> bool { - match self { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(_) => true, - _ => false, - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(_) => true, - _ => false, - }, - } - } - - pub fn map(self, f: F) -> Resolves - where - F: FnOnce(T) -> U, - { - match self { - Resolves::Ok(p_ok) => Resolves::Ok(p_ok.map(f)), - Resolves::Err(p_err) => Resolves::Err(p_err.map(f)), - } - } -} - -impl From for Resolves { - fn from(pending: Pending) -> Self { - match pending { - Pending::Ok(cid) => Resolves::Ok(PromiseOk::Pending(cid)), - Pending::Err(cid) => Resolves::Err(PromiseErr::Pending(cid)), - Pending::Any(cid) => Resolves::Ok(PromiseOk::Pending(cid)), // FIXME - } - } -} - -impl> TryFrom for Resolves { - type Error = Ipld; - - fn try_from(ipld: Ipld) -> Result { - // FIXME so much cloning - let t = ipld.clone().try_into().map_err(|_| ipld.clone())?; - Ok(PromiseOk::Fulfilled(t).into()) - } -} - -impl From> for Option { - fn from(r: Resolves) -> Option { - match r { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl From> for Resolves { - fn from(result: Result) -> Self { - match result { - Ok(value) => Resolves::Ok(PromiseOk::Fulfilled(value)), - Err(value) => Resolves::Err(PromiseErr::Rejected(value)), - } - } -} - -impl> From> for Ipld { - fn from(resolves: Resolves) -> Ipld { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl From> for Resolves { - fn from(ok: PromiseOk) -> Self { - Resolves::Ok(ok) - } -} - -impl From> for Resolves { - fn from(err: PromiseErr) -> Self { - Resolves::Err(err) - } -} - -impl From> for Promise { - fn from(resolves: Resolves) -> Promise { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl TryFrom> for PromiseOk { - type Error = PromiseErr; - - fn try_from(resolved: Resolves) -> Result { - match resolved { - Resolves::Ok(ok) => Ok(ok), - Resolves::Err(err) => Err(err), - } - } -} - -impl TryFrom> for PromiseErr { - type Error = PromiseOk; - - fn try_from(resolved: Resolves) -> Result { - match resolved { - Resolves::Ok(ok) => Err(ok), - Resolves::Err(err) => Ok(err), - } - } -} - -impl From> for PromiseAny { - fn from(resolve: Resolves) -> PromiseAny { - match resolve { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(value) => PromiseAny::Fulfilled(value), - PromiseOk::Pending(cid) => PromiseAny::Pending(cid), - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(err) => PromiseAny::Rejected(err), - PromiseErr::Pending(cid) => PromiseAny::Pending(cid), - }, - } - } -} - -#[cfg(feature = "test_utils")] -impl Arbitrary for Resolves -where - T::Strategy: 'static, - T::Parameters: 'static, -{ - type Parameters = (T::Parameters, T::Parameters); - type Strategy = BoxedStrategy; - - fn arbitrary_with((ok_args, err_args): Self::Parameters) -> Self::Strategy { - prop_oneof![ - PromiseOk::::arbitrary_with(ok_args).prop_map(Resolves::Ok), - PromiseErr::::arbitrary_with(err_args).prop_map(Resolves::Err), - ] - .boxed() - } -} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 43a1f480..30a28c89 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,8 +1,8 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::fmt; use std::path::PathBuf; use thiserror::Error; -use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 781045a9..0416fde2 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,6 +1,6 @@ use crate::{ ability::arguments, - invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, + invocation::promise::{self, Pending, PromiseErr, PromiseOk}, ipld, url, }; use enum_as_inner::EnumAsInner; @@ -51,6 +51,15 @@ pub enum Promised { } impl Promised { + pub fn try_resolve(self) -> Result { + match self { + Promised::WaitOk(cid) => Err(Pending::Ok(cid)), + Promised::WaitErr(cid) => Err(Pending::Err(cid)), + Promised::WaitAny(cid) => Err(Pending::Any(cid)), + other => other.try_into().map_err(Into::into), + } + } + pub fn with_resolved(self, f: F) -> Result where F: FnOnce(Ipld) -> T, @@ -70,6 +79,30 @@ impl Promised { Err(promised) => Ok(f(promised)), } } + + pub fn to_promise_any>( + self, + ) -> Result, >::Error> { + Ok(match Ipld::try_from(self) { + Ok(ipld) => promise::Any::Resolved(ipld.try_into()?), + Err(pending) => match pending { + Pending::Ok(cid) => promise::Any::PendingOk(cid), + Pending::Err(cid) => promise::Any::PendingErr(cid), + Pending::Any(cid) => promise::Any::PendingAny(cid), + }, + }) + } + + // FIXME return type + pub fn to_promise_any_string(self) -> Result, ()> { + match self { + Promised::String(s) => Ok(promise::Any::Resolved(s)), + Promised::WaitOk(cid) => Ok(promise::Any::PendingOk(cid)), + Promised::WaitErr(cid) => Ok(promise::Any::PendingErr(cid)), + Promised::WaitAny(cid) => Ok(promise::Any::PendingAny(cid)), + _ => Err(()), + } + } } impl fmt::Display for Promised { @@ -197,100 +230,13 @@ impl From> for Promised { } } -impl From> for Promised { - fn from(p_any: PromiseAny) -> Promised { +impl From> for Promised { + fn from(p_any: promise::Any) -> Promised { match p_any { - PromiseAny::Fulfilled(ipld) => ipld.into(), - PromiseAny::Rejected(ipld) => ipld.into(), - PromiseAny::Pending(cid) => Promised::WaitAny(cid), - } - } -} - -impl From> for Promised { - fn from(promise: Promise) -> Promised { - match promise { - Promise::Ok(p_ok) => p_ok.into(), - Promise::Err(p_err) => p_err.into(), - Promise::Any(p_any) => p_any.into(), - } - } -} - -impl> TryFrom for Resolves { - type Error = (); - - fn try_from(promised: Promised) -> Result, Self::Error> { - match promised { - Promised::WaitOk(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), - Promised::WaitErr(cid) => Ok(Resolves::Err(PromiseErr::Pending(cid))), - Promised::WaitAny(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), // FIXME - - Promised::Null => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Null.into()).map_err(|_| ())?, - ))), - Promised::Bool(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Bool(b).into()).map_err(|_| ())?, - ))), - Promised::Integer(i) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Integer(i).into()).map_err(|_| ())?, - ))), - Promised::Float(f) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Float(f).into()).map_err(|_| ())?, - ))), - Promised::String(s) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::String(s).into()).map_err(|_| ())?, - ))), - Promised::Bytes(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Bytes(b).into()).map_err(|_| ())?, - ))), - Promised::Link(cid) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Link(cid).into()).map_err(|_| ())?, - ))), - - Promised::List(list) => { - let vec: Vec = list.into_iter().try_fold(vec![], |mut acc, promised| { - let ipld: Ipld = promised.try_into().map_err(|_| ())?; - acc.push(ipld); - Ok(acc) - })?; - - Ok(Resolves::Ok(PromiseOk::Fulfilled( - ipld::Newtype(Ipld::List(vec)).try_into().map_err(|_| ())?, - ))) - } - - Promised::Map(map) => { - let btree: BTreeMap = - map.into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - let ipld: Ipld = v.try_into().map_err(|_| ())?; - acc.insert(k, ipld); - Ok(acc) - })?; - - Ok(Resolves::Ok(PromiseOk::Fulfilled( - ipld::Newtype(Ipld::Map(btree)).try_into().map_err(|_| ())?, - ))) - } - } - } -} - -impl From> for Promised -where - Promised: From, -{ - fn from(r: Resolves) -> Promised { - match r { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(val) => val.into(), - PromiseOk::Pending(cid) => Promised::WaitOk(cid), - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(val) => val.into(), - PromiseErr::Pending(cid) => Promised::WaitErr(cid), - }, + promise::Any::Resolved(ipld) => ipld.into(), + promise::Any::PendingOk(cid) => Promised::WaitOk(cid), + promise::Any::PendingErr(cid) => Promised::WaitErr(cid), + promise::Any::PendingAny(cid) => Promised::WaitAny(cid), } } } From 537811f829132400e8c6d80d8405e0c1d495fe1c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 20:41:25 -0800 Subject: [PATCH 181/234] Switch to envelope trait --- src/ability/pipe.rs | 20 +- src/crypto/signature/envelope.rs | 577 ++++++++++++++++++++----------- src/delegation.rs | 128 +++---- src/delegation/agent.rs | 14 +- src/invocation.rs | 179 +++++----- src/invocation/agent.rs | 82 +++-- src/lib.rs | 1 - src/proof.rs | 38 -- src/receipt.rs | 135 +++++--- 9 files changed, 681 insertions(+), 493 deletions(-) delete mode 100644 src/proof.rs diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index a41d9c01..0dd41874 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,22 +1,22 @@ use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe, Enc: Codec + TryFrom + Into> { - pub source: Cap, - pub sink: Cap, +pub struct Pipe, C: Codec + TryFrom + Into> { + pub source: Cap, + pub sink: Cap, } -pub enum Cap, Enc: Codec + TryFrom + Into> { - Proof(delegation::Proof), +pub enum Cap, C: Codec + TryFrom + Into> { + Proof(delegation::Proof), Literal(Ipld), } -pub struct PromisedPipe, Enc: Codec + TryFrom + Into> { - pub source: PromisedCap, - pub sink: PromisedCap, +pub struct PromisedPipe, C: Codec + TryFrom + Into> { + pub source: PromisedCap, + pub sink: PromisedCap, } -pub enum PromisedCap, Enc: Codec + TryFrom + Into> { - Proof(delegation::Proof), +pub enum PromisedCap, C: Codec + TryFrom + Into> { + Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index b0f3aa51..a8b358fc 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,8 +1,4 @@ -use crate::{ - capsule::Capsule, - crypto::varsig, - did::{Did, Verifiable}, -}; +use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, @@ -10,64 +6,79 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use signature::Verifier; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; -/// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] -pub struct Envelope< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - /// The [Varsig][crate::crypto::varsig] header. - pub varsig_header: V, +pub trait Envelope: Sized { + type DID: Did; + type Payload: Clone + Capsule + TryFrom + Into; + type VarsigHeader: varsig::Header + Clone; + type Encoder: Codec + TryFrom + Into; + + fn varsig_header(&self) -> &Self::VarsigHeader; + fn signature(&self) -> &::Signature; + fn payload(&self) -> &Self::Payload; + fn verifier(&self) -> &Self::DID; + + fn construct( + varsig_header: Self::VarsigHeader, + signature: ::Signature, + payload: Self::Payload, + ) -> Self; + + fn to_ipld_envelope(&self) -> Ipld { + let wrapped_payload: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) + .into(); + + let header_bytes: Vec = (*self.varsig_header()).clone().into(); + let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); + let sig_bytes: Ipld = self.signature().to_vec().into(); + + vec![sig_bytes.into(), header].into() + } - /// The signture of the `payload`. - pub signature: DID::Signature, + fn try_from_ipld_envelope( + ipld: Ipld, + ) -> Result>::Error>> { + if let Ipld::List(list) = ipld { + if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { + if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { + if let (1, Some(inner)) = ( + btree.len(), + btree.get(::TAG.into()), + ) { + let payload = Self::Payload::try_from(inner.clone()) + .map_err(FromIpldError::CannotParsePayload)?; - /// The payload that's being signed over. - pub payload: T, + let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) + .map_err(|_| FromIpldError::CannotParseVarsigHeader)?; - _phantom: std::marker::PhantomData, -} + let signature = ::Signature::try_from(sig.as_slice()) + .map_err(|_| FromIpldError::CannotParseSignature)?; -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Verifiable for Envelope -{ - fn verifier(&self) -> &DID { - &self.payload.verifier() - } -} - -impl< - T: Capsule + Verifiable + Into, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Envelope -{ - pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { - Envelope { - varsig_header, - signature, - payload, - _phantom: std::marker::PhantomData, + Ok(Self::construct(varsig_header, signature, payload)) + } else { + Err(FromIpldError::InvalidPayloadCapsule) + } + } else { + Err(FromIpldError::InvalidVarsigContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) } } - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode + From, + Ipld: Encode + From, { - let codec = self.varsig_header.codec().clone(); + let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let ipld = Ipld::from(self); ipld.encode(codec, w) } @@ -82,14 +93,14 @@ impl< /// # Errors /// /// * [`SignError`] - the payload can't be encoded or the signature fails. - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: T, - ) -> Result, SignError> + // FIXME ported + fn try_sign( + signer: &::Signer, + varsig_header: Self::VarsigHeader, + payload: Self::Payload, + ) -> Result where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { Self::try_sign_generic(signer, varsig_header, payload) } @@ -108,32 +119,26 @@ impl< /// /// # Example /// - /// FIXME - pub fn try_sign_generic( - signer: &DID::Signer, - varsig_header: V, - payload: T, - ) -> Result, SignError> + fn try_sign_generic( + signer: &::Signer, + varsig_header: Self::VarsigHeader, + payload: Self::Payload, + ) -> Result where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + let ipld: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; - ipld.encode(*varsig_header.codec(), &mut buffer) + ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; let signature = signer .try_sign(&buffer) .map_err(SignError::SignatureError)?; - Ok(Envelope { - varsig_header, - signature, - payload, - _phantom: std::marker::PhantomData, - }) + Ok(Self::construct(varsig_header, signature, payload)) } /// Attempt to validate a signature. @@ -149,98 +154,272 @@ impl< /// # Exmaples /// /// FIXME - pub fn validate_signature(&self) -> Result<(), ValidateError> + fn validate_signature(&self) -> Result<(), ValidateError> where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { let mut encoded = vec![]; - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(*self.varsig_header.codec(), &mut encoded) - .map_err(ValidateError::PayloadEncodingError)?; + let ipld: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) + .into(); + ipld.encode( + *varsig::header::Header::codec(self.varsig_header()), + &mut encoded, + ) + .map_err(ValidateError::PayloadEncodingError)?; self.verifier() - .verify(&encoded, &self.signature) + .verify(&encoded, &self.signature()) .map_err(ValidateError::VerifyError) } - pub fn cid(&self) -> Result + fn cid(&self) -> Result where - Self: Clone, - Ipld: Encode + From, + // Ipld: Encode + From, + Self: Encode, { - let codec = self.varsig_header.codec().clone(); + let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let mut ipld_buffer = vec![]; self.encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( - self.varsig_header.codec().clone().into(), + varsig::header::Header::codec(self.varsig_header()) + .clone() + .into(), multihash, )) } } -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > From> for Ipld -where - Ipld: From, -{ - fn from(envelope: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); - let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); - - Ipld::Map(BTreeMap::from_iter([ - ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), - ("pld".into(), Ipld::List(vec![varsig_header, ipld])), - ])) - } -} - -impl< - T: TryFrom + Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > TryFrom for Envelope -{ - type Error = FromIpldError; - - fn try_from(ipld: Ipld) -> Result { - if let Ipld::List(list) = ipld { - if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { - if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { - Ok(Envelope { - payload: T::try_from(payload.clone()) - .map_err(FromIpldError::CannotParsePayload)?, - - varsig_header: V::try_from(varsig_header.as_slice()) - .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, - - signature: DID::Signature::try_from(sig.as_slice()) - .map_err(|_| FromIpldError::CannotParseSignature)?, - - _phantom: std::marker::PhantomData, - }) - } else { - Err(FromIpldError::InvalidPayloadCapsule) - } - } else { - Err(FromIpldError::InvalidVarsigContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } - } -} +// /// A container associating a `payload` with its signature over it. +// #[derive(Debug, Clone, PartialEq)] +// pub struct Envelope< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > { +// /// The [Varsig][crate::crypto::varsig] header. +// pub varsig_header: V, +// +// /// The signture of the `payload`. +// pub signature: DID::Signature, +// +// /// The payload that's being signed over. +// pub payload: T, +// +// _phantom: std::marker::PhantomData, +// } + +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Verifiable for Envelope +// { +// fn verifier(&self) -> &DID { +// &self.payload.verifier() +// } +// } +// +// impl< +// T: Capsule + Verifiable + Into, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Envelope +// { +// pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { +// Envelope { +// varsig_header, +// signature, +// payload, +// _phantom: std::marker::PhantomData, +// } +// } +// +// // FIXME ported +// pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> +// where +// Ipld: Encode + From, +// { +// let codec = self.varsig_header.codec().clone(); +// let ipld = Ipld::from(self); +// ipld.encode(codec, w) +// } +// +// /// Attempt to sign some payload with a given signer. +// /// +// /// # Arguments +// /// +// /// * `signer` - The signer to use to sign the payload. +// /// * `payload` - The payload to sign. +// /// +// /// # Errors +// /// +// /// * [`SignError`] - the payload can't be encoded or the signature fails. +// // FIXME ported +// pub fn try_sign( +// signer: &DID::Signer, +// varsig_header: V, +// payload: T, +// ) -> Result, SignError> +// where +// T: Clone, +// Ipld: Encode, +// { +// Self::try_sign_generic(signer, varsig_header, payload) +// } +// +// /// Attempt to sign some payload with a given signer and specific codec. +// /// +// /// # Arguments +// /// +// /// * `signer` - The signer to use to sign the payload. +// /// * `codec` - The codec to use to encode the payload. +// /// * `payload` - The payload to sign. +// /// +// /// # Errors +// /// +// /// * [`SignError`] - the payload can't be encoded or the signature fails. +// /// +// /// # Example +// /// +// /// FIXME ported +// pub fn try_sign_generic( +// signer: &DID::Signer, +// varsig_header: V, +// payload: T, +// ) -> Result, SignError> +// where +// T: Clone, +// Ipld: Encode, +// { +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); +// +// let mut buffer = vec![]; +// ipld.encode(*varsig_header.codec(), &mut buffer) +// .map_err(SignError::PayloadEncodingError)?; +// +// let signature = signer +// .try_sign(&buffer) +// .map_err(SignError::SignatureError)?; +// +// Ok(Envelope { +// varsig_header, +// signature, +// payload, +// _phantom: std::marker::PhantomData, +// }) +// } +// +// /// Attempt to validate a signature. +// /// +// /// # Arguments +// /// +// /// * `self` - The envelope to validate. +// /// +// /// # Errors +// /// +// /// * [`ValidateError`] - the payload can't be encoded or the signature fails. +// /// +// /// # Exmaples +// /// +// /// FIXME +// pub fn validate_signature(&self) -> Result<(), ValidateError> +// where +// T: Clone, +// Ipld: Encode, +// { +// let mut encoded = vec![]; +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); +// ipld.encode(*self.varsig_header.codec(), &mut encoded) +// .map_err(ValidateError::PayloadEncodingError)?; +// +// self.verifier() +// .verify(&encoded, &self.signature) +// .map_err(ValidateError::VerifyError) +// } +// +// pub fn cid(&self) -> Result +// where +// Self: Clone, +// Ipld: Encode + From, +// { +// let codec = self.varsig_header.codec().clone(); +// let mut ipld_buffer = vec![]; +// self.encode(codec, &mut ipld_buffer)?; +// +// let multihash = Code::Sha2_256.digest(&ipld_buffer); +// Ok(Cid::new_v1( +// self.varsig_header.codec().clone().into(), +// multihash, +// )) +// } +// } + +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > From> for Ipld +// where +// Ipld: From, +// { +// fn from(envelope: Envelope) -> Self { +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); +// let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); +// +// Ipld::Map(BTreeMap::from_iter([ +// ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), +// ("pld".into(), Ipld::List(vec![varsig_header, ipld])), +// ])) +// } +// } +// +// impl< +// T: TryFrom + Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > TryFrom for Envelope +// { +// type Error = FromIpldError; +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::List(list) = ipld { +// if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { +// if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { +// if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { +// Ok(Envelope { +// payload: T::try_from(payload.clone()) +// .map_err(FromIpldError::CannotParsePayload)?, +// +// varsig_header: V::try_from(varsig_header.as_slice()) +// .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, +// +// signature: DID::Signature::try_from(sig.as_slice()) +// .map_err(|_| FromIpldError::CannotParseSignature)?, +// +// _phantom: std::marker::PhantomData, +// }) +// } else { +// Err(FromIpldError::InvalidPayloadCapsule) +// } +// } else { +// Err(FromIpldError::InvalidVarsigContainer) +// } +// } else { +// Err(FromIpldError::InvalidSignatureContainer) +// } +// } else { +// Err(FromIpldError::InvalidSignatureContainer) +// } +// } +// } #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { @@ -262,26 +441,26 @@ pub enum FromIpldError { #[error("Invalid payload capsule")] InvalidPayloadCapsule, } - -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > Encode for Envelope -where - Self: Clone, - Ipld: Encode + From, -{ - fn encode( - &self, - codec: Enc, - w: &mut W, - ) -> Result<(), libipld_core::error::Error> { - let ipld: Ipld = self.clone().into(); - ipld.encode(codec, w) - } -} +// +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > Encode for Envelope +// where +// Self: Clone, +// Ipld: Encode + From, +// { +// fn encode( +// &self, +// codec: C, +// w: &mut W, +// ) -> Result<(), libipld_core::error::Error> { +// let ipld: Ipld = self.clone().into(); +// ipld.encode(codec, w) +// } +// } /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] @@ -307,37 +486,37 @@ pub enum ValidateError { VerifyError(#[from] signature::Error), } -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Serialize for Envelope -{ - fn serialize(&self, serializer: S) -> std::prelude::v1::Result - where - S: Serializer, - { - self.clone().serialize(serializer) - } -} - -impl< - 'de, - T: Verifiable + Capsule + TryFrom, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Deserialize<'de> for Envelope -where - Envelope: TryFrom, - as TryFrom>::Error: std::fmt::Display, -{ - fn deserialize(deserializer: D) -> std::prelude::v1::Result - where - D: Deserializer<'de>, - { - let ipld: Ipld = Deserialize::deserialize(deserializer)?; - Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) - } -} +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Serialize for Envelope +// { +// fn serialize(&self, serializer: S) -> std::prelude::v1::Result +// where +// S: Serializer, +// { +// self.clone().serialize(serializer) +// } +// } +// +// impl< +// 'de, +// T: Verifiable + Capsule + TryFrom, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Deserialize<'de> for Envelope +// where +// Envelope: TryFrom, +// as TryFrom>::Error: std::fmt::Display, +// { +// fn deserialize(deserializer: D) -> std::prelude::v1::Result +// where +// D: Deserializer<'de>, +// { +// let ipld: Ipld = Deserialize::deserialize(deserializer)?; +// Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) +// } +// } diff --git a/src/delegation.rs b/src/delegation.rs index 992341c6..3d43b6d8 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -23,15 +23,12 @@ pub use payload::*; use crate::{ capsule::Capsule, - crypto::{signature, varsig, Nonce}, + crypto::{signature::Envelope, varsig, Nonce}, did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::{ - cid::Cid, - codec::{Codec, Encode}, - ipld::Ipld, -}; +use libipld_core::link::Link; +use libipld_core::{codec::Codec, ipld::Ipld}; use policy::Predicate; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -45,15 +42,21 @@ pub struct Delegation< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(signature::Envelope, DID, V, C>); +> { + pub varsig_header: V, + pub payload: Payload, + pub signature: DID::Signature, + _marker: std::marker::PhantomData, +} -// FIXME rename proofs? #[derive(Clone, Debug, PartialEq)] pub struct Proof< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(Vec>); +> { + pub prf: Vec>>, +} impl, C: Codec + TryFrom + Into> Capsule for Proof @@ -67,118 +70,114 @@ impl, C: Codec + Into + TryFrom> Delega signature: DID::Signature, payload: Payload, ) -> Delegation { - Delegation(signature::Envelope::new(varsig_header, signature, payload)) + Delegation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { - &self.0.payload.issuer + &self.payload.issuer } /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &Option { - &self.0.payload.subject + &self.payload.subject } /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { - &self.0.payload.audience + &self.payload.audience } /// Retrive the `policy` of a [`Delegation`] pub fn policy(&self) -> &Vec { - &self.0.payload.policy + &self.payload.policy } /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { - &self.0.payload.metadata + &self.payload.metadata } /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { - &self.0.payload.nonce + &self.payload.nonce } /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { - self.0.payload.not_before.as_ref() + self.payload.not_before.as_ref() } /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { - &self.0.payload.expiration + &self.payload.expiration } pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - self.0.payload.check_time(now) - } - - pub fn payload(&self) -> &Payload { - &self.0.payload - } - - pub fn varsig_header(&self) -> &V { - &self.0.varsig_header + self.payload.check_time(now) } +} - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } +impl + Clone, C: Codec + TryFrom + Into> Envelope + for Delegation +where + Payload: TryFrom, +{ + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; - pub fn signature(&self) -> &DID::Signature { - &self.0.signature + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Delegation { + Delegation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } - pub fn codec(&self) -> &C { - self.varsig_header().codec() + fn varsig_header(&self) -> &V { + &self.varsig_header } - pub fn cid(&self) -> Result - where - signature::Envelope, DID, V, C>: Clone + Encode, - Ipld: Encode, - { - self.0.cid() + fn payload(&self) -> &Payload { + &self.payload } - pub fn validate_signature(&self) -> Result<(), signature::ValidateError> - where - Payload: Clone, - Ipld: Encode, - { - self.0.validate_signature() + fn signature(&self) -> &DID::Signature { + &self.signature } - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: Payload, - ) -> Result - where - Ipld: Encode, - Payload: Clone, - { - signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, C: Codec + TryFrom + Into> Serialize +impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> - for Delegation +impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> + Deserialize<'de> for Delegation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -187,6 +186,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Delegation) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index e7dc133b..396a650c 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,6 +1,6 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::{ - crypto::{varsig, Nonce}, + crypto::{signature::Envelope, varsig, Nonce}, did::Did, time::Timestamp, }; @@ -38,7 +38,7 @@ impl< 'a, DID: Did + ToString + Clone, S: Store + Clone, - V: varsig::Header, + V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, > Agent<'a, DID, S, V, Enc> where @@ -64,7 +64,10 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> + where + Payload: TryFrom, + { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -119,7 +122,10 @@ where &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> { + ) -> Result<(), ReceiveError> + where + Payload: TryFrom, + { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/invocation.rs b/src/invocation.rs index 4c9e9f8a..823ca793 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,8 +22,7 @@ pub use agent::Agent; pub use payload::*; use crate::{ - ability, - crypto::{signature, varsig}, + crypto::{signature::Envelope, varsig}, did::{self, Did}, time::{Expired, Timestamp}, }; @@ -53,108 +52,68 @@ pub struct Invocation< DID: did::Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(pub signature::Envelope, DID, V, C>); +> { + pub varsig_header: V, + pub payload: Payload, + pub signature: DID::Signature, + _marker: std::marker::PhantomData, +} -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> Invocation where Ipld: Encode, { - pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { - Invocation(signature::Envelope::new(varsig_header, signature, payload)) - } - - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } - - pub fn payload(&self) -> &Payload { - &self.0.payload - } - - pub fn varsig_header(&self) -> &V { - &self.0.varsig_header - } - - pub fn signature(&self) -> &DID::Signature { - &self.0.signature + pub fn new(varsig_header: V, signature: DID::Signature, payload: Payload) -> Self { + Invocation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } pub fn audience(&self) -> &Option { - &self.0.payload.audience + &self.payload.audience } pub fn issuer(&self) -> &DID { - &self.0.payload.issuer + &self.payload.issuer } pub fn subject(&self) -> &DID { - &self.0.payload.subject + &self.payload.subject } pub fn ability(&self) -> &A { - &self.0.payload.ability + &self.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { - Invocation(signature::Envelope::new( - self.0.varsig_header, - self.0.signature, - self.0.payload.map_ability(f), - )) + Invocation::new( + self.varsig_header, + self.signature, + self.payload.map_ability(f), + ) } pub fn proofs(&self) -> &Vec { - &self.payload().proofs + &self.payload.proofs } pub fn issued_at(&self) -> &Option { - &self.payload().issued_at + &self.payload.issued_at } pub fn expiration(&self) -> &Option { - &self.payload().expiration + &self.payload.expiration } pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { - self.payload().check_time(now) - } - - pub fn codec(&self) -> &C { - self.varsig_header().codec() - } - - pub fn cid(&self) -> Result - where - signature::Envelope, DID, V, C>: Clone, - Ipld: Encode, - { - self.0.cid() - } - - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: Payload, - ) -> Result, signature::SignError> - where - Payload: Clone, - { - let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; - Ok(Invocation(envelope)) - } - - pub fn validate_signature(&self) -> Result<(), signature::ValidateError> - where - Payload: Clone, - { - self.0.validate_signature() + self.payload.check_time(now) } } @@ -162,43 +121,92 @@ impl, C: Codec + TryFrom + Into> did for Invocation { fn verifier(&self) -> &DID { - &self.0.verifier() + &self.verifier() } } -impl, C: Codec + TryFrom + Into> - From> for Ipld +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > From> for Ipld +where + Payload: TryFrom, { fn from(invocation: Invocation) -> Self { - invocation.0.into() + invocation.to_ipld_envelope() } } -impl, C: Codec + TryFrom + Into> TryFrom - for Invocation +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Envelope for Invocation where Payload: TryFrom, { - type Error = , DID, V, C> as TryFrom>::Error; + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; + + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Invocation { + Invocation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } + } - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Invocation) + fn varsig_header(&self) -> &V { + &self.varsig_header + } + + fn payload(&self) -> &Payload { + &self.payload + } + + fn signature(&self) -> &DID::Signature { + &self.signature + } + + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, C: Codec + TryFrom + Into> Serialize - for Invocation +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Serialize for Invocation +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, A, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> - for Invocation +impl< + 'de, + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Deserialize<'de> for Invocation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -207,6 +215,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Invocation) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 71bfa770..1ab29ac0 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,7 +6,10 @@ use super::{ }; use crate::{ ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, - crypto::{signature, varsig, Nonce}, + crypto::{ + signature::{self, Envelope}, + varsig, Nonce, + }, delegation, did::Did, invocation::promise, @@ -30,11 +33,11 @@ pub struct Agent< 'a, T: Resolvable, DID: Did, - S: Store, + S: Store, P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, + D: delegation::store::Store, + V: varsig::Header + Clone, + C: Codec + Into + TryFrom, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -49,21 +52,21 @@ pub struct Agent< pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, V, Enc)>, + marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, Enc> Agent<'a, T, DID, S, P, D, V, Enc> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> where T::Promised: Clone, - Ipld: Encode, + Ipld: Encode, delegation::Payload: Clone, T: Resolvable + Clone, DID: Did + Clone, - S: Store, + S: Store, P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, + D: delegation::store::Store, + V: varsig::Header + Clone, + C: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -94,12 +97,15 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, ParseAbilityError<()>, // FIXME argserror >, - > { + > + where + Payload: TryFrom, + { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), vec![], now) @@ -138,12 +144,15 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, ParseAbilityError<()>, // FIXME errs >, - > { + > + where + Payload: TryFrom, + { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), vec![], now) @@ -172,19 +181,15 @@ where pub fn receive( &mut self, - promised: Invocation, + promised: Invocation, now: &SystemTime, - ) -> Result< - Recipient>, - ReceiveError, - > + ) -> Result>, ReceiveError> where - Enc: From + Into, + Payload: TryFrom, arguments::Named: From, - Invocation: Clone, + Invocation: Clone + Encode,

>::PromiseStoreError: fmt::Debug, - signature::Envelope, DID, V, Enc>: Clone, - ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, + ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; let _ = promised @@ -211,15 +216,17 @@ where } }; - let proof_payloads = self + let proof_payloads: Vec<&delegation::Payload> = self .delegation_store .get_many(&promised.proofs()) .map_err(ReceiveError::DelegationStoreError)? - .into_iter() - .map(|d| d.payload()) - .collect(); + .iter() + .fold(vec![], |mut acc, d| { + acc.push(&d.payload); + acc + }); - let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); + let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); let _ = &resolved_payload .check(proof_payloads, now) @@ -240,9 +247,10 @@ where now: Timestamp, varsig_header: V, // FIXME return type - ) -> Result, ()> + ) -> Result, ()> where T: From, + Payload: TryFrom, { let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { @@ -278,7 +286,7 @@ where #[derive(Debug)] pub enum Recipient { - // FIXME change to status + // FIXME change to status? You(T), Other(T), Unresolved(Cid), @@ -290,12 +298,12 @@ pub enum ReceiveError< P: promise::Store, DID: Did, D, - S: Store, - V: varsig::Header, - Enc: Codec + From + Into, + S: Store, + V: varsig::Header, + C: Codec + TryFrom + Into, > where

>::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, + ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -305,7 +313,7 @@ pub enum ReceiveError< #[error("invocation store error: {0}")] InvocationStoreError( - #[source] ::Promised, DID, V, Enc>>::InvocationStoreError, + #[source] ::Promised, DID, V, C>>::InvocationStoreError, ), #[error("promise store error: {0}")] diff --git a/src/lib.rs b/src/lib.rs index 571310bb..1c21c44a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs deleted file mode 100644 index 126ff504..00000000 --- a/src/proof.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{crypto::varsig, delegation::Delegation, did::Did}; -use libipld_core::{cid::Cid, codec::Codec, link::Link}; -use serde::{Deserialize, Deserializer, Serialize}; - -#[derive(Debug, Clone, PartialEq)] -pub struct Proof, Enc: Codec + TryFrom + Into> { - pub prf: Vec>>, -} - -impl, Enc: Codec + TryFrom + Into> Serialize - for Proof -{ - fn serialize(&self, serializer: S) -> Result { - let chain = self - .prf - .iter() - .map(|link| link.to_string()) - .collect::>(); - - chain.serialize(serializer) - } -} - -impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> - for Proof -{ - fn deserialize>(deserializer: D) -> Result { - let prf = Vec::::deserialize(deserializer)? - .into_iter() - .map(|s| { - let cid: Cid = s.try_into().map_err(serde::de::Error::custom)?; - Ok(cid.into()) - }) - .collect::, _>>()?; - - Ok(Proof { prf }) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index 5ec60a84..087bd013 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -15,93 +15,117 @@ pub use responds::Responds; pub use store::Store; use crate::{ - ability, - crypto::{signature, varsig}, + crypto::{signature::Envelope, varsig}, did::{self, Did}, }; -use libipld_core::{ - codec::{Codec, Encode}, - ipld::Ipld, -}; +use libipld_core::{codec::Codec, ipld::Ipld}; use serde::{Deserialize, Serialize}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt, C: Codec + Into + TryFrom>( - pub signature::Envelope, DID, V, C>, -); - -/// An alias for the [`Receipt`] type with the library preset -/// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -impl, Enc: Codec + Into + TryFrom> - Receipt -{ - /// Returns the [`Payload`] of the [`Receipt`]. - pub fn payload(&self) -> &Payload { - &self.0.payload - } +pub struct Receipt< + T: Responds, + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, +> { + pub varsig_header: V, + pub signature: DID::Signature, + pub payload: Payload, - /// Returns the [`signature::Envelope`] of the [`Receipt`]. - pub fn signature(&self) -> &DID::Signature { - &self.0.signature - } - - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } + _marker: std::marker::PhantomData, } -impl, Enc: Codec + TryFrom + Into> - did::Verifiable for Receipt +impl, C: Codec + TryFrom + Into> + did::Verifiable for Receipt { fn verifier(&self) -> &DID { - &self.0.verifier() + &self.verifier() } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > From> for Ipld +where + Payload: TryFrom, { - fn from(invocation: Receipt) -> Self { - invocation.0.into() + fn from(rec: Receipt) -> Self { + rec.to_ipld_envelope() } } -impl, Enc: Codec + TryFrom + Into> - TryFrom for Receipt +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Envelope for Receipt where Payload: TryFrom, { - type Error = , DID, V, Enc> as TryFrom>::Error; + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Receipt) + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Receipt { + Receipt { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } + } + + fn varsig_header(&self) -> &V { + &self.varsig_header + } + + fn payload(&self) -> &Payload { + &self.payload + } + + fn signature(&self) -> &DID::Signature { + &self.signature + } + + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, Enc: Codec + TryFrom + Into> Serialize - for Receipt +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Serialize for Receipt +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, T: Responds, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> - Deserialize<'de> for Receipt +impl< + 'de, + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Deserialize<'de> for Receipt where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -110,6 +134,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Receipt) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } From d7a7e11956d5ab4a168cc0df799b77d97cbf8990 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 20:57:23 -0800 Subject: [PATCH 182/234] add default for in memory store --- src/delegation/store/memory.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index c13cc828..0cafeea9 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -69,16 +69,24 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore< - DID: Did + Ord, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - ucans: BTreeMap>, +pub struct MemoryStore, C: Codec + TryFrom + Into> { + ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } +impl, C: Codec + TryFrom + Into> Default + for MemoryStore +{ + fn default() -> Self { + MemoryStore { + ucans: BTreeMap::new(), + index: BTreeMap::new(), + revocations: BTreeSet::new(), + } + } +} + // FIXME check that UCAN is valid impl, Enc: Codec + TryFrom + Into> Store for MemoryStore From a5180cd9f47bfbe841d3c9f39bda02b2ebb563f7 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 21:19:26 -0800 Subject: [PATCH 183/234] Helpers for integrating to server --- src/delegation/payload.rs | 17 +++++++++++++++++ src/time/timestamp.rs | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5408bdff..7a544e26 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -74,6 +74,23 @@ pub struct Payload { } impl Payload { + pub fn powerbox(issuer: DID, audience: DID, command: String, expiration: Timestamp) -> Self { + let mut seed = vec![]; + let nonce = Nonce::generate_12(seed.as_mut()); + + Payload { + issuer, + subject: None, + audience, + command, + policy: vec![], + metadata: BTreeMap::new(), + nonce, + expiration, + not_before: None, + } + } + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index a43eb858..8b4cfea7 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -39,7 +39,12 @@ impl Timestamp { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).map_err(|_| OutOfRangeError{ tried: time })?.as_secs() > 0x1FFFFFFFFFFFFF { + if time + .duration_since(UNIX_EPOCH) + .map_err(|_| OutOfRangeError { tried: time })? + .as_secs() + > 0x1FFFFFFFFFFFFF + { Err(OutOfRangeError { tried: time }) } else { Ok(Timestamp { time }) @@ -52,6 +57,11 @@ impl Timestamp { .expect("the current time to be somtime in the 3rd millenium CE") } + pub fn five_years_from_now() -> Timestamp { + Self::new(SystemTime::now() + Duration::from_secs(5 * 365 * 24 * 60 * 60)) + .expect("the current time to be somtime in the 3rd millenium CE") + } + /// Convert a [`Timestamp`] to a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time From 656b52918eaba22a72fc32b8c306f7f1363cf227 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 22:06:44 -0800 Subject: [PATCH 184/234] u64 because libpld --- src/crypto/signature/envelope.rs | 286 +---------------------------- src/crypto/varsig/header/traits.rs | 2 +- src/did/key/signer.rs | 8 +- 3 files changed, 6 insertions(+), 290 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index a8b358fc..13d2b7a4 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -15,7 +15,7 @@ pub trait Envelope: Sized { type DID: Did; type Payload: Clone + Capsule + TryFrom + Into; type VarsigHeader: varsig::Header + Clone; - type Encoder: Codec + TryFrom + Into; + type Encoder: Codec + TryFrom + Into; fn varsig_header(&self) -> &Self::VarsigHeader; fn signature(&self) -> &::Signature; @@ -192,235 +192,6 @@ pub trait Envelope: Sized { } } -// /// A container associating a `payload` with its signature over it. -// #[derive(Debug, Clone, PartialEq)] -// pub struct Envelope< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > { -// /// The [Varsig][crate::crypto::varsig] header. -// pub varsig_header: V, -// -// /// The signture of the `payload`. -// pub signature: DID::Signature, -// -// /// The payload that's being signed over. -// pub payload: T, -// -// _phantom: std::marker::PhantomData, -// } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Verifiable for Envelope -// { -// fn verifier(&self) -> &DID { -// &self.payload.verifier() -// } -// } -// -// impl< -// T: Capsule + Verifiable + Into, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Envelope -// { -// pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { -// Envelope { -// varsig_header, -// signature, -// payload, -// _phantom: std::marker::PhantomData, -// } -// } -// -// // FIXME ported -// pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> -// where -// Ipld: Encode + From, -// { -// let codec = self.varsig_header.codec().clone(); -// let ipld = Ipld::from(self); -// ipld.encode(codec, w) -// } -// -// /// Attempt to sign some payload with a given signer. -// /// -// /// # Arguments -// /// -// /// * `signer` - The signer to use to sign the payload. -// /// * `payload` - The payload to sign. -// /// -// /// # Errors -// /// -// /// * [`SignError`] - the payload can't be encoded or the signature fails. -// // FIXME ported -// pub fn try_sign( -// signer: &DID::Signer, -// varsig_header: V, -// payload: T, -// ) -> Result, SignError> -// where -// T: Clone, -// Ipld: Encode, -// { -// Self::try_sign_generic(signer, varsig_header, payload) -// } -// -// /// Attempt to sign some payload with a given signer and specific codec. -// /// -// /// # Arguments -// /// -// /// * `signer` - The signer to use to sign the payload. -// /// * `codec` - The codec to use to encode the payload. -// /// * `payload` - The payload to sign. -// /// -// /// # Errors -// /// -// /// * [`SignError`] - the payload can't be encoded or the signature fails. -// /// -// /// # Example -// /// -// /// FIXME ported -// pub fn try_sign_generic( -// signer: &DID::Signer, -// varsig_header: V, -// payload: T, -// ) -> Result, SignError> -// where -// T: Clone, -// Ipld: Encode, -// { -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); -// -// let mut buffer = vec![]; -// ipld.encode(*varsig_header.codec(), &mut buffer) -// .map_err(SignError::PayloadEncodingError)?; -// -// let signature = signer -// .try_sign(&buffer) -// .map_err(SignError::SignatureError)?; -// -// Ok(Envelope { -// varsig_header, -// signature, -// payload, -// _phantom: std::marker::PhantomData, -// }) -// } -// -// /// Attempt to validate a signature. -// /// -// /// # Arguments -// /// -// /// * `self` - The envelope to validate. -// /// -// /// # Errors -// /// -// /// * [`ValidateError`] - the payload can't be encoded or the signature fails. -// /// -// /// # Exmaples -// /// -// /// FIXME -// pub fn validate_signature(&self) -> Result<(), ValidateError> -// where -// T: Clone, -// Ipld: Encode, -// { -// let mut encoded = vec![]; -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); -// ipld.encode(*self.varsig_header.codec(), &mut encoded) -// .map_err(ValidateError::PayloadEncodingError)?; -// -// self.verifier() -// .verify(&encoded, &self.signature) -// .map_err(ValidateError::VerifyError) -// } -// -// pub fn cid(&self) -> Result -// where -// Self: Clone, -// Ipld: Encode + From, -// { -// let codec = self.varsig_header.codec().clone(); -// let mut ipld_buffer = vec![]; -// self.encode(codec, &mut ipld_buffer)?; -// -// let multihash = Code::Sha2_256.digest(&ipld_buffer); -// Ok(Cid::new_v1( -// self.varsig_header.codec().clone().into(), -// multihash, -// )) -// } -// } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > From> for Ipld -// where -// Ipld: From, -// { -// fn from(envelope: Envelope) -> Self { -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); -// let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); -// -// Ipld::Map(BTreeMap::from_iter([ -// ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), -// ("pld".into(), Ipld::List(vec![varsig_header, ipld])), -// ])) -// } -// } -// -// impl< -// T: TryFrom + Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > TryFrom for Envelope -// { -// type Error = FromIpldError; -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::List(list) = ipld { -// if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { -// if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { -// if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { -// Ok(Envelope { -// payload: T::try_from(payload.clone()) -// .map_err(FromIpldError::CannotParsePayload)?, -// -// varsig_header: V::try_from(varsig_header.as_slice()) -// .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, -// -// signature: DID::Signature::try_from(sig.as_slice()) -// .map_err(|_| FromIpldError::CannotParseSignature)?, -// -// _phantom: std::marker::PhantomData, -// }) -// } else { -// Err(FromIpldError::InvalidPayloadCapsule) -// } -// } else { -// Err(FromIpldError::InvalidVarsigContainer) -// } -// } else { -// Err(FromIpldError::InvalidSignatureContainer) -// } -// } else { -// Err(FromIpldError::InvalidSignatureContainer) -// } -// } -// } - #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { #[error("Invalid signature container")] @@ -441,26 +212,6 @@ pub enum FromIpldError { #[error("Invalid payload capsule")] InvalidPayloadCapsule, } -// -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > Encode for Envelope -// where -// Self: Clone, -// Ipld: Encode + From, -// { -// fn encode( -// &self, -// codec: C, -// w: &mut W, -// ) -> Result<(), libipld_core::error::Error> { -// let ipld: Ipld = self.clone().into(); -// ipld.encode(codec, w) -// } -// } /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] @@ -485,38 +236,3 @@ pub enum ValidateError { #[error("Signature verification failed: {0}")] VerifyError(#[from] signature::Error), } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Serialize for Envelope -// { -// fn serialize(&self, serializer: S) -> std::prelude::v1::Result -// where -// S: Serializer, -// { -// self.clone().serialize(serializer) -// } -// } -// -// impl< -// 'de, -// T: Verifiable + Capsule + TryFrom, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Deserialize<'de> for Envelope -// where -// Envelope: TryFrom, -// as TryFrom>::Error: std::fmt::Display, -// { -// fn deserialize(deserializer: D) -> std::prelude::v1::Result -// where -// D: Deserializer<'de>, -// { -// let ipld: Ipld = Deserialize::deserialize(deserializer)?; -// Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) -// } -// } diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs index 6a88f49d..a91bfe29 100644 --- a/src/crypto/varsig/header/traits.rs +++ b/src/crypto/varsig/header/traits.rs @@ -2,7 +2,7 @@ use libipld_core::codec::{Codec, Encode}; use signature::Verifier; use thiserror::Error; -pub trait Header + Into>: +pub trait Header + Into>: for<'a> TryFrom<&'a [u8]> + Into> { type Signature: signature::SignatureEncoding; diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs index 6bc8584e..529f557c 100644 --- a/src/did/key/signer.rs +++ b/src/did/key/signer.rs @@ -1,5 +1,7 @@ -use enum_as_inner::EnumAsInner; use super::Signature; +use crate::did::Did; +use did_url::DID; +use enum_as_inner::EnumAsInner; #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -25,15 +27,13 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use crate::crypto::bls12381; - - /// Signature types that are verifiable by `did:key` [`Verifier`]s. #[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] pub enum Signer { /// `EdDSA` signature. #[cfg(feature = "eddsa")] EdDsa(ed25519_dalek::SigningKey), - + // FIXME // /// `ES256K` (`secp256k1`) signature. // #[cfg(feature = "es256k")] // Es256k(k256::ecdsa::Signer), From ff366c5edafa606d72a4be4ff495771720933e2b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 22:17:37 -0800 Subject: [PATCH 185/234] update for u64 everywhere --- src/ability/pipe.rs | 8 ++++---- src/ability/ucan/batch.rs | 4 ++-- src/crypto/varsig/encoding/preset.rs | 2 +- src/crypto/varsig/header/eddsa.rs | 12 ++++++------ src/crypto/varsig/header/es256.rs | 12 ++++++------ src/crypto/varsig/header/es256k.rs | 12 ++++++------ src/crypto/varsig/header/es512.rs | 12 ++++++------ src/crypto/varsig/header/rs256.rs | 14 +++++++------- src/crypto/varsig/header/rs512.rs | 12 ++++++------ src/delegation.rs | 14 +++++++------- src/delegation/agent.rs | 4 ++-- src/delegation/store/memory.rs | 6 +++--- src/delegation/store/traits.rs | 2 +- src/invocation.rs | 14 +++++++------- src/invocation/agent.rs | 6 +++--- src/invocation/store.rs | 6 +++--- src/receipt.rs | 12 ++++++------ src/receipt/store/memory.rs | 4 ++-- src/receipt/store/traits.rs | 2 +- 19 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 0dd41874..536fd55a 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,22 +1,22 @@ use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe, C: Codec + TryFrom + Into> { +pub struct Pipe, C: Codec + TryFrom + Into> { pub source: Cap, pub sink: Cap, } -pub enum Cap, C: Codec + TryFrom + Into> { +pub enum Cap, C: Codec + TryFrom + Into> { Proof(delegation::Proof), Literal(Ipld), } -pub struct PromisedPipe, C: Codec + TryFrom + Into> { +pub struct PromisedPipe, C: Codec + TryFrom + Into> { pub source: PromisedCap, pub sink: PromisedCap, } -pub enum PromisedCap, C: Codec + TryFrom + Into> { +pub enum PromisedCap, C: Codec + TryFrom + Into> { Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/ability/ucan/batch.rs b/src/ability/ucan/batch.rs index e068a5f4..9712fd93 100644 --- a/src/ability/ucan/batch.rs +++ b/src/ability/ucan/batch.rs @@ -3,11 +3,11 @@ // use std::collections::BTreeMap; // // #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -// pub struct Batch, Enc: Codec + TryFrom + Into> { +// pub struct Batch, Enc: Codec + TryFrom + Into> { // pub batch: Vec>, // FIXME not quite right; would be nice to include meta etc // } // -// pub struct Step, Enc: Codec + TryFrom + Into> { +// pub struct Step, Enc: Codec + TryFrom + Into> { // pub subject: DID, // pub audience: Option, // pub ability: A, // FIXME promise version instead? Promised version shoudl be able to promise any field diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index cf7cf8ea..a4189ba3 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -42,7 +42,7 @@ impl<'a> TryFrom<&'a [u8]> for Preset { type Error = (); fn try_from(bytes: &'a [u8]) -> Result { - if let (encoding_info, &[]) = unsigned_varint::decode::u32(&bytes).map_err(|_| ())? { + if let (encoding_info, &[]) = unsigned_varint::decode::u64(&bytes).map_err(|_| ())? { return match encoding_info { 0x5f => Ok(Preset::Identity), 0x70 => Ok(Preset::DagPb), diff --git a/src/crypto/varsig/header/eddsa.rs b/src/crypto/varsig/header/eddsa.rs index 0ccffd0a..5dd2009e 100644 --- a/src/crypto/varsig/header/eddsa.rs +++ b/src/crypto/varsig/header/eddsa.rs @@ -6,12 +6,12 @@ pub struct EdDsaHeader { pub codec: C, } -impl> TryFrom<&[u8]> for EdDsaHeader { +impl> TryFrom<&[u8]> for EdDsaHeader { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0xed, inner)) = unsigned_varint::decode::u8(&bytes) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&inner) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(EdDsaHeader { codec }); } @@ -21,19 +21,19 @@ impl> TryFrom<&[u8]> for EdDsaHeader { } } -impl + Clone> From> for Vec { +impl + Clone> From> for Vec { fn from(ed: EdDsaHeader) -> Vec { let mut tag_buf: [u8; 2] = Default::default(); let tag: &[u8] = unsigned_varint::encode::u8(0xed, &mut tag_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc: &[u8] = unsigned_varint::encode::u32(ed.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc: &[u8] = unsigned_varint::encode::u64(ed.codec.into(), &mut enc_buf); [tag, enc].concat().into() } } -impl + TryFrom> Header for EdDsaHeader { +impl + TryFrom> Header for EdDsaHeader { type Signature = ed25519_dalek::Signature; type Verifier = ed25519_dalek::VerifyingKey; diff --git a/src/crypto/varsig/header/es256.rs b/src/crypto/varsig/header/es256.rs index 901d863d..4e53a460 100644 --- a/src/crypto/varsig/header/es256.rs +++ b/src/crypto/varsig/header/es256.rs @@ -6,13 +6,13 @@ pub struct Es256Header { pub codec: C, } -impl> TryFrom<&[u8]> for Es256Header { +impl> TryFrom<&[u8]> for Es256Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1200, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es256Header { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es256Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1200, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es256Header { +impl + TryFrom> Header for Es256Header { type Signature = p256::ecdsa::Signature; type Verifier = p256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es256k.rs b/src/crypto/varsig/header/es256k.rs index 9b01c09f..465c4338 100644 --- a/src/crypto/varsig/header/es256k.rs +++ b/src/crypto/varsig/header/es256k.rs @@ -6,13 +6,13 @@ pub struct Es256kHeader { pub codec: C, } -impl> TryFrom<&[u8]> for Es256kHeader { +impl> TryFrom<&[u8]> for Es256kHeader { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0xe7, inner)) = unsigned_varint::decode::u8(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner).map_err(|_| ()) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es256kHeader { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es256kHeader { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es256kHeader) -> Vec { let mut tag_buf: [u8; 2] = Default::default(); let tag = unsigned_varint::encode::u8(0xe7, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es256kHeader { +impl + TryFrom> Header for Es256kHeader { type Signature = k256::ecdsa::Signature; type Verifier = k256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es512.rs b/src/crypto/varsig/header/es512.rs index 00e4b1a1..07089fac 100644 --- a/src/crypto/varsig/header/es512.rs +++ b/src/crypto/varsig/header/es512.rs @@ -6,13 +6,13 @@ pub struct Es512Header { pub codec: C, } -impl> TryFrom<&[u8]> for Es512Header { +impl> TryFrom<&[u8]> for Es512Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1202, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es512Header { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es512Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es512Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1202, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es512Header { +impl + TryFrom> Header for Es512Header { type Signature = p521::ecdsa::Signature; type Verifier = p521::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs index a175af8e..bc137ecb 100644 --- a/src/crypto/varsig/header/rs256.rs +++ b/src/crypto/varsig/header/rs256.rs @@ -8,13 +8,13 @@ pub struct Rs256Header { pub codec: C, } -impl> TryFrom<&[u8]> for Rs256Header { - type Error = ParseFromBytesError<>::Error>; +impl> TryFrom<&[u8]> for Rs256Header { + type Error = ParseFromBytesError<>::Error>; fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(ParseFromBytesError::CodecPrefixError)?; @@ -36,7 +36,7 @@ pub enum ParseFromBytesError { CodecPrefixError(#[from] C), } -impl> From> for Vec { +impl> From> for Vec { fn from(rs: Rs256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); @@ -44,14 +44,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(rs.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Rs256Header { +impl + TryFrom> Header for Rs256Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/crypto/varsig/header/rs512.rs b/src/crypto/varsig/header/rs512.rs index d1cb098a..7b9d161d 100644 --- a/src/crypto/varsig/header/rs512.rs +++ b/src/crypto/varsig/header/rs512.rs @@ -7,13 +7,13 @@ pub struct Rs512Header { pub codec: C, } -impl> TryFrom<&[u8]> for Rs512Header { +impl> TryFrom<&[u8]> for Rs512Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Rs512Header { codec }); } @@ -24,7 +24,7 @@ impl> TryFrom<&[u8]> for Rs512Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(rs: Rs512Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); @@ -32,14 +32,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(rs.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Rs512Header { +impl + TryFrom> Header for Rs512Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/delegation.rs b/src/delegation.rs index 3d43b6d8..67f873a0 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -41,7 +41,7 @@ use web_time::SystemTime; pub struct Delegation< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -53,18 +53,18 @@ pub struct Delegation< pub struct Proof< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub prf: Vec>>, } -impl, C: Codec + TryFrom + Into> Capsule +impl, C: Codec + TryFrom + Into> Capsule for Proof { const TAG: &'static str = "ucan/prf"; } -impl, C: Codec + Into + TryFrom> Delegation { +impl, C: Codec + Into + TryFrom> Delegation { pub fn new( varsig_header: V, signature: DID::Signature, @@ -123,7 +123,7 @@ impl, C: Codec + Into + TryFrom> Delega } } -impl + Clone, C: Codec + TryFrom + Into> Envelope +impl + Clone, C: Codec + TryFrom + Into> Envelope for Delegation where Payload: TryFrom, @@ -163,7 +163,7 @@ where } } -impl + Clone, C: Codec + TryFrom + Into> Serialize +impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation where Payload: TryFrom, @@ -176,7 +176,7 @@ where } } -impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> +impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> Deserialize<'de> for Delegation where Payload: TryFrom, diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 396a650c..abe27a34 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -22,7 +22,7 @@ pub struct Agent< DID: Did, S: Store, V: varsig::Header, - Enc: Codec + TryFrom + Into, + Enc: Codec + TryFrom + Into, > { /// The [`Did`][Did] of the agent. pub did: &'a DID, @@ -39,7 +39,7 @@ impl< DID: Did + ToString + Clone, S: Store + Clone, V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, + Enc: Codec + TryFrom + Into, > Agent<'a, DID, S, V, Enc> where Ipld: Encode, diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 0cafeea9..b603fb5c 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -69,13 +69,13 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, C: Codec + TryFrom + Into> { +pub struct MemoryStore, C: Codec + TryFrom + Into> { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } -impl, C: Codec + TryFrom + Into> Default +impl, C: Codec + TryFrom + Into> Default for MemoryStore { fn default() -> Self { @@ -88,7 +88,7 @@ impl, C: Codec + TryFrom + Into> } // FIXME check that UCAN is valid -impl, Enc: Codec + TryFrom + Into> +impl, Enc: Codec + TryFrom + Into> Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 1969e01f..58d08bbc 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,7 +8,7 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; diff --git a/src/invocation.rs b/src/invocation.rs index 823ca793..840b537a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -51,7 +51,7 @@ pub struct Invocation< A, DID: did::Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -59,7 +59,7 @@ pub struct Invocation< _marker: std::marker::PhantomData, } -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> Invocation where Ipld: Encode, @@ -117,7 +117,7 @@ where } } -impl, C: Codec + TryFrom + Into> did::Verifiable +impl, C: Codec + TryFrom + Into> did::Verifiable for Invocation { fn verifier(&self) -> &DID { @@ -129,7 +129,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > From> for Ipld where Payload: TryFrom, @@ -143,7 +143,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Envelope for Invocation where Payload: TryFrom, @@ -187,7 +187,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Serialize for Invocation where Payload: TryFrom, @@ -205,7 +205,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where Payload: TryFrom, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1ab29ac0..64a4e750 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< P: promise::Store, D: delegation::store::Store, V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + C: Codec + Into + TryFrom, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -66,7 +66,7 @@ where P: promise::Store, D: delegation::store::Store, V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + C: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -300,7 +300,7 @@ pub enum ReceiveError< D, S: Store, V: varsig::Header, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > where

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; + let _ = promised .validate_signature() .map_err(ReceiveError::SigVerifyError)?; @@ -253,9 +255,9 @@ where // FIXME return type ) -> Result, ()> where - Ipld: From, + Named: From, T: From, - Payload: TryFrom, + Payload: TryFrom>, { let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { @@ -301,14 +303,14 @@ pub enum Recipient { #[derive(Debug, Error)] pub enum ReceiveError< T: Resolvable, - P: promise::Store, + P: promise::Store, DID: Did, D, S: Store, V: varsig::Header, C: Codec + TryFrom + Into, > where -

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] @@ -322,8 +324,8 @@ pub enum ReceiveError< #[source] ::Promised, DID, V, C>>::InvocationStoreError, ), - #[error("promise store error: {0}")] - PromiseStoreError(#[source]

>::PromiseStoreError), + #[error("promise store error: {0:?}")] // FIXME + PromiseStoreError(

::PromiseStoreError), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -343,3 +345,47 @@ pub enum InvokeError { #[error("promise store error: {0}")] PromiseResolveError(#[source] ArgsErr), } + +#[cfg(test)] +mod tests { + use super::*; + use rand::thread_rng; + use testresult::TestResult; + + #[test_log::test] + fn test_agent_creation<'a>() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let agent: crate::invocation::agent::Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + assert!(false); + + // assert_eq!(agent.did, &did); + // assert_eq!(agent.invocation_store, &invocation_store); + // assert_eq!(agent.delegation_store, &delegation_store); + // assert_eq!(agent.unresolved_promise_index, &unresolved_promise_index); + + Ok(()) + } +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fa281e76..0969db05 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,4 +1,6 @@ use super::promise::Resolvable; +use crate::ability::command::Command; +use crate::invocation::Named; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, @@ -249,13 +251,19 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } -impl, DID: Did> From> for arguments::Named { +impl From> for arguments::Named +where + arguments::Named: From, +{ fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ ("iss".into(), payload.issuer.to_string().into()), ("sub".into(), payload.subject.to_string().into()), ("cmd".into(), payload.ability.to_command().into()), - ("args".into(), payload.ability.into()), + ( + "args".into(), + arguments::Named::::from(payload.ability).into(), + ), ( "prf".into(), Ipld::List(payload.proofs.iter().map(Into::into).collect()), @@ -264,7 +272,7 @@ impl, DID: Did> From> for arguments::N ]); if let Some(aud) = payload.audience { - args.insert("aud".into(), aud.into().to_string().into()); + args.insert("aud".into(), aud.to_string().into()); } if let Some(iat) = payload.issued_at { @@ -470,18 +478,108 @@ impl Verifiable for Payload { } } -use crate::ability::command::Command; - -impl + Command, DID: Did> TryFrom for Payload { +impl> + Command, DID: Did> + TryFrom> for Payload +where + >>::Error: fmt::Debug, +{ type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(btree) = ipld { - let payload_ipld = btree.get(A::COMMAND).ok_or(())?; - payload_ipld.clone().try_into().map_err(|_| ()) - } else { - Err(()) + fn try_from(named: arguments::Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut args = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut proofs = None; + let mut issued_at = None; + + for (k, v) in named { + match k.as_str() { + "sub" => { + subject = Some( + match v { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match v { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match v { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match v { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match v { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "args" => match v.try_into() { + Ok(a) => args = Some(a), + Err(_) => return Err(()), + }, + "meta" => match v { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match v { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), + _ => return Err(()), + }, + "exp" => match v { + Ipld::Integer(i) => expiration = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "iat" => match v { + Ipld::Integer(i) => issued_at = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "prf" => match v { + Ipld::List(xs) => { + proofs = Some( + xs.into_iter() + .map(|x| match x { + Ipld::Link(cid) => Ok(cid), + _ => Err(()), + }) + .collect::, ()>>() + .map_err(|_| ())?, + ) + } + _ => return Err(()), + }, + _ => return Err(()), + } } + + let cmd = command.ok_or(())?; + let some_args = args.ok_or(())?; + let ability = ::try_parse(cmd.as_str(), some_args).map_err(|_| ())?; + + Ok(Payload { + issuer: issuer.ok_or(())?, + subject: subject.ok_or(())?, + audience, + ability, + proofs: proofs.ok_or(())?, + cause: None, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + issued_at, + expiration, + }) } } @@ -490,14 +588,14 @@ impl + Command, DID: Did> TryFrom for Payload { /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld -where - Ipld: From, -{ - fn from(payload: Payload) -> Self { - arguments::Named::from(payload).into() - } -} +// impl From> for Ipld +// where +// Named: From, +// { +// fn from(payload: Payload) -> Self { +// arguments::Named::from(payload).into() +// } +// } #[cfg(feature = "test_utils")] impl Arbitrary for Payload diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 3a221e41..6f587f7a 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -6,12 +6,12 @@ use std::{ convert::Infallible, }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct MemoryStore { pub index: BTreeMap>, } -impl Store for MemoryStore { +impl Store for MemoryStore { type PromiseStoreError = Infallible; fn put_waiting( diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 02d80686..0e894c84 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -2,7 +2,7 @@ use crate::{did::Did, invocation::promise::Resolvable}; use libipld_core::cid::Cid; use std::collections::BTreeSet; -pub trait Store { +pub trait Store { type PromiseStoreError; fn put_waiting( diff --git a/src/invocation/store.rs b/src/invocation/store.rs index e7e3ad50..846a863d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,11 +1,18 @@ //! Storage for [`Invocation`]s. use super::Invocation; +use crate::ability; use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store, Enc: Codec + Into + TryFrom> { +pub trait Store< + T = crate::ability::preset::Preset, + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, +> +{ type InvocationStoreError; fn get( @@ -25,8 +32,23 @@ pub trait Store, Enc: Codec + Into + Tr } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, Enc: Codec + Into + TryFrom> { - store: BTreeMap>, +pub struct MemoryStore< + T = crate::ability::preset::PromisedPreset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>, +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + store: BTreeMap::new(), + } + } } impl, Enc: Codec + Into + TryFrom> diff --git a/src/receipt.rs b/src/receipt.rs index c96a7cf9..38fbf4dd 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,6 +14,7 @@ pub use payload::*; pub use responds::Responds; pub use store::Store; +use crate::ability::arguments; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -51,7 +52,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn from(rec: Receipt) -> Self { rec.to_ipld_envelope() @@ -65,7 +67,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -109,7 +112,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -127,8 +131,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Receipt where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Ipld: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4bc40a96..cd3d22ce 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -94,6 +94,47 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/r@1.0.0-rc.1"; } +impl From> for arguments::Named +where + Ipld: From, +{ + fn from(payload: Payload) -> Self { + let out_ipld = match payload.out { + Ok(ok) => BTreeMap::from_iter([("ok".to_string(), Ipld::from(ok))]).into(), + Err(err) => BTreeMap::from_iter([("err".to_string(), err.0.into())]).into(), + }; + + let mut args = arguments::Named::::from_iter([ + ("iss".to_string(), Ipld::String(payload.issuer.to_string())), + ("ran".to_string(), payload.ran.into()), + ("out".to_string(), out_ipld), + ( + "next".to_string(), + Ipld::List( + payload + .next + .clone() + .into_iter() + .map(|x| Ipld::Link(x)) + .collect(), + ), + ), + ( + "prf".to_string(), + Ipld::List(payload.next.into_iter().map(|x| Ipld::Link(x)).collect()), + ), + ("meta".to_string(), payload.metadata.into()), + ("nonce".to_string(), payload.nonce.into()), + ]); + + if let Some(issued_at) = payload.issued_at { + args.insert("iat".to_string(), issued_at.into()); + } + + args + } +} + impl Serialize for Payload where T::Success: Serialize, @@ -106,7 +147,7 @@ where let mut state = serializer.serialize_struct("receipt::Payload", field_count)?; - state.serialize_field("iss", &self.issuer.clone().into().as_str())?; + state.serialize_field("iss", &self.issuer.to_string().as_str())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index a378cf83..2e0f60c4 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -126,6 +126,30 @@ impl From for Ipld { } } +impl TryFrom for Timestamp { + type Error = (); + + fn try_from(ipld: Ipld) -> Result { + match ipld { + // FIXME do bounds checking + Ipld::Integer(secs) => Ok(Timestamp::new( + UNIX_EPOCH + Duration::from_secs(secs as u64), + ) + .map_err(|_| ())?), + _ => Err(()), + } + } +} + +impl TryFrom for Timestamp { + type Error = OutOfRangeError; + + fn try_from(secs: i128) -> Result { + // FIXME do bounds checking + Timestamp::new(UNIX_EPOCH + Duration::from_secs(secs as u64)) + } +} + impl Serialize for Timestamp { fn serialize(&self, serializer: S) -> Result where From fce6a9a3944394567042c36911f3e6d2e4079a53 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 14 Mar 2024 00:45:18 -0700 Subject: [PATCH 211/234] Introduced (correctly) failing test --- src/delegation/store/memory.rs | 72 ++---- src/invocation.rs | 21 +- src/invocation/agent.rs | 399 ++++++++++++++++----------------- src/invocation/payload.rs | 19 +- src/invocation/store.rs | 2 +- 5 files changed, 241 insertions(+), 272 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 5b958d5f..11a99d6d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -125,7 +125,6 @@ where cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) .or_default() @@ -135,15 +134,6 @@ where self.ucans.insert(cid.clone(), delegation); - dbg!(self.ucans.len()); - dbg!(self.index.len()); - for (sub, inner) in self.index.clone() { - dbg!(sub.clone().map(|x| x.to_string())); - for (aud, cids) in inner { - dbg!(aud.to_string()); - dbg!(cids.len()); - } - } Ok(()) } @@ -267,10 +257,13 @@ where #[cfg(test)] mod tests { - use crate::ability::arguments::Named; - use crate::ability::command::Command; - use crate::crypto::signature::Envelope; - use crate::delegation::store::Store; + use crate::{ + ability::{arguments::Named, command::Command}, + crypto::signature::Envelope, + delegation::store::Store, + invocation::promise::{CantResolve, Resolvable}, + ipld, + }; use libipld_core::ipld::Ipld; use rand::thread_rng; use testresult::TestResult; @@ -360,9 +353,9 @@ mod tests { #[derive(Debug, Clone, PartialEq)] pub struct AccountManage; - use crate::invocation::promise::CantResolve; - use crate::invocation::promise::Resolvable; - use crate::ipld; + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } impl From for Named { fn from(_: AccountManage) -> Self { @@ -408,21 +401,6 @@ mod tests { } } - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - // #[derive(Debug, Clone, PartialEq)] - // pub struct DnsLinkUpdate { - // pub cid: Cid, - // } - - // impl From for DnsLinkUpdate { - // fn from(_: Ipld) -> Self { - // todo!() - // } - // } - // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, @@ -458,7 +436,7 @@ mod tests { varsig::encoding::Preset, > = Default::default(); - let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); let _ = store.insert( account_device_ucan.cid().expect("FIXME"), @@ -466,22 +444,16 @@ mod tests { ); let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); - let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); use std::time::SystemTime; - dbg!(device.to_string().clone()); - dbg!(server.to_string().clone()); - dbg!(account.to_string().clone()); - dbg!(dnslink.to_string().clone()); - let chain_for_powerline = store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); let chain_for_dnslink = store.get_chain( &device, - &Some(dnslink), + &Some(dnslink.clone()), "/".into(), vec![], SystemTime::now(), @@ -496,30 +468,24 @@ mod tests { let mut inv_store = crate::invocation::store::MemoryStore::default(); let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); let mut agent: Agent< '_, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore, - crate::invocation::promise::store::MemoryStore, AccountManage, - > = Agent::new( - &server, - &server_signer, - &mut inv_store, - &mut del_store, - &mut prom_store, - ); + > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); + + let observed = agent.receive(account_invocation); - let observed = agent.receive(account_invocation, &SystemTime::now()); + dbg!(&observed); assert!(observed.is_ok()); let not_account_invocation = crate::Invocation::try_sign( &device_signer, varsig_header, crate::invocation::PayloadBuilder::default() - .subject(account.clone()) + .subject(dnslink.clone()) .issuer(server.clone()) .audience(Some(device.clone())) .ability(AccountManage) @@ -527,7 +493,9 @@ mod tests { .build()?, )?; - let observed_other = agent.receive(not_account_invocation, &SystemTime::now()); + dbg!(not_account_invocation.clone()); + + let observed_other = agent.receive(not_account_invocation); assert!(observed_other.is_err()); Ok(()) diff --git a/src/invocation.rs b/src/invocation.rs index 0d64902a..0c48387d 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -23,6 +23,7 @@ pub use payload::*; use crate::ability::arguments::Named; use crate::ability::command::ToCommand; +use crate::ability::parse::ParseAbility; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -62,7 +63,7 @@ pub struct Invocation< } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Clone + did::Did, V: Clone + varsig::Header, C: Codec + TryFrom + Into, @@ -71,14 +72,19 @@ where Ipld: Encode, Named: From + From>, Payload: TryFrom>, + // >>::Error: std::fmt::Debug, { fn encode(&self, c: C, w: &mut W) -> Result<(), libipld_core::error::Error> { self.to_ipld_envelope().encode(c, w) } } -impl, C: Codec + TryFrom + Into> - Invocation +impl< + A: Clone + ToCommand + ParseAbility, + DID: Did + Clone, + V: varsig::Header, + C: Codec + TryFrom + Into, + > Invocation where Ipld: Encode, { @@ -110,6 +116,7 @@ where pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, + Z: ParseAbility + ToCommand, { Invocation::new( self.varsig_header, @@ -144,7 +151,7 @@ impl, C: Codec + TryFrom + Into> did } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -159,7 +166,7 @@ where } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -204,7 +211,7 @@ where } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -223,7 +230,7 @@ where impl< 'de, - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index aefd5117..899d5f7b 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,11 +1,11 @@ use super::{ payload::{Payload, ValidationError}, - promise::Resolvable, store::Store, Invocation, }; use crate::ability::arguments::Named; use crate::ability::command::ToCommand; +use crate::ability::parse::ParseAbility; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -14,7 +14,6 @@ use crate::{ }, delegation, did::{self, Did}, - invocation::promise, time::Timestamp, }; use libipld_core::{ @@ -33,10 +32,9 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - S: Store, + S: Store, D: delegation::store::Store, - P: promise::Store, - T: Resolvable + ToCommand = ability::preset::Preset, + T: ToCommand = ability::preset::Preset, DID: Did = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, @@ -50,39 +48,35 @@ pub struct Agent< /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: &'a mut S, - /// A [`promise::Store`] for the agent's unresolved promises. - pub unresolved_promise_index: &'a mut P, - signer: &'a ::Signer, marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, D, P, T, DID, V, C> +impl<'a, T, DID, S, D, V, C> Agent<'a, S, D, T, DID, V, C> where Ipld: Encode, + T: ToCommand + Clone + ParseAbility, Named: From, + Payload: TryFrom>, delegation::Payload: Clone, - T: Resolvable + ToCommand + Clone, - T::Promised: Clone + ToCommand, DID: Did + Clone, - S: Store, + S: Store, D: delegation::store::Store, - P: promise::Store, V: varsig::Header + Clone, C: Codec + Into + TryFrom, + >::InvocationStoreError: fmt::Debug, + >::DelegationStoreError: fmt::Debug, { pub fn new( did: &'a DID, signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - unresolved_promise_index: &'a mut P, ) -> Self { Self { did, invocation_store, delegation_store, - unresolved_promise_index, signer, marker: PhantomData, } @@ -99,17 +93,7 @@ where issued_at: Option, now: SystemTime, varsig_header: V, - ) -> Result< - Invocation, - InvokeError< - D::DelegationStoreError, - ParseAbilityError<()>, // FIXME argserror - >, - > - where - Named: From, - Payload: TryFrom>, - { + ) -> Result, InvokeError> { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME @@ -136,95 +120,85 @@ where .map_err(InvokeError::SignError)?) } - pub fn invoke_promise( + // pub fn invoke_promise( + // &mut self, + // audience: Option<&DID>, + // subject: DID, + // ability: T::Promised, + // metadata: BTreeMap, + // cause: Option, + // expiration: Option, + // issued_at: Option, + // now: SystemTime, + // varsig_header: V, + // ) -> Result< + // Invocation, + // InvokeError< + // D::DelegationStoreError, + // ParseAbilityError<()>, // FIXME errs + // >, + // > + // where + // Named: From, + // Payload: TryFrom>, + // { + // let proofs = self + // .delegation_store + // .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) + // .map_err(InvokeError::DelegationStoreError)? + // .map(|chain| chain.map(|(cid, _)| cid).into()) + // .unwrap_or(vec![]); + + // let mut seed = vec![]; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject, + // audience: audience.cloned(), + // ability, + // proofs, + // metadata, + // nonce: Nonce::generate_12(&mut seed), + // cause, + // expiration, + // issued_at, + // }; + + // Ok(Invocation::try_sign(self.signer, varsig_header, payload) + // .map_err(InvokeError::SignError)?) + // } + + pub fn receive( &mut self, - audience: Option<&DID>, - subject: DID, - ability: T::Promised, - metadata: BTreeMap, - cause: Option, - expiration: Option, - issued_at: Option, - now: SystemTime, - varsig_header: V, - ) -> Result< - Invocation, - InvokeError< - D::DelegationStoreError, - ParseAbilityError<()>, // FIXME errs - >, - > + invocation: Invocation, + ) -> Result>, ReceiveError> where - Named: From, - Payload: TryFrom>, + arguments::Named: From, + Payload: TryFrom>, + Invocation: Clone + Encode, { - let proofs = self - .delegation_store - .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]); - - let mut seed = vec![]; - - let payload = Payload { - issuer: self.did.clone(), - subject, - audience: audience.cloned(), - ability, - proofs, - metadata, - nonce: Nonce::generate_12(&mut seed), - cause, - expiration, - issued_at, - }; - - Ok(Invocation::try_sign(self.signer, varsig_header, payload) - .map_err(InvokeError::SignError)?) + self.generic_receive(invocation, &SystemTime::now()) } - pub fn receive( + pub fn generic_receive( &mut self, - promised: Invocation, + invocation: Invocation, now: &SystemTime, - ) -> Result>, ReceiveError> + ) -> Result>, ReceiveError> where - arguments::Named: From + From, - Payload: TryFrom>, - Invocation: Clone + Encode, -

::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, + arguments::Named: From, + Payload: TryFrom>, + Invocation: Clone + Encode, { - let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; - - let _ = promised - .validate_signature() - .map_err(ReceiveError::SigVerifyError)?; + let cid: Cid = invocation.cid().map_err(ReceiveError::EncodingError)?; self.invocation_store - .put(cid.clone(), promised.clone()) + .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; - let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { - Ok(resolved) => resolved, - Err(cant_resolve) => { - let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); - - self.unresolved_promise_index - .put_waiting( - promised.cid()?, - waiting_on.into_iter().collect::>(), - ) - .map_err(ReceiveError::PromiseStoreError)?; - - return Ok(Recipient::Unresolved(cid)); - } - }; - let proof_payloads: Vec<&delegation::Payload> = self .delegation_store - .get_many(&promised.proofs()) + .get_many(&invocation.proofs()) .map_err(ReceiveError::DelegationStoreError)? .iter() .fold(vec![], |mut acc, d| { @@ -232,64 +206,63 @@ where acc }); - let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); - - let _ = &resolved_payload + &invocation + .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - if promised.audience() != &Some(self.did.clone()) { - return Ok(Recipient::Other(resolved_payload)); - } - - Ok(Recipient::You(resolved_payload)) - } - - pub fn revoke( - &mut self, - subject: DID, - cause: Option, - cid: Cid, - now: Timestamp, - varsig_header: V, - // FIXME return type - ) -> Result, ()> - where - Named: From, - T: From, - Payload: TryFrom>, - { - let ability: T = Revoke { ucan: cid.clone() }.into(); - let proofs = if &subject == self.did { - vec![] + Ok(if *invocation.audience() != Some(self.did.clone()) { + Recipient::Other(invocation.payload) } else { - todo!("update to latest trait interface"); // FIXME - // self.delegation_store - // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) - // .map_err(|_| ())? - // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) - // .unwrap_or(vec![]) - }; - - let payload = Payload { - issuer: self.did.clone(), - subject: self.did.clone(), - audience: Some(self.did.clone()), - ability, - proofs, - cause, - metadata: BTreeMap::new(), - nonce: Nonce::generate_12(&mut vec![]), - expiration: None, - issued_at: None, - }; - - let invocation = - Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; - - self.delegation_store.revoke(cid).map_err(|_| ())?; - Ok(invocation) + Recipient::You(invocation.payload) + }) } + + // pub fn revoke( + // &mut self, + // subject: DID, + // cause: Option, + // cid: Cid, + // now: Timestamp, + // varsig_header: V, + // // FIXME return type + // ) -> Result, ()> + // where + // Named: From, + // T: From, + // Payload: TryFrom>, + // { + // let ability: T = Revoke { ucan: cid.clone() }.into(); + // let proofs = if &subject == self.did { + // vec![] + // } else { + // todo!("update to latest trait interface"); // FIXME + // // self.delegation_store + // // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) + // // .map_err(|_| ())? + // // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + // // .unwrap_or(vec![]) + // }; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject: self.did.clone(), + // audience: Some(self.did.clone()), + // ability, + // proofs, + // cause, + // metadata: BTreeMap::new(), + // nonce: Nonce::generate_12(&mut vec![]), + // expiration: None, + // issued_at: None, + // }; + + // let invocation = + // Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; + + // self.delegation_store.revoke(cid).map_err(|_| ())?; + // Ok(invocation) + // } } #[derive(Debug)] @@ -302,16 +275,14 @@ pub enum Recipient { #[derive(Debug, Error)] pub enum ReceiveError< - T: Resolvable, - P: promise::Store, + T, DID: Did, D, - S: Store, + S: Store, V: varsig::Header, C: Codec + TryFrom + Into, > where -

::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, + >::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -320,12 +291,7 @@ pub enum ReceiveError< SigVerifyError(#[from] signature::ValidateError), #[error("invocation store error: {0}")] - InvocationStoreError( - #[source] ::Promised, DID, V, C>>::InvocationStoreError, - ), - - #[error("promise store error: {0:?}")] // FIXME - PromiseStoreError(

::PromiseStoreError), + InvocationStoreError(#[source] >::InvocationStoreError), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -335,15 +301,12 @@ pub enum ReceiveError< } #[derive(Debug, Error)] -pub enum InvokeError { +pub enum InvokeError { #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), - #[error("promise store error: {0}")] + #[error("store error: {0}")] SignError(#[source] signature::SignError), - - #[error("promise store error: {0}")] - PromiseResolveError(#[source] ArgsErr), } #[cfg(test)] @@ -352,40 +315,66 @@ mod tests { use rand::thread_rng; use testresult::TestResult; - #[test_log::test] - fn test_agent_creation<'a>() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); - - let agent: crate::invocation::agent::Agent< - '_, - crate::invocation::store::MemoryStore, - crate::delegation::store::MemoryStore, - crate::invocation::promise::store::MemoryStore, - > = Agent::new( - &server, - &server_signer, - &mut inv_store, - &mut del_store, - &mut prom_store, - ); - - assert!(false); - - // assert_eq!(agent.did, &did); - // assert_eq!(agent.invocation_store, &invocation_store); - // assert_eq!(agent.delegation_store, &delegation_store); - // assert_eq!(agent.unresolved_promise_index, &unresolved_promise_index); - - Ok(()) - } + // fn setup<'a>( + // inv_store: &'a mut crate::invocation::store::MemoryStore, + // del_store: &'a mut crate::delegation::store::MemoryStore, + // ) -> ( + // crate::did::preset::Verifier, + // crate::did::preset::Signer, + // Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore>, + // ) { + // let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + // let server_signer = + // crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + // let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + // server_sk.verifying_key(), + // )); + + // let agent = + // crate::invocation::agent::Agent::new(&server, &server_signer, inv_store, del_store); + + // (server, server_signer, agent) + // } + + // mod receive { + // use super::*; + // use crate::ability::crud::{read::Read, Crud}; + // use crate::ability::preset::Preset; + // use crate::crypto::varsig; + + // #[test_log::test] + // fn test_happy_path() -> TestResult { + // let mut inv_store = crate::invocation::store::MemoryStore::default(); + // let mut del_store = crate::delegation::store::MemoryStore::default(); + + // let (_, _, mut agent) = setup(&mut inv_store, &mut del_store); + // let invocation = agent.invoke( + // None, + // agent.did.clone(), + // // FIXME flatten + // Preset::Crud(Crud::Read(Read { + // path: None, + // args: None, + // })), + // BTreeMap::new(), + // None, + // None, + // None, + // SystemTime::now(), + // varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + // codec: varsig::encoding::Preset::DagCbor, + // }), + // )?; + + // let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + // let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + // unknown_sk.verifying_key(), + // )); + + // agent.receive(invocation)?; + + // Ok(()) + // } + // } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0969db05..5932b0e4 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -163,7 +163,7 @@ impl Payload { cmd.push('/'); } - let (_, vias) = proofs.into_iter().try_fold( + let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { if *iss != proof.audience { @@ -211,8 +211,14 @@ impl Payload { }, )?; + if self.subject != *final_iss { + return Err(ValidationError::DidNotTerminateInSubject); + } + if !vias.is_empty() { - todo!() + return Err(ValidationError::UnfulfilledViaConstraint( + vias.into_iter().cloned().collect(), + )); } Ok(()) @@ -245,6 +251,9 @@ pub enum ValidationError { #[error("via field constraint was unfulfilled: {0:?}")] UnfulfilledViaConstraint(BTreeSet), + + #[error("The chain did not terminate in the expected subject")] + DidNotTerminateInSubject, } impl Capsule for Payload { @@ -478,11 +487,7 @@ impl Verifiable for Payload { } } -impl> + Command, DID: Did> - TryFrom> for Payload -where - >>::Error: fmt::Debug, -{ +impl TryFrom> for Payload { type Error = (); // FIXME fn try_from(named: arguments::Named) -> Result { diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 846a863d..a9e76b13 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -33,7 +33,7 @@ pub trait Store< #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - T = crate::ability::preset::PromisedPreset, + T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, From aa41773bcb8625898bb63614b1298dd9c46a3e91 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 14 Mar 2024 19:50:32 -0700 Subject: [PATCH 212/234] Evidently proof search is borked --- src/delegation/store/memory.rs | 209 +++++++++++++++++---------------- src/delegation/store/traits.rs | 45 +++++-- src/did/preset.rs | 2 +- src/invocation.rs | 4 +- src/invocation/agent.rs | 143 +++++++++++----------- src/invocation/payload.rs | 8 +- src/invocation/store.rs | 39 ++++-- 7 files changed, 249 insertions(+), 201 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 11a99d6d..f4c1937d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,10 +1,13 @@ use super::Store; use crate::ability::arguments::Named; +use crate::delegation; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, did::{self, Did}, }; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::collections::{BTreeMap, BTreeSet}; @@ -82,6 +85,10 @@ impl MemoryStore { Self::default() } + pub fn len(&self) -> usize { + self.ucans.len() + } + pub fn is_empty(&self) -> bool { self.ucans.is_empty() // FIXME acocunt for revocations? } @@ -99,10 +106,6 @@ impl, C: Codec + TryFrom + Into> } } -use crate::delegation; -use libipld_core::codec::Encode; -use libipld_core::ipld::Ipld; - // FIXME check that UCAN is valid impl< DID: Did + Ord + Clone, @@ -117,7 +120,12 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or("nope".into()) // FIXME + dbg!("@@@@@@@@@@@@@@@"); + dbg!(cid); + self.ucans + .get(cid) + .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) + // FIXME } fn insert( @@ -264,10 +272,66 @@ mod tests { invocation::promise::{CantResolve, Resolvable}, ipld, }; - use libipld_core::ipld::Ipld; + use crate::{ + crypto::varsig, + invocation::{payload::ValidationError, Agent}, + }; + use libipld_core::{cid::Cid, ipld::Ipld}; use rand::thread_rng; + use std::time::SystemTime; use testresult::TestResult; + #[derive(Debug, Clone, PartialEq)] + pub struct AccountManage; + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + #[test_log::test] fn test_powerbox_ucan_resource() -> TestResult { let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); @@ -306,6 +370,9 @@ mod tests { }, ); + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + // 1. account -*-> server // 2. server -a-> device // 3. dnslink -d-> account @@ -350,70 +417,6 @@ mod tests { .build()?, )?; - #[derive(Debug, Clone, PartialEq)] - pub struct AccountManage; - - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl Resolvable for AccountManage { - type Promised = AccountManage; - - fn try_resolve(promised: Self::Promised) -> Result> { - Ok(promised) - } - } - - // 4. [dnslink -d-> account -*-> server -a-> device] - let account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header.clone(), - crate::invocation::PayloadBuilder::default() - .subject(account.clone()) - .issuer(device.clone()) - .audience(Some(server.clone())) - .ability(AccountManage) - .proofs(vec![]) // FIXME - .build()?, - )?; - // FIXME reenable // let dnslink_invocation = crate::Invocation::try_sign( // &device, @@ -428,30 +431,26 @@ mod tests { // ) // .expect("FIXME"); - use crate::crypto::varsig; - - let mut store: crate::delegation::store::MemoryStore< - crate::did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, - > = Default::default(); + // let mut store: crate::delegation::store::MemoryStore< + // crate::did::preset::Verifier, + // varsig::header::Preset, + // varsig::encoding::Preset, + // > = Default::default(); // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); - let _ = store.insert( - account_device_ucan.cid().expect("FIXME"), - account_device_ucan.clone(), - ); - - let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); - let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); + drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); + drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); + drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); - use std::time::SystemTime; + let proofs_for_powerline: Vec = del_store + .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + .ok_or("FIXME")? + .iter() + .map(|x| x.0.clone()) + .collect(); - let chain_for_powerline = - store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); - - let chain_for_dnslink = store.get_chain( + let chain_for_dnslink = del_store.get_chain( &device, &Some(dnslink.clone()), "/".into(), @@ -459,25 +458,32 @@ mod tests { SystemTime::now(), ); - use crate::invocation::Agent; + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header.clone(), + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountManage) + .proofs(proofs_for_powerline.clone()) + .build()?, + )?; - let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); + let powerline_len = proofs_for_powerline.len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut agent: Agent< '_, - crate::invocation::store::MemoryStore, - crate::delegation::store::MemoryStore, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, AccountManage, > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); - let observed = agent.receive(account_invocation); - + let observed = agent.receive(account_invocation.clone()); dbg!(&observed); assert!(observed.is_ok()); @@ -493,10 +499,11 @@ mod tests { .build()?, )?; - dbg!(not_account_invocation.clone()); - let observed_other = agent.receive(not_account_invocation); - assert!(observed_other.is_err()); + assert_eq!( + observed_other.unwrap_err().as_validation_error(), + Some(&ValidationError::DidNotTerminateInSubject) + ); Ok(()) } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c6e91443..76c8f85a 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,14 +8,7 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store< - - - - DID: Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, - > { +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; @@ -57,9 +50,41 @@ pub trait Store< cids: &[Cid], ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; - acc.push(d); + acc.push(self.get(cid)?); Ok(acc) }) } } + +impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> + Store for &mut T +{ + type DelegationStoreError = >::DelegationStoreError; + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + (**self).get(cid) + } + + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError> { + (**self).insert(cid, delegation) + } + + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + (**self).revoke(cid) + } + + fn get_chain( + &self, + audience: &DID, + subject: &Option, + command: String, + policy: Vec, + now: SystemTime, + ) -> Result)>>, Self::DelegationStoreError> { + (**self).get_chain(audience, subject, command, policy, now) + } +} diff --git a/src/did/preset.rs b/src/did/preset.rs index cdc52528..0b590412 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -31,7 +31,7 @@ pub enum Signer { impl Did for Verifier { type Signature = key::Signature; - type Signer = Signer; + type Signer = self::Signer; } impl TryFrom for Verifier { diff --git a/src/invocation.rs b/src/invocation.rs index 0c48387d..d610fbe3 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -12,8 +12,8 @@ //! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. //! - [`store`] is an interface for caching [`Invocation`]s. -mod agent; -mod payload; +pub mod agent; +pub mod payload; pub mod promise; pub mod store; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 899d5f7b..825c7a4f 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -16,6 +16,7 @@ use crate::{ did::{self, Did}, time::Timestamp, }; +use enum_as_inner::EnumAsInner; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, @@ -43,10 +44,10 @@ pub struct Agent< pub did: &'a DID, /// A [`delegation::Store`][delegation::store::Store]. - pub delegation_store: &'a mut D, + pub delegation_store: D, /// A [`Store`][Store] for the agent's [`Invocation`]s. - pub invocation_store: &'a mut S, + pub invocation_store: S, signer: &'a ::Signer, marker: PhantomData<(T, V, C)>, @@ -70,8 +71,8 @@ where pub fn new( did: &'a DID, signer: &'a ::Signer, - invocation_store: &'a mut S, - delegation_store: &'a mut D, + invocation_store: S, + delegation_store: D, ) -> Self { Self { did, @@ -96,7 +97,7 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -116,7 +117,7 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, varsig_header, payload) + Ok(Invocation::try_sign(&self.signer, varsig_header, payload) .map_err(InvokeError::SignError)?) } @@ -201,12 +202,10 @@ where .get_many(&invocation.proofs()) .map_err(ReceiveError::DelegationStoreError)? .iter() - .fold(vec![], |mut acc, d| { - acc.push(&d.payload); - acc - }); + .map(|d| &d.payload) + .collect(); - &invocation + let _ = &invocation .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; @@ -265,7 +264,7 @@ where // } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, EnumAsInner)] pub enum Recipient { // FIXME change to status? You(T), @@ -273,7 +272,7 @@ pub enum Recipient { Unresolved(Cid), } -#[derive(Debug, Error)] +#[derive(Debug, Error, EnumAsInner)] pub enum ReceiveError< T, DID: Did, @@ -315,66 +314,60 @@ mod tests { use rand::thread_rng; use testresult::TestResult; - // fn setup<'a>( - // inv_store: &'a mut crate::invocation::store::MemoryStore, - // del_store: &'a mut crate::delegation::store::MemoryStore, - // ) -> ( - // crate::did::preset::Verifier, - // crate::did::preset::Signer, - // Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore>, - // ) { - // let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - // let server_signer = - // crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - // let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - // server_sk.verifying_key(), - // )); - - // let agent = - // crate::invocation::agent::Agent::new(&server, &server_signer, inv_store, del_store); - - // (server, server_signer, agent) - // } - - // mod receive { - // use super::*; - // use crate::ability::crud::{read::Read, Crud}; - // use crate::ability::preset::Preset; - // use crate::crypto::varsig; - - // #[test_log::test] - // fn test_happy_path() -> TestResult { - // let mut inv_store = crate::invocation::store::MemoryStore::default(); - // let mut del_store = crate::delegation::store::MemoryStore::default(); - - // let (_, _, mut agent) = setup(&mut inv_store, &mut del_store); - // let invocation = agent.invoke( - // None, - // agent.did.clone(), - // // FIXME flatten - // Preset::Crud(Crud::Read(Read { - // path: None, - // args: None, - // })), - // BTreeMap::new(), - // None, - // None, - // None, - // SystemTime::now(), - // varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - // codec: varsig::encoding::Preset::DagCbor, - // }), - // )?; - - // let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - // let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - // unknown_sk.verifying_key(), - // )); - - // agent.receive(invocation)?; - - // Ok(()) - // } - // } + fn setup_agent<'a>( + did: &'a crate::did::preset::Verifier, + signer: &'a crate::did::preset::Signer, + ) -> Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore> + { + let inv_store = crate::invocation::store::MemoryStore::default(); + let del_store = crate::delegation::store::MemoryStore::default(); + + crate::invocation::agent::Agent::new(did, signer, inv_store, del_store) + } + + mod receive { + use super::*; + use crate::ability::crud::{read::Read, Crud}; + use crate::ability::preset::Preset; + use crate::crypto::varsig; + + #[test_log::test] + fn test_happy_path() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let mut agent = setup_agent(&server, &server_signer); + let invocation = agent.invoke( + None, + agent.did.clone(), + // FIXME flatten + Preset::Crud(Crud::Read(Read { + path: None, + args: None, + })), + BTreeMap::new(), + None, + None, + None, + SystemTime::now(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + unknown_sk.verifying_key(), + )); + + agent.receive(invocation)?; + + Ok(()) + } + } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5932b0e4..1c649909 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -166,17 +166,19 @@ impl Payload { let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { + dbg!("$$$$$$$$$$$$$$"); + dbg!(proof.audience.to_string(), iss.to_string()); if *iss != proof.audience { - return Err(ValidationError::InvalidSubject.into()); + return Err(ValidationError::MisalignedIssAud.into()); } if let Some(proof_subject) = &proof.subject { if self.subject != *proof_subject { - return Err(ValidationError::MisalignedIssAud.into()); + return Err(ValidationError::InvalidSubject.into()); } } - if SystemTime::from(proof.expiration.clone()) > *now { + if SystemTime::from(proof.expiration.clone()) < *now { return Err(ValidationError::Expired.into()); } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index a9e76b13..b7acbd36 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -6,24 +6,18 @@ use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store< - T = crate::ability::preset::Preset, - DID: Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, -> -{ +pub trait Store, C: Codec + Into + TryFrom> { type InvocationStoreError; fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError>; + ) -> Result>, Self::InvocationStoreError>; fn put( &mut self, cid: Cid, - invocation: Invocation, + invocation: Invocation, ) -> Result<(), Self::InvocationStoreError>; fn has(&self, cid: Cid) -> Result { @@ -31,6 +25,33 @@ pub trait Store< } } +impl< + S: Store, + T, + DID: Did, + V: varsig::Header, + C: Codec + Into + TryFrom, + > Store for &mut S +{ + type InvocationStoreError = >::InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result>, >::InvocationStoreError> + { + (*self).get(cid) + } + + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), >::InvocationStoreError> { + (*self).put(cid, invocation) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< T = crate::ability::preset::Preset, From 42358bb35e79d5f8c82a53de1bb8e69f7bbf12d9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 13:04:11 -0700 Subject: [PATCH 213/234] Fix test --- src/delegation/store/memory.rs | 12 +++++++----- src/invocation/payload.rs | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index f4c1937d..7bab0c6e 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -120,8 +120,6 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - dbg!("@@@@@@@@@@@@@@@"); - dbg!(cid); self.ucans .get(cid) .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) @@ -463,11 +461,16 @@ mod tests { &device_signer, varsig_header.clone(), crate::invocation::PayloadBuilder::default() - .subject(account.clone()) + .subject(dnslink.clone()) .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(proofs_for_powerline.clone()) + .proofs(vec![ + account_device_ucan.cid()?, + account_pbox.cid()?, + dnslink_ucan.cid()?, + ]) + // .proofs(proofs_for_powerline.clone()) .build()?, )?; @@ -484,7 +487,6 @@ mod tests { > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); let observed = agent.receive(account_invocation.clone()); - dbg!(&observed); assert!(observed.is_ok()); let not_account_invocation = crate::Invocation::try_sign( diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 1c649909..b1256fc5 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -166,8 +166,6 @@ impl Payload { let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { - dbg!("$$$$$$$$$$$$$$"); - dbg!(proof.audience.to_string(), iss.to_string()); if *iss != proof.audience { return Err(ValidationError::MisalignedIssAud.into()); } From fa90a81f4e500e6795c26e6bb81411736ccb0dd8 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 14:16:56 -0700 Subject: [PATCH 214/234] Plugging little edge cases in invocation agent flow --- src/ability/crud.rs | 24 +++++ src/ability/preset.rs | 9 ++ src/delegation/store/memory.rs | 5 +- src/delegation/store/traits.rs | 12 +++ src/invocation.rs | 8 ++ src/invocation/agent.rs | 154 ++++++++++++++++++++++++++++----- src/invocation/payload.rs | 6 ++ 7 files changed, 193 insertions(+), 25 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index e43a47a8..7a47a611 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -81,6 +81,30 @@ impl From for arguments::Named { } } +impl From for Crud { + fn from(create: Create) -> Self { + Crud::Create(create) + } +} + +impl From for Crud { + fn from(read: Read) -> Self { + Crud::Read(read) + } +} + +impl From for Crud { + fn from(update: Update) -> Self { + Crud::Update(update) + } +} + +impl From for Crud { + fn from(destroy: Destroy) -> Self { + Crud::Destroy(destroy) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedCrud { Create(PromisedCreate), diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 44bd43cf..8adec291 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -25,6 +25,15 @@ pub enum Preset { Wasm(wasm::Run), } +impl From for Preset +where + Crud: From, +{ + fn from(t: T) -> Self { + Preset::Crud(Crud::from(t)) + } +} + impl ToCommand for Preset { fn to_command(&self) -> String { match self { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 7bab0c6e..4638001d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -263,6 +263,7 @@ where #[cfg(test)] mod tests { + use crate::invocation::{payload::ValidationError, Agent}; use crate::{ ability::{arguments::Named, command::Command}, crypto::signature::Envelope, @@ -270,10 +271,6 @@ mod tests { invocation::promise::{CantResolve, Resolvable}, ipld, }; - use crate::{ - crypto::varsig, - invocation::{payload::ValidationError, Agent}, - }; use libipld_core::{cid::Cid, ipld::Ipld}; use rand::thread_rng; use std::time::SystemTime; diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 76c8f85a..3bd7b1fb 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -33,6 +33,18 @@ pub trait Store, Enc: Codec + TryFrom + In now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; + fn get_chain_cids( + &self, + audience: &DID, + subject: &Option, + command: String, + policy: Vec, + now: SystemTime, + ) -> Result>, Self::DelegationStoreError> { + self.get_chain(audience, subject, command, policy, now) + .map(|chain| chain.map(|chain| chain.map(|(cid, _)| cid))) + } + fn can_delegate( &self, issuer: DID, diff --git a/src/invocation.rs b/src/invocation.rs index d610fbe3..1fa3a899 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -101,6 +101,14 @@ where &self.payload.audience } + pub fn normalized_audience(&self) -> &DID { + if let Some(audience) = &self.payload.audience { + audience + } else { + &self.payload.subject + } + } + pub fn issuer(&self) -> &DID { &self.payload.issuer } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 825c7a4f..ca146080 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,6 +6,7 @@ use super::{ use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::ability::parse::ParseAbility; +use crate::invocation::payload::PayloadBuilder; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -210,7 +211,7 @@ where .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - Ok(if *invocation.audience() != Some(self.did.clone()) { + Ok(if invocation.normalized_audience() != self.did { Recipient::Other(invocation.payload) } else { Recipient::You(invocation.payload) @@ -311,9 +312,22 @@ pub enum InvokeError { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use rand::thread_rng; + use std::ops::Add; + use std::ops::Sub; + use std::time::Duration; use testresult::TestResult; + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + fn setup_agent<'a>( did: &'a crate::did::preset::Verifier, signer: &'a crate::did::preset::Signer, @@ -325,48 +339,146 @@ mod tests { crate::invocation::agent::Agent::new(did, signer, inv_store, del_store) } + fn setup_valid_time() -> (Timestamp, Timestamp, Timestamp) { + let now = SystemTime::UNIX_EPOCH.add(Duration::from_secs(60 * 60 * 24 * 30)); + let exp = now.add(std::time::Duration::from_secs(60)); + let nbf = now.sub(std::time::Duration::from_secs(60)); + + ( + nbf.try_into().expect("valid nbf time"), + now.try_into().expect("valid now time"), + exp.try_into().expect("valid exp time"), + ) + } + mod receive { use super::*; - use crate::ability::crud::{read::Read, Crud}; - use crate::ability::preset::Preset; + use crate::ability::crud::read::Read; use crate::crypto::varsig; #[test_log::test] - fn test_happy_path() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - + fn test_invoker_is_sub_implicit_aud() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); let mut agent = setup_agent(&server, &server_signer); + let invocation = agent.invoke( None, agent.did.clone(), - // FIXME flatten - Preset::Crud(Crud::Read(Read { + Read { path: None, args: None, - })), + } + .into(), BTreeMap::new(), None, + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) + } + + #[test_log::test] + fn test_invoker_is_sub_and_aud() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let invocation = agent.invoke( + Some(agent.did.clone()), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), None, + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) + } + + #[test_log::test] + fn test_other_recipient() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let (not_server, _) = gen_did(); + + let invocation = agent.invoke( + Some(not_server), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), None, - SystemTime::now(), + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { codec: varsig::encoding::Preset::DagCbor, }), )?; - let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - unknown_sk.verifying_key(), - )); + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::Other(invocation.payload)); + Ok(()) + } - agent.receive(invocation)?; + #[test_log::test] + fn test_expired() -> TestResult { + let (past, now, _exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let (not_server, _) = gen_did(); + + let invocation = agent.invoke( + Some(not_server), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), + None, + Some(past.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + let observed = agent.generic_receive(invocation.clone(), &now.into()); + pretty::assert_eq!( + observed + .unwrap_err() + .as_validation_error() + .ok_or("not a validation error")?, + &ValidationError::Expired + ); Ok(()) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b1256fc5..ed0ad884 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -156,6 +156,12 @@ impl Payload { DID: Clone, arguments::Named: From, { + if let Some(ref exp) = self.expiration { + if SystemTime::from(exp.clone()) < *now { + return Err(ValidationError::Expired); + } + } + let args: arguments::Named = self.ability.clone().into(); let mut cmd = self.ability.to_command(); From 08f72df353e1848a100ee766a8a24916a5eba18f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 17:40:59 -0700 Subject: [PATCH 215/234] Manual test context --- Cargo.toml | 1 + src/crypto/varsig/header/preset.rs | 30 +++ src/delegation/store/memory.rs | 247 -------------------- src/invocation/agent.rs | 356 +++++++++++++++++++++++++++-- src/invocation/payload.rs | 12 +- 5 files changed, 375 insertions(+), 271 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1415bdec..db8883df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ wasm-bindgen-derive = "0.2" web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] +assert_matches = "1.5" libipld = "0.16" pretty_assertions = "1.4" rand = "0.8" diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index e658a285..00bab1d8 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -13,6 +13,36 @@ pub enum Preset { // FIXME Es384 needs varsig specs } +impl From> for Preset { + fn from(ed: eddsa::EdDsaHeader) -> Self { + Preset::EdDsa(ed) + } +} + +impl From> for Preset { + fn from(rs256: rs256::Rs256Header) -> Self { + Preset::Rs256(rs256) + } +} + +impl From> for Preset { + fn from(rs512: rs512::Rs512Header) -> Self { + Preset::Rs512(rs512) + } +} + +impl From> for Preset { + fn from(es256: es256::Es256Header) -> Self { + Preset::Es256(es256) + } +} + +impl From> for Preset { + fn from(es256k: es256k::Es256kHeader) -> Self { + Preset::Es256k(es256k) + } +} + impl From for Vec { fn from(preset: Preset) -> Vec { match preset { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 4638001d..18d86103 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -260,250 +260,3 @@ where Ok(NonEmpty::from_vec(hypothesis_chain)) } } - -#[cfg(test)] -mod tests { - use crate::invocation::{payload::ValidationError, Agent}; - use crate::{ - ability::{arguments::Named, command::Command}, - crypto::signature::Envelope, - delegation::store::Store, - invocation::promise::{CantResolve, Resolvable}, - ipld, - }; - use libipld_core::{cid::Cid, ipld::Ipld}; - use rand::thread_rng; - use std::time::SystemTime; - use testresult::TestResult; - - #[derive(Debug, Clone, PartialEq)] - pub struct AccountManage; - - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl Resolvable for AccountManage { - type Promised = AccountManage; - - fn try_resolve(promised: Self::Promised) -> Result> { - Ok(promised) - } - } - - #[test_log::test] - fn test_powerbox_ucan_resource() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - - let account_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let account = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - account_sk.verifying_key(), - )); - let account_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(account_sk)); - - let dnslink_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let dnslink = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - dnslink_sk.verifying_key(), - )); - let dnslink_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(dnslink_sk)); - - let device_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let device = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - device_sk.verifying_key(), - )); - let device_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(device_sk)); - - // FIXME perhaps add this back upstream as a named const - let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( - crate::crypto::varsig::header::EdDsaHeader { - codec: crate::crypto::varsig::encoding::Preset::DagCbor, - }, - ); - - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - - // 1. account -*-> server - // 2. server -a-> device - // 3. dnslink -d-> account - // 4. [dnslink -d-> account -*-> server -a-> device] - - // 1. account -*-> server - let account_pbox = crate::Delegation::try_sign( - &account_signer, - varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(account.clone()) - .audience(server.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, - )?; - - // 2. server -a-> device - let account_device_ucan = crate::Delegation::try_sign( - &server_signer, - varsig_header.clone(), // FIXME can also put this on a builder - crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox - .issuer(server.clone()) - .audience(device.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, // I don't love this is now failable - )?; - - // 3. dnslink -d-> account - let dnslink_ucan = crate::Delegation::try_sign( - &dnslink_signer, - varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(dnslink.clone())) - .issuer(dnslink.clone()) - .audience(account.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, - )?; - - // FIXME reenable - // let dnslink_invocation = crate::Invocation::try_sign( - // &device, - // varsig_header, - // crate::invocation::PayloadBuilder::default() - // .subject(dnslink) - // .issuer(device) - // .audience(Some(server)) - // .ability(DnsLinkUpdate { cid: todo!() }) - // .build() - // .expect("FIXME"), - // ) - // .expect("FIXME"); - - // let mut store: crate::delegation::store::MemoryStore< - // crate::did::preset::Verifier, - // varsig::header::Preset, - // varsig::encoding::Preset, - // > = Default::default(); - - // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); - - drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); - drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); - drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); - - let proofs_for_powerline: Vec = del_store - .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? - .ok_or("FIXME")? - .iter() - .map(|x| x.0.clone()) - .collect(); - - let chain_for_dnslink = del_store.get_chain( - &device, - &Some(dnslink.clone()), - "/".into(), - vec![], - SystemTime::now(), - ); - - // 4. [dnslink -d-> account -*-> server -a-> device] - let account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header.clone(), - crate::invocation::PayloadBuilder::default() - .subject(dnslink.clone()) - .issuer(device.clone()) - .audience(Some(server.clone())) - .ability(AccountManage) - .proofs(vec![ - account_device_ucan.cid()?, - account_pbox.cid()?, - dnslink_ucan.cid()?, - ]) - // .proofs(proofs_for_powerline.clone()) - .build()?, - )?; - - let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); - - assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); - - let observed = agent.receive(account_invocation.clone()); - assert!(observed.is_ok()); - - let not_account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header, - crate::invocation::PayloadBuilder::default() - .subject(dnslink.clone()) - .issuer(server.clone()) - .audience(Some(device.clone())) - .ability(AccountManage) - .proofs(vec![]) // FIXME - .build()?, - )?; - - let observed_other = agent.receive(not_account_invocation); - assert_eq!( - observed_other.unwrap_err().as_validation_error(), - Some(&ValidationError::DidNotTerminateInSubject) - ); - - Ok(()) - } -} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ca146080..4235299e 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -179,13 +179,13 @@ where Payload: TryFrom>, Invocation: Clone + Encode, { - self.generic_receive(invocation, &SystemTime::now()) + self.generic_receive(invocation, SystemTime::now()) } pub fn generic_receive( &mut self, invocation: Invocation, - now: &SystemTime, + now: SystemTime, ) -> Result>, ReceiveError> where arguments::Named: From, @@ -194,6 +194,12 @@ where { let cid: Cid = invocation.cid().map_err(ReceiveError::EncodingError)?; + invocation + .validate_signature() + .map_err(ReceiveError::SigVerifyError)?; + + // FIXME validate signature directly in inv store + self.invocation_store .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; @@ -312,13 +318,76 @@ pub enum InvokeError { #[cfg(test)] mod tests { use super::*; + use crate::ability::crud::read::Read; + use crate::crypto::varsig; + use crate::crypto::varsig::encoding; + use crate::crypto::varsig::header; + use crate::invocation::{payload::ValidationError, Agent}; + use crate::{ + ability::{arguments::Named, command::Command}, + crypto::signature::Envelope, + delegation::store::Store, + invocation::promise::{CantResolve, Resolvable}, + ipld, + }; + use libipld_core::{cid::Cid, ipld::Ipld}; use pretty_assertions as pretty; use rand::thread_rng; - use std::ops::Add; - use std::ops::Sub; - use std::time::Duration; + use std::ops::{Add, Sub}; + use std::time::{Duration, SystemTime}; use testresult::TestResult; + #[derive(Debug, Clone, PartialEq)] + pub struct AccountManage; + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); let verifier = @@ -353,8 +422,7 @@ mod tests { mod receive { use super::*; - use crate::ability::crud::read::Read; - use crate::crypto::varsig; + use assert_matches::assert_matches; #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { @@ -380,7 +448,7 @@ mod tests { }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::You(invocation.payload)); Ok(()) } @@ -404,13 +472,14 @@ mod tests { Some(exp.try_into()?), Some(now.try_into()?), now.into(), - varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - codec: varsig::encoding::Preset::DagCbor, + header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) } @@ -440,7 +509,7 @@ mod tests { }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::Other(invocation.payload)); Ok(()) } @@ -451,10 +520,8 @@ mod tests { let (server, server_signer) = gen_did(); let mut agent = setup_agent(&server, &server_signer); - let (not_server, _) = gen_did(); - let invocation = agent.invoke( - Some(not_server), + None, agent.did.clone(), Read { path: None, @@ -466,12 +533,13 @@ mod tests { Some(past.try_into()?), Some(now.try_into()?), now.into(), - varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - codec: varsig::encoding::Preset::DagCbor, - }), + header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + } + .into(), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into()); + let observed = agent.generic_receive(invocation.clone(), now.into()); pretty::assert_eq!( observed .unwrap_err() @@ -481,5 +549,255 @@ mod tests { ); Ok(()) } + + #[test_log::test] + fn test_invalid_sig() -> TestResult { + let (_past, now, _exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let mut invocation = agent.invoke( + None, + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), + None, + None, + Some(now.try_into()?), + now.into(), + header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + } + .into(), + )?; + + let (not_server, _) = gen_did(); + + invocation.payload.issuer = not_server.clone(); + invocation.payload.audience = Some(server.clone()); + invocation.payload.subject = not_server; + + let observed = agent.generic_receive(invocation, now.into()); + + assert_matches!( + observed, + Err(ReceiveError::SigVerifyError( + crate::crypto::signature::ValidateError::VerifyError(_) + )) + ); + + Ok(()) + } + } + + mod chain { + use super::*; + use assert_matches::assert_matches; + + struct Ctx { + varsig_header: crate::crypto::varsig::header::Preset, + powerline_len: usize, + dnslink_len: usize, + inv_store: crate::invocation::store::MemoryStore, + del_store: crate::delegation::store::MemoryStore, + account_invocation: Invocation, + server: crate::did::preset::Verifier, + server_signer: crate::did::preset::Signer, + device: crate::did::preset::Verifier, + device_signer: crate::did::preset::Signer, + dnslink: crate::did::preset::Verifier, + dnslink_signer: crate::did::preset::Signer, + } + + fn setup_test_chain() -> Result> { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let (account, account_signer) = gen_did(); + let (device, device_signer) = gen_did(); + let (dnslink, dnslink_signer) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + + // Scenario + // ======== + // + // Delegations + // 1. account -*-> server + // 2. server -a-> device + // 3. dnslink -d-> account + // + // Invocation + // 4. [dnslink -d-> account -*-> server -a-> device] + + // 1. account -*-> server + let account_pbox = crate::Delegation::try_sign( + &account_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(account.clone()) + .audience(server.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. server -a-> device + let account_device_ucan = crate::Delegation::try_sign( + &server_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(server.clone()) + .audience(device.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. dnslink -d-> account + let dnslink_ucan = crate::Delegation::try_sign( + &dnslink_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(dnslink.clone())) + .issuer(dnslink.clone()) + .audience(account.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); + drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); + drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); + + let proofs_for_powerline: Vec = del_store + .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + .ok_or("FIXME")? + .iter() + .map(|x| x.0.clone()) + .collect(); + + let chain_for_dnslink = del_store.get_chain( + &device, + &Some(dnslink.clone()), + "/".into(), + vec![], + SystemTime::now(), + ); + + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header.clone(), + crate::invocation::PayloadBuilder::default() + .subject(dnslink.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountManage) + .proofs(vec![ + account_device_ucan.cid()?, + account_pbox.cid()?, + dnslink_ucan.cid()?, + ]) + // .proofs(proofs_for_powerline.clone()) + .build()?, + )?; + + let powerline_len = proofs_for_powerline.len(); + let dnslink_len = chain_for_dnslink?.ok_or("FIXME")?.len(); + + Ok(Ctx { + varsig_header, + powerline_len, + dnslink_len, + inv_store, + del_store, + account_invocation, + server, + server_signer, + device, + device_signer, + dnslink, + dnslink_signer, + }) + } + + #[test_log::test] + fn test_chain_len() -> TestResult { + let ctx = setup_test_chain()?; + assert_eq!((ctx.powerline_len, ctx.dnslink_len), (3, 3)); + Ok(()) + } + + #[test_log::test] + fn test_chain_ok() -> TestResult { + let mut ctx = setup_test_chain()?; + + let mut agent: Agent< + '_, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, + AccountManage, + > = Agent::new( + &ctx.server, + &ctx.server_signer, + &mut ctx.inv_store, + &mut ctx.del_store, + ); + + let observed = agent.receive(ctx.account_invocation.clone()); + assert!(observed.is_ok()); + Ok(()) + } + + #[test_log::test] + fn test_chain_wrong_sub() -> TestResult { + let mut ctx = setup_test_chain()?; + + let mut agent: Agent< + '_, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, + AccountManage, + > = Agent::new( + &ctx.server, + &ctx.server_signer, + &mut ctx.inv_store, + &mut ctx.del_store, + ); + + let not_account_invocation = crate::Invocation::try_sign( + &ctx.device_signer, + ctx.varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(ctx.dnslink.clone()) + .issuer(ctx.server.clone()) + .audience(Some(ctx.device.clone())) + .ability(AccountManage) + .proofs(vec![]) // FIXME + .build()?, + )?; + + let observed_other = agent.receive(not_account_invocation); + assert_eq!( + observed_other.unwrap_err().as_validation_error(), + Some(&ValidationError::DidNotTerminateInSubject) + ); + + Ok(()) + } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed0ad884..a16c77fb 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -149,15 +149,17 @@ impl Payload { pub fn check( &self, proofs: Vec<&delegation::Payload>, - now: &SystemTime, + now: SystemTime, ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, arguments::Named: From, { - if let Some(ref exp) = self.expiration { - if SystemTime::from(exp.clone()) < *now { + let now_ts = Timestamp::postel(now); + + if let Some(exp) = self.expiration { + if exp < now_ts { return Err(ValidationError::Expired); } } @@ -182,12 +184,12 @@ impl Payload { } } - if SystemTime::from(proof.expiration.clone()) < *now { + if proof.expiration < now_ts { return Err(ValidationError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { + if nbf > now_ts { return Err(ValidationError::NotYetValid.into()); } } From 2cb9a703e66a980e6c1d222faec8d22981d670c1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 17:47:20 -0700 Subject: [PATCH 216/234] Fixed test --- src/invocation/agent.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4235299e..daf0ba2d 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -780,7 +780,7 @@ mod tests { ); let not_account_invocation = crate::Invocation::try_sign( - &ctx.device_signer, + &ctx.server_signer, ctx.varsig_header, crate::invocation::PayloadBuilder::default() .subject(ctx.dnslink.clone()) @@ -792,9 +792,11 @@ mod tests { )?; let observed_other = agent.receive(not_account_invocation); - assert_eq!( - observed_other.unwrap_err().as_validation_error(), - Some(&ValidationError::DidNotTerminateInSubject) + assert_matches!( + observed_other, + Err(ReceiveError::ValidationError( + ValidationError::DidNotTerminateInSubject + )) ); Ok(()) From de000f2516f9b876b8f3ebc3241ecb522755695d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 23:23:55 -0700 Subject: [PATCH 217/234] Don't mind me, just fighting with varint in did:key --- Cargo.toml | 2 +- flake.nix | 6 +- proptest-regressions/delegation/payload.txt | 7 + proptest-regressions/did/key/verifier.txt | 7 + src/ability/arguments/named.rs | 3 +- src/delegation/payload.rs | 246 +++++++++++++--- src/delegation/policy/predicate.rs | 14 +- src/delegation/policy/selector/select.rs | 36 ++- src/did/key/verifier.rs | 308 +++++++++++++------- src/did/preset.rs | 36 +++ src/invocation/agent.rs | 5 - src/ipld/number.rs | 5 +- src/lib.rs | 4 +- src/time/timestamp.rs | 7 +- 14 files changed, 501 insertions(+), 185 deletions(-) create mode 100644 proptest-regressions/delegation/payload.txt create mode 100644 proptest-regressions/did/key/verifier.txt diff --git a/Cargo.toml b/Cargo.toml index db8883df..770279b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,11 @@ path = "examples/counterparts.rs" aquamarine = { version = "0.5", optional = true } # Encoding +multibase = "0.9" base64 = "0.21" # Crypto blst = { version = "0.3.11", optional = true, default-features = false } -bs58 = "0.5" # Web Stack did_url = "0.1" diff --git a/flake.nix b/flake.nix index b0935515..925581d7 100644 --- a/flake.nix +++ b/flake.nix @@ -163,7 +163,7 @@ "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; "watch:test:host" = cmd "Run all host tests on save" - "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; + "${cargo} watch --clear --exec 'test --features=mermaid_docs,test_utils'"; "watch:test:wasm" = cmd "Run all Wasm tests on save" "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; @@ -174,7 +174,7 @@ "test:host && test:docs && test:wasm"; "test:host" = cmd "Run Cargo tests for host target" - "${cargo} test"; + "${cargo} test --features=test_utils"; "test:wasm" = cmd "Run wasm-pack tests on all targets" "test:wasm:node && test:wasm:chrome"; @@ -186,7 +186,7 @@ "${wasm-pack} test --headless --chrome"; "test:docs" = cmd "Run Cargo doctests" - "${cargo} test --doc --features=mermaid_docs"; + "${cargo} test --doc --features=mermaid_docs,test_utils"; }; docs = { diff --git a/proptest-regressions/delegation/payload.txt b/proptest-regressions/delegation/payload.txt new file mode 100644 index 00000000..a1f73406 --- /dev/null +++ b/proptest-regressions/delegation/payload.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2219b6f1cfd2b9c29cdae1174fd8633335cb25947b15ef61c3df44f2664175c3 # shrinks to payload = Payload { subject: None, issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), via: Some(Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })))), command: "eâ'Ⱥ{Ⱥ𞹒/𖿠¦꧊=:Z꧉&ceG4ᅲQ&]%", policy: [And(LessThan(Select([Values]), Integer(-17977310508110653107821168443657725762)), GreaterThan(Select([ArrayIndex(-2063331637), Values, ArrayIndex(1729047197)]), Integer(-71863900340009931549720359637876494783))), LessThanOrEqual(Select([ArrayIndex(1500241979), ArrayIndex(-659046873), Field("`Ⴧ*ᧂ:𑛄&"), Values, Values, ArrayIndex(1750800327), Field("Ⱥd"), ArrayIndex(-1789308745)]), Integer(70158225305663682194208457939428168215)), LessThanOrEqual(Select([Values, Field("*🫁𝕄L𞸧/ຄ𑻤$u\"*𝼥m':ᨼ𑾰/\\"), ArrayIndex(-554915756)]), Float(9.08306675974206e-234))), Or(LessThanOrEqual(Select([Values]), Float(1.003428263794859e-307)), And(Equal(Select([ArrayIndex(-233587294), Field("𝒫𞻰& キ<&ஞ臨2m𞺣'𞋿&{\u{ac3}{D8𒑀𑰈5\u{1ac0}\""), Values, Values, Field("G.吸aj𑿤q^a9%.<\u{84}%.𝝹𔓶": true, "*E🕴5/.`\\x=\u{7a853}&\u{5fd16}𩟨\u{b}\u{b3012}\u{9e345}/A\u{1b}Dts&E{w}\u{d8473}": 51, ".Z\u{795c2}?*\u{bffa7}B¥-/\tb阂?": 50, ".\u{7f}": baguqegzax2jx7mre4zpv63vto6u3mtjy4ivcrpnsnrgfcb6bymbpbwz5ky2q, ".\u{80}%\u{b}$\u{c6cf9}=&&\u{4}↜$'N\u{7f}\u{b}2\u{2}?\u{65b02}Uð𫻓\u{b}\u{feff}": -1.3122304768215823e-86, ".𗨥Ø\u{7f}\u{fe136}g\u{880c8}0O\\è": false, "1={\u{4fc0c}/\u{37be6}+\u{73284}.{\r�腄/_%�?\r\u{b022e}H": false, "2𱟴.": null, "4'\u{70fad}\\�o{\u{ce77b}\u{7f}~\u{d4dac}\u{1b}V\u{b}𩱋�\t&\\\u{aee01}`🕴\u{ee7dd}": null, "5$w\u{df9bd}\\\u{6}\tѨ\u{1b}M\u{cd289}:U%\u{202e}I\u{ea2ae}\r\u{d0993}D\u{feff}Ñ": null, "5\u{db9ee}\u{feff}\"�Ⱥ🕴z\0v<<\"5": 0.0, ":": bafy2bzacecp4g47wlyry7zukgmgp6xoxsh5aqlhn2vtxbpk2j77pzmx5w5xci, "<\0\u{3fe95}\t=`Z𰲨pdËf\u{5}�\u{1b}30\u{a2f77}\t:|%\u{a65a3}": 25, "<\u{2}¥?\u{9ae9b}'?q\u{7f}\u{84177}/?d🕴\u{d0dee}_x\u{a73f9}=`/\"Ѩ¥+\u{1}\u{202e}+Ⱥ/": "%){\u{f34d0}\u{91bf2}{]¥?<\u{7e5a3}\\\u{7f}¥", "<\"k&🕴Q\u{a0cc1}\u{193c1}\u{6fef1}\u{57765}=\u{b04a9}\u{1ee38}*$i�趼\r:^�\t\u{2}/": null, "<'\u{202e}": [246, 133, 18, 125, 51, 172], "<*\r\u{feff}'\u{b}\u{feff},\t\u{202e}\u{b}": 1.9096030614441494e34, "\0#.\u{e50a7}`\u{6b781}\u{72c56}IѨTȺ:\u{202e}\u{c1d84}\u{d1ebf}F\u{bc6f1}P�\u{1b}}?d\u{feff}Ⱥ\u{96517}\u{7}": -21, ">\u{7f}h)\t&'\u{b}": "p$\u{ce2d9}T\t\u{6486c}\"\u{af416}", "?*=\u{eea8d}\u{a8f3d}\u{479bf}\\\u{577da}<Ѩ&\u{202e}\u{ceb29}\u{8}/\u{7f}\u{7f}𘂤*\u{91}\u{96d48}\u{202e}\u{60a35}6\u{7cd5b}\u{b}~7\u{feff}\u{fc2b3}\u{6ca08}": bafykbzacedzd5rmt4wtpaenao7lyjfstkk3babvem5zvxsx66esxsdwdmnuok, "?\u{f5061}\u{6}\u{adb94}\tz<\u{8e6f5}\t]\"\u{1b}\u{50c21}?=\u{feff}\"'\u{cdff0}{�\u{11b58}\u{7f}ñ¥'\"I\u{6efe5}\0": "M\u{d7240}\u{5fa2f}\"\t\\Ⱥ\u{8}\r𓂂E\u{615a1}$ :\0\u{4b6de}2�&::", "D\u{834c0}": bafybwictq6cimsvk5g36pcopei5vzwuqjgz6zab4qnlw6h6pexvenpnuz4, "G{\0P\u{202e}z?<*\u{e4d94}\u{caeb6}c\u{b}\"\u{d4fa0}\0\u{3d0e1}\u{c315a}": true, "O:{.:6\u{1}𒋵=": [215, 19, 250, 35, 145, 227, 89, 97, 226, 22, 100, 90, 212, 248, 231, 66, 1, 74, 222, 19, 252, 253, 50, 43, 95, 28, 217, 195, 43, 193, 37, 211, 223, 168, 162, 177, 117, 244, 235, 50, 129, 137, 255, 246], "P\u{7f}m[*^\u{d4b73}\u{a4603}\u{cae36}\u{202e}C\u{2}\u{99a81}/<": "\t=/\u{202e}{?=z𣿸&Ѩ\t\u{10fefb}:`\\\u{88684}ᇵ&<'IB9Ѩ", "Q=/\u{2}[W$🕴Ѩi`\u{6b1f0}g\0\u{202e}&äq\0\\\u{e7b6e}\u{7}2": 0.0, "S\u{feff}¥\u{363f5}\\�\"|/$\u{1b}🕴\u{d61d7}\u{9c5fc}\u{5}%": true, "\\": 1797410591.2173085, "\\'\\\u{72082}9\r\u{fd777}&'\u{8}-\t$Ѩ\u{85}&'\\$\u{202e}\u{4}\u{d59a8}𐋪": null, "\\*\t&>ó": [197, 70, 103, 161, 242, 103, 93, 85, 220, 20, 175, 146, 70, 248, 167, 201, 81, 105, 89, 137, 113, 31, 52, 197, 100, 44, 200, 169, 146, 8, 75, 185, 88, 27, 164, 65, 95, 19, 82, 178, 129, 49], "\\¥𧷋%\u{7f}/%": 5.3388979855107657e-222, "]\u{2},n{\t": 4.726007655014673e-268, "]\u{e5bc2}:\u{67d79}": [192, 72, 37, 153, 92, 243, 115, 45, 85, 213, 125, 24, 161, 32, 231, 155, 146, 251, 181, 211, 205, 114], "^": false, "`\"\rBbIU\u{b38e7}~{\u{1b}<": [166, 52, 57, 140, 164, 87, 17, 112, 52, 240, 111, 77, 237, 27, 236, 44, 43, 106, 52, 249, 5, 150, 136, 195, 238, 123], "`=": 37, "`=\0\u{ae1db}$'\u{b}<{\u{1}\u{2};": true, "`~HѨ\r=": null, "`\u{91304}8?=\u{456a5}?\u{ee878}`": [72, 35, 50, 245, 247, 42, 83, 78, 154, 184, 6, 235, 171, 159, 23, 112, 160, 167, 206, 73, 56, 6, 234, 198, 253, 149, 213, 217, 121, 184, 227, 205, 182, 89, 67, 116, 130, 153, 30, 111, 90, 28, 205, 170, 195, 105, 252, 13], "`\u{b62b4}l\rȺ\0䱮Ѩ\u{325fa}://%.H*\r4\u{67fc7}f\u{feff}\u{97c83}d_z𒋤𩞬<": "{\u{7}\u{1b}\u{1}\t\"Ѩr\u{5}", "`\u{db05f}\u{82068}%\u{e3259}\u{202e}&\u{8916b}r\u{b}\u{eda56}�7\u{7f}\u{800bc}": null, "a\u{7f}": -4.342818332244186e254, "e;='\u{66fb7}¦_.R\u{6}S\u{99a3c}1*'d�\u{aecf1}?¥:\u{cd4f6}🕴¥{\u{202e}V'": null, "i\u{7acc3}\0*\u{3a0a4};\"G\0x\u{202e}\u{202e}\u{a6ef1}\u{7}y\u{c4c0c}*=\u{202e}Ñ|]\u{b}`S�\u{63b03}&¦🕴|": "\u{9b}\u{feff}/\u{2}\u{32ba0}$5N\u{c18b1}\u{6e305}", "i\u{b88de}:%<:\u{b}i¥\\%&\u{e9081}\u{36b96}\\wa\u{53cd7}\u{7f}\u{441e2}": [254, 111, 111, 15, 235, 43, 5, 115, 184, 222, 104, 79, 29, 45, 167, 151, 205, 114, 64, 194, 79, 2, 66, 154, 153, 108, 151, 126, 250, 233, 134], "z": true, "{\u{7f}Ⱥ&��0a]\u{7f}\u{b3046}\u{2fa93}\u{7f}<`a\u{10b100}b\u{e36be}-:\u{b}}I\\\u{c99dc}(�\u{d4177}<": 5.69430214693307e-309, "{\u{90}�\r¥%`": null, "\u{7f}=n\r\u{b904f}m\\`&\u{8d}&\u{8}q\u{10fb4a}\u{202e}\u{feff}": 30, "\u{7f}\u{6677f}\"\t\t𥡏&\u{929fa}%\u{1b}": false, "¥\u{4b398}$\u{b}.\u{10edc3}.v\u{4}\u{c8f3b}`\u{1b}o\u{b}B�.\u{8a4e7}": true, "¦\u{567e2}\u{789fb}.\u{1}\u{bac03}FeLѨ": [46, 154, 121, 10, 113, 153, 177, 226, 108, 199, 94, 194, 182, 56, 189, 253, 144, 68, 220, 178, 227, 39, 5, 27, 46, 171, 2, 171, 100, 125, 136, 48, 185, 143, 175, 224, 38, 180, 89, 73, 253, 97, 152, 89, 126, 226, 69, 159, 39, 4, 5, 151, 166, 157, 129, 18, 74, 141, 30, 245, 18, 68, 59, 4, 84, 71, 127, 83, 157, 160, 70, 180, 19, 67, 237, 156, 119, 247, 69, 10, 220, 32, 51, 35], "Ê`/\u{e98d1}`8\u{5a946}¥\tѨ\\\u{71794}\u{1b}\u{5d467}\u{5f80a}\u{2}o\\\u{78e03}\u{1b}$V\0,Ⱥu<\t\u{4}": "l\u{80}/H", "Û\u{a246c}\0\u{cc1d1}\u{1b}": -2.3446004632097955e-204, "Ⱥ\u{48608}�\u{1}Ѩ\t\"\\\rRx\u{feff}𱋭🕴.>.\r?/Ѩe": bafybmibqonatpssw5xwj3ckqwghe3uv52fbojbwo6wevvgyxhhi6is34iy, "ЏѨ$N==jX\u{ef98d}?{Ⱥk\u{4c9b3}\u{a5c43}'\rx": true, "Ѩ?=Ⱥ\u{7f}\0🕴$�\u{39466}\u{1b}�\"`\0.\u{feff}*.x-\u{7f}EѨÔ\u{7}\u{10f98d}$P": false, "Ѩ뀳🕴\t\u{202e}<{\u{950c8}\0\u{6abe2}2¥G\0": bafkrmicucdpcfbvzkhxwm7wgcnbx7wbbsx7yf4xs3xnebcwqv4fzfnyeui, "\u{202e}\u{5}\u{f93a8}�\t\u{93751}DX.4\u{b}\u{47079}\u{cb546}Ê\u{3c9c6}('Q\u{df412}^(?%\u{1b}\u{d4087}\u{87657}\u{69291}": bafyr4igptyd3pys2jr5l7f6wly2ixknaqjhxa2goliyvnoxef4q5npn2wq, "\u{202e}//)H\u{489d0}\u{b}\"\u{7f}7\u{fd8c1}\u{52967}": -49, "퀹\u{1e584}Ⱥ\u{b5e48}?\u{d70a5}\u{202e}\u{feff}o&Ⱥ\t2&$TѨ\u{c7d9d}¥z9�#B,\u{7d2c6}\u{a9082}": -39, "�\u{b}\u{9b}:\u{afa9e}\u{8b}\"G%=\u{6}\u{b1e04}𫃏\u{202e}{:Ⱥ'\0[;\u{cbe4b}Ѩ¥V\r:e\r�\"\u{36274}{\"\u{feff}\t\u{1b},\\�鴇\\µ": "*\u{b2484}(j'0\\�\t\u{feff}~I*\u{1abf0}鲫!¥í\u{7f}\t\t\u{feff}", "\u{2}¥@@x\u{7}o<>\u{b}>�\"=\u{4a714}H\u{356a6}Y\u{feff}¥\0Ѩ`\u{8586b}�@\u{86433}8\u{b5eb0}U\t\u{928a7}?:3V\r\0\u{e963}\u{91606}\u{f5d9a}`&&\t\\{$&`": [203, 219, 129, 233, 167, 86, 133, 68, 30, 123, 72, 152, 245, 36, 18, 51, 184, 218, 78, 248, 74, 234, 85, 239, 225, 163, 68, 62, 10, 51, 41, 233, 241, 66, 225, 241, 213, 58, 199, 207, 196, 20, 9, 130, 162, 125, 157, 172, 66, 51, 176, 230, 83, 217, 243, 126, 106, 4, 227, 168, 130], "?\t#?\u{c8453}l<\u{ebb70}<\u{202e}\u{1}\u{4a0a6}\u{bb34d}𧆽{\"\u{202e}\u{a6e5e}<\u{2}$\u{1b}{\u{1b}\u{a7345}\u{bcba2}=\u{55f80}𠘓`": false, "@\0 \u{e2e8b}\u{feff}&{¸\u{1b}`Ã`<\u{c80a3}\"h%V\\:\r:{%\u{b}\u{91b96}\0": 16, "A\0\0%Ⱥ\u{7f}o6Ѩ\ra\t\u{fa6d1}": 0.0, "E5\u{3ef04}\\iѨ4==\u{ad632}\u{103a8b}\0\u{882ea}\u{3fb8f}\u{80a35}{\r\"\u{9afa0}\\<3*\u{e368f}\"{Ѩ\"\u{85b40}/": [62, 206, 252, 139, 165, 123, 83, 85], "K?\u{15ea9}%/Ⱥ\u{39c0e}Ѩ\tM/\u{806b5}𣎂+": "\u{7b58c}\u{eb27}", "Py/\u{eeae7}\t:z/u&\u{fdecd}\\`<": 2, "P🕴EU\t$\0V\"*": true, "S\u{6}?'{¦~:¥\"\u{797af}:Ⱥ": false, "W/\u{7d22d}\u{7f}k\u{6f5bc}\"/\u{7ac51}\u{feff}\r\u{1b}\u{b}`\r\u{16d8a}\"\u{feff}\u{4e68c}": null}, 3.7320199453131945e18, true, "\td", -2.1302548429054803e-66, [9, 130, 254, 29, 224, 58, 3, 226, 45, 154, 180, 58, 156, 107, 43, 160], [184, 58, 122, 21, 191, 153, 91, 193, 125, 41, 253, 9, 18, 17, 100, 75, 60, 215, 91, 98, 131, 82, 225, 220, 226, 154, 246, 54, 253, 108, 18, 104, 142, 238, 92, 249, 74, 194, 85, 60, 101, 114, 120, 130, 76, 198, 254, 54, 43, 192, 5], -3.035213207805024e-151, true, null, true, [144, 5, 247, 45, 158, 101, 252, 143, 38, 225, 226, 145, 14, 34, 204, 157, 201, 182, 152, 54, 29, 245, 248, 233, 203, 103, 50], 7.126162207025803e98, 0.0, 38, 44, false, 1.442945330979412e-308, -3.2821103465088214e185, false, -0.0])), GreaterThanOrEqual(Select([Field("\u{dca}?i%ѨѨ<\\{Ѩৡ=ᨁr\"T\u{1a7f}🛞{༴Ⱥ:ᛧ"), Values, Values, ArrayIndex(-206845918), ArrayIndex(284566073)]), Integer(-130442174005234510445148006169593931048)))), Every(Select([Values, ArrayIndex(-1235287252), ArrayIndex(1235974543), Field(":�𐔆Ѩ1ꬓ*%𝔹⑩%By�ࡤ{6&🃅%ᒜ!*t\u{cd5}ܔÈ🃌K-:f"), ArrayIndex(-74267187), Values, Field("𐝧¥ѨK𛄲ﹰ~/𞺀Ⱥ𐁑{A℈ꧻxኁ$X")]), Every(Select([ArrayIndex(-835767092), ArrayIndex(-90842294), Values, Field("&Z𛱺\u{1a6b}¥¡\u{2002}O�Eࡪ(/ѨѨ\u{fb3}ö2<=u,ⶣb"), Field("হ\u{1d242}]𐺱'<×\".᪗\u{cc6}%\u{9e2}/$`+ꚠ𞹲ѨRr'7\"\u{1921}"), ArrayIndex(-1657524013), ArrayIndex(-1358049927)]), Equal(Select([ArrayIndex(-2133215332), Field("�ѨA$=%0𑒌$𞴻{ῚL𒿎<*Hÿ)HಯmJ᧗ૉvকj¥𑶄ዅ"), ArrayIndex(1372867808), Field("9U𒐽ᅥטּ:^�ཬ{L\u{1a7f}🛞e{𝔊sை8%$-SޥJ=\u{1e136}&"), ArrayIndex(-1515605936), Values]), Newtype([[220, 112, 193, 144, 130, 2, 201, 3, 173, 97, 18, 34, 79, 189, 93, 185, 99, 8, 146, 72, 228, 21, 109, 226, 107, 211, 58, 9, 68, 90, 109, 0, 69, 37, 94, 48, 179, 143, 33, 56, 70, 63, 33, 221, 197, 203, 25, 245, 187, 130, 222, 127, 221, 178, 88, 248, 218, 158, 87, 24, 127, 138, 100, 104], bafkr4ighuk7gjom3gactib6iatiimrm4ytvtdk7y5k4pzutsg56i77lanm, -8.793256611809324e-208, [49, 12, 111, 228, 6, 196, 193, 26, 82, 212, 21, 13, 44, 254, 161, 77, 168, 161, 151, 85, 22, 53, 168, 74, 48, 15, 119, 171, 245], 3.675952639990706e158, false, null, null, null, null, -156.1567975459344, "\u{1070c5}\u{202e}m\u{7f}'#\t\":\u{dad72}ü4\u{202e}\u{7f}'涛\0$E{-/\u{202e}\u{7f}b", "\u{13e62}$e<\0&<\u{c5acc}ny\u{9a704}\"\u{202e}𔖚D", bafy6bzaceagsds7mbaud2m6qhdohvpgwuhfeedqwrntvioyma3kuo5v6vtyt6, null, null, "\r\tg:\u{46cc2}", [142, 185, 121, 120, 78, 34, 236, 132, 123, 49, 125, 116, 146, 139, 230, 17, 174, 47, 83, 146, 252, 30, 220, 161, 163, 207, 136, 121, 230, 68, 139, 61, 190, 89, 223, 74, 72, 219], [28, 249, 124, 123, 205, 232, 92, 8, 106, 47, 104, 13, 104, 201, 76, 228, 38, 128, 160, 168, 2, 34, 83, 71, 248, 91, 111, 250, 225, 49, 225, 194, 169, 133, 253, 173, 44, 47, 27, 4, 181, 140, 138, 211, 3, 136, 21, 139, 138, 196, 191, 191, 76, 163], true, [87, 3, 80, 153, 162, 125, 138, 138, 127, 8, 103, 89, 128, 18, 40, 41, 190, 156, 216, 106, 118, 162, 241, 107, 134, 144, 107, 52, 51, 165, 85, 124, 197, 155], "\u{2f39d}{¥\u{40daf}ѨS\u{a44e7}{\u{a7dc1}�\u{cee1b}'", [235, 139], false, "", "\u{a1d4d}\u{feff}\u{4}\u{feff}3\u{1494d}\u{e1172}.\u{1b}`\u{d2482}{\u{9e}\u{e1d5d}\"3/{\u{5}2].Ⱥ¤<\u{96fa3}Ⱥ\u{49ea4}*\"p\u{b83db}\u{6}\u{6}": null, "?/\u{bdd3c}\u{370b4}\0*\u{102d37}\u{cbc47}.\\Zâ\u{1ced4}KѨ🕴::\u{7f}/": 44, "?8?A:/'H\u{b}\tr\"¥=qu\u{b}¢\u{fde59}\"\u{2}ye'?\t\u{ea1b7}K`\t": -3.9299672377331247e-146, "?{\u{7f}%\u{7f}\r%\u{d3348}\u{b}\u{eb7b2}%¥\u{9406d}\rѨѨ],\u{202e}'\u{105dc6}<^úr\u{8015f}": "\u{9d}\u{891c2}𫩼\u{606aa}\u{45288}p!/&G:㝸ã\u{97}Ⱥ\u{b}", "?Ѩᆬq7/%\\\t\u{feff}\u{da5c3}\"\u{8509f}\"H`\u{1b}%\u{4d464}\u{479f4}\"\u{c147b}\u{1b}/𧳓*,|": "Y\\\u{7f}\u{b1fcf}\u{e99b9}7\u{9e18b}:?\u{ede3}$W", "?\u{5d644}\u{5a09a}\"�l\u{1b}/\06Ѩ.&{\u{10a396}": bafyrmihqvy27erjh5esd3x6wdgnr6z6lru7wfcmkuuvzp7h7am6z63mbia, "A\u{763e5}& \u{d7169}\u{e8031}{\u{b}*\"\t𰩧\u{548b4}?u`/\r🕴\u{7c626}$å🕴\u{10dd6}:🕴": null, "DѨ\u{6ebc8}\u{7f}<\\[?\u{7f}&ኵ:f=𫗿\0�\u{202e}": "s\\/;*\\\u{4150f}E", "F\u{1b}\u{10ea63}\r": -3.881911088191245e-265, "H.¥$": -21, "K\u{cbff5}\u{202e}\u{1260e}\u{feff}&:%\t$\"|&\u{342e6}*:\"&ju/c": true, "N\u{b}&\u{feff}\u{691d4}\u{6}\u{b}:&*Ѩ\u{6f0dd}w\u{e581c}¥z�\u{9cce5}\u{16d5d}\u{4f30a}**\u{93d8c}\u{202e}\u{7}{\u{1b}{±'{K": "%\u{feff}¥", "[𪲁@\u{1387b}\0]\u{6fca2}Bi.\u{89a95}\u{6a578}\0\u{4a64d}\u{b}\t\u{647d9}n\0^\u{8ff74}Yk\u{9c}\u{9c}(": [116, 173, 176, 186, 52, 23, 252, 79, 37, 2, 204, 64, 93, 133, 149, 198, 164, 255, 254, 251, 238, 69, 160, 225, 130, 239, 127, 62, 193, 179, 31, 95, 161, 114], "\\\u{6d15d}\u{b}&\\Ѩã\u{e6f74}<\u{a15a9}\tѨ,\u{b}\u{819b6}\u{3ef57}/\u{4ddf0}Y(\u{5}\u{100a6f}\u{b}/.:": -37, "]\u{3}\t\u{e5fb9}?{\u{202e}z\u{44ab3}\u{39431}\u{e8c44}¥\u{79b81}'\u{777e6}�:{%\u{3900c}{%\u{108b7b}<{`\u{7f}Ó\t🕴T.": 29, "`\u{6}.:🕴{\u{9c4a2}\\<*:": bafy6bzaced7s7j4l6dybvc55yypxhw5em5sg2ciws33rjhgce6hmfmpgf4fdu, "`o=\u{6}3\u{e81b2}\u{1b}G&\0tw\u{3}/\u{3d977}`㊈)=Y\u{2}\0": false, "`\u{44886}/\u{a1f9b}\u{5f978}:\r\u{95be2}/🕴": 0.0, "c\u{5}\u{4bbc4}b\u{c96f8}+%$\u{202e}'<\u{e078c}\u{ac7cc}x\"Ѩ": 53, "n": "\0?\u{efd30}\u{686e5}\"\r\u{5fe32}\u{b5b0c}\u{51f54}\t\u{89}=\u{33916}ZM¥\u{4}\u{7}m�}\u{91c01}\u{6839a}*\u{c59e9}", "oѨ`": null, "w\t\\`\u{da1d2}*&.$E;\u{62ec7})\"Y\r\"5\u{bd563}\u{326cb}{å?/\u{b}\u{98e3b}": "D::\u{202e}F\u{d9e2c}J'=\u{ec5db}\0'\"Ѩ\u{1b}\u{419e5}𮞿?E*mb\u{feff}_Z\u{ab92a}=\u{89c4e}逋\0\u{7f}J", "y6\t<": [179, 71, 5, 85, 100, 29, 222, 53, 97, 142, 194, 243, 96, 220, 13, 106, 64, 105, 167, 218, 123, 136, 220, 228, 82, 153, 8, 92, 185, 11, 112, 146, 197, 109, 163, 11, 117, 83, 66, 85, 178, 149, 17, 95, 212, 87, 96, 62, 216, 80, 38, 36, 236, 156, 22, 40, 44, 133, 95, 15], "{Ⱥ/&\u{7f}\u{5795a}_*\0*\u{a13ce}f?`/\u{1bd42}<𮧏<\\U(n\u{2ffdf}\u{bad0b}&]\u{feff}U🕴$": 9.93350022419413e112, "{\u{39ee3}TI\u{ea081}%Pï>\"Ⱥ\u{feff}{\\j": {"": "\u{feff}\u{1}\u{1}&=¥\u{68854}&/ö\\\u{33a28}\u{d8908}f4]Y\u{39e57}**Ѩ\\*Ѩ\u{5270a}{B¥+x\u{feff}", "\0${\u{8f006}:f<\u{3}\u{ae249}\u{a3dc8}\u{5eb29}\u{b67f9}%:\u{37105}\r\u{1b}'$\u{2faa8}$/A\tW'\u{909b7}\0\u{2}": 2.914324980421285e-195, "\0$\u{ac00f}\u{1b}9\r\u{3}\u{2fd7f}=:R.\u{e3705}\u{c4450}NG\u{73b7c}&$\u{202e}\u{b527c}GL": "\u{a3b69}6a*==?\u{feff}\u{1a1cf}\u{a82da}`/==�🕴\u{6b826}\rD?A\u{feff}\u{1}", "\0*{\u{202e}\t�\u{1b}/|Ѩ%©%": baguqefragwjjb3m3ljrschzrusb2unylas52kabmwfr34wqmvo6rkg2avvuq, "\u{3}\t\0\u{6695e}\"{»1\u{202e}\u{68a4a}N$": [233, 212, 54, 104, 52, 88, 23, 191, 247, 21, 223, 50, 174, 43, 59, 131, 37, 131, 62, 190, 142, 176, 67, 43, 184, 235, 120, 202, 175, 190, 189, 145, 211, 136, 59, 252, 222, 235, 131, 213, 187, 34, 118, 61, 42, 93, 166, 43, 180, 114, 34, 166, 57, 195, 172, 167, 175, 177, 81, 106, 26, 118, 209, 95, 105, 177, 234, 126, 58], "\t\u{1b}7🕴¥qb\u{8}\r\t\u{1062f0}%'\u{b}.g\u{411f2}𭕨B{Ⱥ\u{77264}/\u{b}D\u{3d766}<": null, "\u{b}/<\u{34eed}\0S&>�텐$$": ".\u{7ebf4}Z\u{d3912}\u{feff}?*H\"\u{202e}𦊇\u{91}Ⱥ\u{7f}\r?{)<\"\u{42b46}?T\\\rg\u{ec834}\\\u{7f}\u{7f}¥", "\u{b}?\t\u{2}�4¥𞸩*\"\u{578ba}\0G%��\\r%«=🕴=\\&*=": "\u{8237a}\u{8a}<\u{8ab59}.?<.tàô\u{945d6}\u{feff}`\\#\u{ce61d}c:)?.`a<\u{9d398}\u{962eb}\"&", "\u{b}ÞѨ¾y狦\0🕴\t\u{19d5e}\u{f0148}": bafykbzacebxelvnoczrdap55pukvsxpc6gicbmnqzgsouqceppvlhsicwet6e, "\u{b}ãx\"\\\u{4}`\r:\u{feff}ᦇ\r": 22, "\u{b}�`+\".?hC\u{88713}:\t\0N¥/Ⱥ\u{1d2b9}1&`Ѩ.": 0.0, "\rAf7\06`\u{ff186}\u{9f715}\t": "/$\u{eaf96}\rW.{{.l흄*\u{1b}\"🕴!?\u{a64ef}\\\u{7f}/\u{109cc5}\u{5}'\u{b}\u{fc0c5}𱇴\u{d1388}", "\rUKG/\u{3}\u{2}ꕻ\u{b}\t": -1.3995651248504633e262, "\r\u{72228}\u{3}\t'\t\u{c5bb7}\tv": [207, 209, 38, 232, 225, 181, 157, 174, 248, 85, 75, 104, 14, 234, 9, 86, 149, 189, 217, 176, 122, 32, 78, 186, 174, 19, 241, 185, 202, 135, 32, 184, 35, 47, 78, 189, 35, 153, 142, 128, 46, 7, 65, 55, 37, 215, 0, 109, 138, 244, 78, 202, 124, 93, 146, 143, 199, 70, 40, 153, 254, 139, 213, 103, 34, 98, 157, 146, 23, 126, 115, 247, 59, 186, 119, 63, 16, 179, 123], "\"%3\r\0\t*\u{74f05}\u{10ecdf}%\u{56b43}\u{e2041}\u{b}Ѩ🕴%\u{47ac1}\u{b4501}\u{10314f}\u{b}": 8.190682906409504e77, "\"üE8À%": -5.01070380812316e-309, "\"🕴¿%z4q\u{f5f81}\"\u{202e}\\": [99, 154, 31, 116, 21, 233, 127, 12, 159, 139, 166, 170, 113, 31, 35], "$&\u{81e34}�\u{feff}\u{3}🕴\u{b}\u{3e109}\u{8e8b1}A\u{1b}Ѩ)\r¥\u{c68f3}\u{10081f}\u{10dd8f}": bafyrwibh3uv7biwsbpe37hkwovhrpkm3l7s5icdgji6imq5fjykcl7p24e, "$'Ⱥ':\u{f6dca}{&\u{7d81d}GQѨ𧗿~.Ѩ\u{3f8b4}\u{feff}\u{2}\u{2}`\u{b}\u{202e}\u{7f}\u{8}\u{b45ad}": bafyrwiczadd6c3wkaraxq3lg5clhqyngn6fprrjscfzxfe2mp2kg4pvxba, "$}\u{4998f}}\u{7afe3}\\\0n?\"V\u{1}\"{!*y%\u{1b}\rꢢѨI\u{f13fb}X\u{1061f7}\u{7f}%'l\0=\0^)\u{5ac7e}<": [60, 118, 165, 67, 27, 119, 21, 252, 153, 133, 75, 209, 126, 27, 107, 9, 142, 133, 201, 242, 96, 56, 139, 85, 39, 185, 191, 172, 121, 16, 61, 101, 100, 93, 23, 114, 153, 201, 213, 220, 14, 1, 73, 194, 75, 71, 226, 125, 205, 187, 97, 24, 229, 247, 75, 87, 172, 90], ".`\u{bec17}Ç'\0/\u{d491b}𘅣🕴.\r": 1.2131909958473984e-266, "/\"": bafkr4ifg4p3nsgepq6lvarhhrt4lxydl5arndmqieaknvnqhyvw4l62eau, "/,/\u{b}\u{a9092}\u{3}.\u{7f}ñu?Ⱥ'&\u{e2082}\u{3d612}u": baguqfiheaiqibdayxvhi3ktwv4rfqhx7adreiuo2muqdzc72zj7wydxi2ircenq, "/.\t¬79\u{202e}<%Z\0\u{7}\u{b}&\\🕴\u{c07fd}j=(1\0𮟻mJ�.\u{fe739}ȺXB\u{feff}": [35, 209, 94, 4, 132, 12, 218, 13, 76, 94, 110, 176, 233, 104, 74, 8, 231, 103, 204, 48, 35, 1, 208, 81, 103, 39, 15, 209, 174, 232, 89, 234, 13, 222, 88, 45, 83, 226, 52, 81, 83, 59, 125, 241, 159, 38, 79, 5, 21, 197, 38, 169, 183, 154, 154, 183, 251, 209, 151, 212, 196, 132, 155, 189, 109, 45, 206, 253, 141, 141, 226, 210, 69, 80, 142, 13, 168, 40, 84, 52, 228, 159, 231, 76, 229, 248, 47, 21, 115, 15, 247, 164], "/.\u{b}\0gMg%\0\"n\u{1b}�\u{dc8ef}": null, "0\\\u{b2c71}:.\u{7f}.q\u{ee1a4}\u{3}.`:=\u{7f}shÕ\0'\u{ff5c2}\0<\u{48d2d}": null, "6\u{7f}z\u{fb43d}\0\u{e71c2}n\t\u{1b492}\u{be144}%m\0O£{\u{4}ß`S\u{cc20a}\t\r@\u{1b}\u{2f392}T\0�:4i": [184, 132, 8, 217, 132, 246, 183, 246, 210, 254, 92, 125, 142, 179, 49, 205, 173, 48, 36, 66, 57, 14, 184, 195, 88, 109, 101, 153, 91, 53, 120, 69, 198, 5, 81, 144, 203, 189, 119, 182, 143, 191, 14, 61, 34, 36], ":(`T\0\u{1b}\u{1b}\t$¥z\u{36613}\u{a5f2d}Ѩ\u{d1ec0}Ⱥ\u{7f}\"'u\u{b7fb7}ë` \u{feff}\u{5d6e0}f\rê🕴": [191, 71, 93, 227, 249, 253, 186, 190, 214, 13, 71, 250, 111, 182, 66, 219, 22, 139, 183, 203, 250, 58, 184, 20, 20, 213, 80, 32, 66, 40, 214, 69, 53, 203, 170, 113, 150, 95, 7, 238, 77, 250, 5, 90, 119, 215, 135, 80, 225, 91, 49, 36, 143, 153, 196, 238, 205, 224, 18, 127, 173, 184, 246, 90, 134, 17, 119], "=@\u{8f9f3}7?\\𫥂\u{1}": "i|*\u{c09ed}'\u{b871f}\u{4}*\u{202e}&\\\u{105cbd}bѨ\\*?{F\u{feff}uȺz\0`\u{feff}{+", "={": null, "=\u{a02a9}\\\":¥🕴\"\u{202e}P\u{7f}\u{b7f71}\0\u{3a7e9}Y<î\u{157d9}Å": [19, 138, 165, 245, 200, 2, 67, 217, 45, 240, 188, 107, 149, 30, 61, 57, 176, 165, 123, 163, 219, 5, 81, 31, 155, 252, 139, 204, 155, 201, 120, 3, 32, 18, 218, 98, 147, 233, 22, 106, 60, 86, 204, 202, 147, 32, 223, 159, 66, 107, 250, 251, 19, 167, 246, 252, 90, 205, 212, 54, 251, 5, 241, 43, 213, 120, 23, 1, 29, 163, 69, 234, 99], "?&🕴.*.\u{605a3}¥&j/=a\\\u{b}*Ѩ*'¾�ธ#Ⱥ\t": "\u{7f}\u{8}Y{5\u{60a62}jN=f\0'𩧦\u{feff}\u{7}//a¥kK\u{48989}&\u{f203b}\"\"`", "@Ѩ": "/*\u{95147}\u{b}{`\u{4e71c}", "C\u{3}<Ò?B¥@&": [130, 96, 105, 54, 223, 53, 178, 56, 46, 187, 18, 221, 159, 48, 247, 89, 117, 41, 108, 127, 91, 200, 247, 120, 240, 237, 155, 170, 65, 55, 58, 141, 247, 254, 60, 102, 29, 148, 211, 55, 4, 14, 68, 42, 90, 173, 222, 142, 20, 22, 68, 185, 132, 255, 132, 185, 189, 197], "D\u{a65a4}$\u{df148}\\F\rntѨK\u{1b}jd\u{8ea98}\u{b106e}�": 1.3784978135410346e-170, "Eò<.z\u{781d5}\u{2}\"�^": null, "H\r\u{361f5}{\"�\"\u{41b3d}\\'𦀳¥\u{80}Ⱥ\u{c26fd}�6-`N\u{dca3b}G\u{1de7a}=\\": true, "L\u{7f}:¥=t*{_AP\u{366cc}?wȺ\u{eb31}o.\u{881ae}\u{77df7}t\u{b}\0{\tѨ\u{d6c3a}": [97, 251, 86, 194, 220, 214, 153, 209, 190, 118, 25, 200, 75, 133, 240, 162, 84, 193, 128, 3, 238, 222, 6, 149, 222, 157, 239, 202, 16, 102, 50], "S=U\u{bff90}/?\u{da120}.\u{633c3}a|\u{1c959}\u{3be11}\u{feff}X\u{a9622}?\t.Å.🕴=\u{73c63}𰰅\u{61725}¥\u{a6664}<\u{895f4}i": [140, 113, 176, 106, 69, 9, 192, 221, 52, 44, 56, 187, 134, 114, 29, 65, 208, 39, 0, 95, 237, 224, 76, 195, 8, 225, 21, 98, 228, 60, 95, 240, 189, 156, 136, 235, 72, 132, 236, 170, 1, 250, 184, 134, 77, 48, 249, 199, 172, 3, 66, 201, 75, 15, 29, 254, 104, 111, 158, 53, 80, 85, 74, 36, 20, 15, 150, 52, 31, 50, 233, 6, 228, 244, 185, 202, 142, 56, 126, 47, 69, 35, 225, 162, 147, 42, 172, 213, 71], "\\\u{1b}Ç'(\u{76ac1}\r\u{202e}NÙ\u{87e35}`{\u{52666}Ѩ\u{7f}\u{e28bd}?窣\0\u{e4901}🕴\\": 1.307777027892035e-308, "\\V$\u{71773}\0.H𘑞\u{202e}`&)'\u{cc7d7}4_�📲j.\u{c5777}:\u{b}{\t?\u{abcf6}\u{2}\u{f114a}": null, "`\u{1}7\u{dc7f6}è\u{1b}\u{e6461}", "bF罩`/?{\u{6}m�🕴\u{a0dcf}\u{1b}\u{85683}\r*z\u{3}`\u{202e}\t\0¥\0ó�\u{fa710}": null, "f\u{71088}\u{eeba2}/\u{1b}\u{b}>\r:$H\"ï\\\u{7f}\0¥{+?": null, "k:\t<鏁\t^:u碗\tñr:": bafkrwidekl2tflko33qlm47p7adikj4q6bdju2kq5elnuipzykk565mhby, "s<Ⱥ*[L*<ѨÉ\u{88d19}": [88, 175, 1, 80, 241, 221], "u\r": -2.6904580729605092e122, "u¥\u{feff}\u{1b}": [1, 36, 101, 136, 222, 223], "x`\u{6d787}\u{e5350}\u{df1fb}(>qP🕴\u{ba6ac}𮠰🕴\\\u{4b5e0}J<": false, "{\0\u{3b4bd}=g¥\u{107402}\u{5c069}Ѩ=5k*$`<%p": [42, 216, 219, 136, 90, 214, 91, 194, 12, 10, 62, 81, 119, 54, 126, 138, 227, 206, 148, 138, 95, 191, 247, 89, 212, 142, 18, 121, 148, 163, 152, 177, 37, 69, 222, 22, 226, 117, 153, 5, 89, 234, 92, 155, 223, 10, 158, 200, 18, 37, 110, 103, 181, 109, 202, 35, 39, 117, 93, 5, 90], "\u{7f}\u{7e6e9}\u{8b}𥚝": baguqehrahkajpop4geeiwsi7gu2wdsuybkf6lbq6rfen4f7v3vrtqbyazd3a, "\u{7f}\u{b601e}'\rK/\t\u{feff}¤?\rp\u{af66a}": [73, 33, 204, 97, 67, 215, 167, 191, 121, 17, 85, 93, 75, 141, 150, 250, 223, 157, 229, 29, 48, 217, 58, 27, 191, 36, 145, 93, 14, 17, 12, 32, 61, 36, 83, 58, 181, 136, 104, 35, 237, 219, 146, 240, 223, 170, 203, 45, 27, 109, 190, 5, 96, 122, 180, 241, 211, 211, 147, 12, 136, 219, 25, 19, 108, 191, 73, 165, 95, 129, 7, 16, 40, 44, 123, 182, 100, 246, 148, 80, 99, 191, 137, 144, 177, 240, 82, 242, 163, 30, 210, 13, 45, 140, 6, 196, 122], "¥O\u{7f}Ⱥ𗢎e/\u{10697e}\u{202e}\u{7f}0�Ⱥ𤂹\u{e098b}\u{8383e}`G<<\u{b94d8}Q\u{e6032}\u{cf7bc}\0:\"?\u{1}\u{1a8a3},": null, "¥\u{202e}'\u{8d073}\u{78d6b}\u{6e610}\":E?\u{9bb82}Ѩ<�>\u{4c02a}//\u{a27e4}=\u{f456f}<3𧉟/\u{98}\u{f3520}": "x=`ᒼ\u{12af8}\"=", "Ò\u{51a90}�\u{1b}\"䩖𦄱\u{b3b7f}\u{aee50}<🕴{\\A\\\"X\u{f32ab}::\u{4}\u{c7c01}`\u{1b}\u{4}&\u{ff44f}": 0.0, "\u{86b18}Ⱥ<": null, "\u{a0feb}&¥\u{106aba}{B\u{be082}1\u{62718}\tS\u{aa928}\u{2}\u{3d1dc}\u{f5e89}": [16, 76, 209, 135, 206, 142, 16], "\u{afe7a}A`>\u{dafee}𡪩b\u{77678}%\r劗?\u{10819a}^.S\u{7f}\u{94083}$�\rA": 5, "\u{b2ccd}ë9('*\u{b}2\u{8}\u{c9f32}\u{5a462}\"?2\u{b}\0W�\u{3e6e8}th\\=\"F": null, "\u{e6ebb}r\u{202e}\u{80bd8}:🁚\t�\u{fd427}\u{b2fb5}\u{4badf}w/\"0\u{202e}<\u{15010}^v\u{53eb1}\0='": null, "\u{f31e7}41\r\u{68a9f}\u{202e}\t.p\u{4ea54}\u{860c8}\u{19987}l": -0.0, "\u{faf52}\u{6}¥\r\u{202e}䲖\u{202e}R&\r\u{973bd}\u{feff}l\\\"$\u{e5961}¥\u{d805a}\0+OB*tu\u{eb5a9}\u{109146}Ⱥ\u{759f5}`\0": null, "\u{fc398}\u{2}\\.\u{ee493}\u{d0de6};\u{ed8fa}🕴<": null, "\u{10ed00}?\u{7f}�`\u{b}L*\0®": -0.0}, "\u{7f}':e/Ѩ\u{36fde}`/\u{202e}\u{7f}=\u{feff}🕴\u{f28b6}\r": null, "¥": -0.0, "¥ :?\r\u{70234}": "�¥", "¥i🕴\0\u{93569}\u{71ace}\t:\u{75563}*+": false, "¥\u{be29b}\u{202e}\u{7ef68}5\u{1b}`\u{202e}\"\0\u{feff}\u{10e7da}�\u{7f123}|\u{8c8c5}n": 11, "±:𢽭🕴t�%r\u{8}?`^'r": false, "Ⱥ/\u{882e4}:\u{e0f71}$%/{\u{36b45}:,z\u{8}[1\u{7f})%\t\u{b}v\r\u{aaa37}\u{8}𫷝\u{7f}\u{46db0}¥\u{ad5a1}ke": -12, "🕴\u{feff}\\x\t\u{6}\t~\u{39f68}\u{202e};�\u{1b}6:/3\t\u{7f}": bafkrmiepyixjho2atqqcqgtd575iqsam6klfzdtkx5g4wummups24pdqcu, "🕴\u{5de4c}\\�𐝣\u{feff}𱴜\u{feff}": "\u{6}\r:\u{190a7}�%Ѩ𲇭:\u{6}_🕴Ò%\"'\u{e1b6d}F'\u{f7ad8}\u{57ad7}`\u{9c70b}$`\u{202e}\u{4b5bb}UK\u{90}", "𣉓��e\"$0R\u{a837d}\0<🕴?@,": true, "\u{2fa70}\\𪚅¯\u{108509}\u{4ab0e}\u{4ecc6}\u{bebaa}B\u{1}\\\u{f24f8}'%ð\u{202e}¥\u{3a476}/%Ⱥ\t$vyC]>+\u{87304}": [94, 245, 167, 7, 68, 250, 234, 108, 122, 64, 21, 238, 150, 215, 116, 48, 232, 184, 232, 43, 30, 213, 149, 183, 215, 1, 242, 95, 189, 228, 190, 9, 43, 222, 14, 254, 203, 238, 30, 51, 216, 40, 125, 87, 240, 71, 185, 206, 114, 87, 214, 85, 33, 163], "\u{373f7}": true, "\u{4f31f}*A?\\${\u{91276}\u{52eb1}#\u{b}\u{1b}": -47, "\u{5e975}🕴z${'\u{5d19a}b\u{aa904}'J}`Ѩ9": "\t=\u{1}±\u{84533}T*\u{4d0eb}\"A\u{8}<", "\u{69742}o\u{81}n<@\u{feff}L:\u{1041f7}🕴\u{8}𬾧\u{7f}\u{108007}?[\u{feff}\r": [31, 200, 160, 81, 110, 124, 249, 10, 85, 215, 192, 121, 17, 28, 185, 121], "\u{6cfa0}\u{6}𡽫{\u{feff}bJ&\u{5}`x\u{d2999}d&\u{badf8}\u{1b}\u{365dc}'�\u{5a37e}": null, "\u{a202e}~QE?\u{b}\u{7}/Ⱥ/\\&\u{fb894}\u{e0565}\u{2}�^?Ѩ'": [146, 41, 216, 116, 157, 252, 155, 192, 137, 113, 110, 231, 49, 142, 206, 2, 93, 201, 68, 48, 93, 58, 173, 197, 93, 40, 41, 112, 193, 244, 79, 228, 221, 71, 177, 129, 102, 97, 55, 137, 3, 148, 166, 101, 253, 166, 170, 139, 23, 120, 178, 140, 132, 104, 89, 46, 120, 30, 154, 194, 138, 39, 103, 152, 6, 136, 237, 241, 138, 193, 248, 33, 185, 56, 220, 118, 23, 17, 132, 220, 239, 90, 207, 237, 94, 75, 90, 222, 46, 180], "\u{b2ebe}*\u{84}.\u{a909e}7ѨjV𠷃Ⱥ?ü\u{6}u𝣒L\u{34303}:\u{bc294}?DZ\0\u{7f}/\u{103c3f}\t\\": bafkr4ihqz56mmzigglx6apxygvxcnreb7hjve6eftqfcooxtdzyzahyil4, "\u{c3588}*\"\u{8}\u{51cd1}躅\u{8f2d6}u🕴*\r\u{1}7🕴\u{71aa7}{\u{77c1d}%\u{cf015}\u{7a969}\u{8}\u{3cbf9}\u{92}\u{b}*\t\u{15db3}": "/{ªN?\u{8711e}=\0g{\u{202e}\u{202e}¥.\u{1b}H%\u{95}\0r%:", "\u{d7ffe}\u{44f52}\u{1b}\"%ỵ*m\u{5}$\r¥Ⱥ\u{1b}Uo':\r/%'<Ⱥ": -7, "\u{e0680}={\u{fd1e9}j<'CѨ�3\u{ce0a4}Ѩ\u{4b081}C\"`%9GGv\t\u{10322d};Ⱥ\\\u{e1a9c}\u{202e}\\": true, "\u{e5e60}\u{b}{\u{e3551}=\u{b5edf}<Ⱥ\"#?\u{7}\u{80}\\'5f=\":𪟤=\t\u{dc111}*<\u{346be}": true, "\u{ebf8a}Y\u{8}\u{f98c9}\\\u{7f}9🕴\u{4e9df}=\"t": [222, 226, 183, 11, 204, 93, 208, 118, 17, 243, 19, 124, 63, 137, 157, 195, 178, 3, 70, 223, 216, 164, 164, 66, 246, 151, 76, 100, 93, 11, 7, 234, 146, 38, 18, 169, 97, 221, 8, 133, 14, 197, 108, 213, 201, 214, 93, 202, 188, 165, 171, 86, 195, 223, 164, 6, 51, 234, 214, 26, 158, 32, 12, 35, 10, 245, 171], "\u{ffc11}#\u{b}¾": null, "\u{101fde}\u{b6522}'.&$": {"": 1.5658615101276272e160, "\u{2}=\u{5bd45}?o뗡p\u{2}\u{b}\\Ѩ¥\u{8b3a4}ib=": null, "\u{3}𤥺": null, "\u{6}¥\u{feff}%\u{202e}�Ѩ%\u{10df7a}$%\u{2}\u{b}\u{59972}𝝊\u{5107d}\u{202e}\0.k%V\u{55f7d}±": "`\u{7f}\u{4e0cf}\u{f7dab}\u{7f}zw[Ѩ.\u{b}Ⱥ\u{202e}\u{1b}#�\u{61e5a}\u{569c2}:\u{51a0f}\u{1b}*R\u{bb37d}:🕴{+Rf\u{ea9f}", "\u{7}Ѩ\u{1}\u{dca54}t": 17, "\t`": [25, 228, 9, 39, 85, 50, 121], "\t\u{2ef3e}\0": bafkrwibffxly7lxjvxuvp2ic3ao6f2icoflqqiadi5dvz4yvpdsyd27jne, "\r*\u{1b}\u{4}{`_\u{feff}\u{b}¥.K\t<\u{3}&\u{7}$🕴\u{7f}": -40, "\r\u{587e8}Ⱥ": [207, 53, 166, 39, 184, 202, 32, 200, 7, 155, 18, 5, 102, 226, 211, 93, 116, 183, 163, 131, 161, 249, 63, 112, 198, 231, 166, 86, 176, 31, 56, 43, 28, 142, 150, 56, 244, 64, 75, 60, 122, 214, 109, 138, 103, 217, 188, 127, 42, 7, 30, 47, 190, 51, 7, 110, 190, 106, 215, 102, 86, 94, 41, 76, 159, 221, 62, 236, 96, 83, 215, 26, 102, 233, 126, 117, 233, 43], "\"z=%`\r�ñ%:Ⱥ\u{ee132}.}Ue'\u{8}\u{a6c62}": bafyrwiefif6srseryrwhg7lx5ddm3cuzdwepr4rmvorftfhq2vj57d2gsq, "#\u{75c49}!": "\u{7f}\u{5a95d}L\u{32a81}\"{\u{7}¥²�\u{e3087}O'!\u{ab00c}\r./ \u{202e}h🕴'", "$\0\"\u{ca931}\u{2}\u{1}:\r\u{3}\u{63225}g¥Ñ": true, "$Ѩ\u{bd470}û54Ⱥ%¥\u{feff}": null, "$�0\u{a8b97}𱗶\\0\t$": 6.488444060884696e306, "&'\\w:<<%\u{feff}\u{5}\u{1b}`¥/?n𡚋&,4\u{5e1f3}\u{4b359}\u{fbe37}i\r\\\t:": true, "&?\u{6}r\u{c3665}\u{7f}i%": baguqefranz7i2sd2lryjkxvq7st4ujh3xjdttm6fldqgbci5rlbzwbet2dma, "&?%": bafykbzacea3napu4rwrzbqevwhiaptytsgo3gscof2glvjfje6h75nyc5npb6, "&\u{7f}\r}>`\"%\rY\")>\0\u{1b}X\u{b65b4}": [93, 89, 232, 156, 166, 193, 205, 122, 207, 235, 2, 186, 202, 177, 228, 245, 238, 222, 129, 191, 32, 176, 162, 238, 204, 162, 152, 39, 105, 236, 197, 76, 201, 44, 148, 170, 224, 150, 87, 94, 134, 189, 238, 51, 118, 124, 144, 5, 252, 27, 17, 111, 250, 236, 173, 159, 46, 45, 170, 32, 133, 60, 134, 50, 28, 47, 178, 197, 160, 126, 143, 31, 160, 25, 175], "'d'\u{4e27c}\0\u{b4b7c}`{?\u{3cff7}Â<\0": "h\u{feff}\u{15730}\u{c364f}]WV.TH\u{feff}.Z\0.\u{9d3ae}$\u{e5559}?\u{d403d}\u{7f}%(k𠢘", "'i🕴\tb{\u{7}\u{feff}&\u{49079}'S/\\\u{10600a}\u{af8aa}\u{3a6a6}\r": false, "'ѨH8'Ѩ{\u{f5f05}ѨQ\u{d5ba2}\u{3773e}::\u{eb168}:)CȺѨѨ": baguqehra3ml5lg6x5oaboqvr65pulvfntbvdrj6v5zyvuaf2j7bpx7wkhbpa, "*I¥w\u{b}\u{7b9a4}\u{3e747}\u{b2e81}/y<5'*S?$$O\"\u{7}\u{9f10a}\u{8}S\u{7a8bd}I\u{7f}U%": null, "*\u{202e}": [61, 242, 249, 132, 34, 222, 153], "*\u{861a1}//\u{10ca95}`'𓀒\\'=Q;\u{1073cb}M\u{5dbd9}": [34, 41, 251, 216, 124, 171, 190, 175, 12, 20, 65, 118, 149, 193, 33, 184, 96, 7, 181, 39, 92, 97, 36, 84, 72, 99, 69, 241, 55, 2, 31, 74, 55, 172, 146, 32, 230, 228, 234, 148, 164, 27, 55, 71, 222, 203, 70, 239, 15, 85, 157], ". \u{202e}v'*<\u{3c008}\u{ea8b}c¥?g:5$Y¥\u{5a3ec}": [60, 209, 45, 129, 120, 83, 199, 46, 198, 62, 131, 13, 210, 117, 41, 136, 9, 59, 142, 242, 198, 194, 181, 206, 19, 221, 114, 94, 216, 25, 58, 191, 30, 252, 131, 130, 133, 109, 116, 32, 231, 108, 160, 173, 195, 103, 78, 179, 165, 157, 227, 152, 245, 222, 164, 242, 208, 136, 66, 6, 54, 146], "/'%\u{3b4df}`𫨁e*\u{71a70}=\u{c5203}\rȺ궖JȺ/\u{4bb7c}\u{3}*#\u{d3bde}p\0qÙ\u{b}🕴\u{202e}\u{202e}r.": false, "/{H\u{10c9ea}\u{79be5}?\u{ca12d}": 8.6395723193706e-309, "4\u{54c08}'`?": -8.973748901720123e-29, "6\u{883eb}Ⱥ$\u{1b}": -1.0199828187670042e-159, ":<\u{1a04a}U�": -1.8179682492493788e-167, ":n\u{8a}\u{8}?𡜬\u{7f}F'ýr0\u{1edbd}%\r8oѨ\u{1b}": "", "<=🕴\u{dd6ce}\u{3}\u{65218}�\u{b3002}\u{81}<\\//z=\u{6106d}*Ѩ\u{8090a}hC\u{88ce1}\u{1b}\0🕴\u{ac262}\r/": null, "?*\u{a6066}aѨ\u{7f}\u{3}\u{1b}`'\u{aa717}\u{14b1c}o&\u{8d098}É?Sv\u{7f}¥`\0\0<]\u{15ce3}\u{1}=:$=": [66, 253, 182, 100, 225, 215, 132, 63, 183, 229, 8, 172, 23, 12, 49, 232, 47, 74, 175, 130, 83, 252, 88, 185, 110, 27, 188, 151, 251, 208, 54, 250, 230, 62, 210, 35, 23, 251, 21, 206, 161, 29, 185, 225, 190, 48, 125, 83, 49, 116, 98, 148, 46, 18, 204, 43, 23, 115, 8, 245, 113, 58, 152, 205, 222, 26, 169, 223, 244, 99, 43, 185, 3, 45, 50, 190, 66, 69, 188, 185, 150, 232, 128, 102, 195, 99, 78, 226, 251, 253, 84, 106, 110, 178, 192, 203], "?\u{7f}\u{b}\u{9f2c9}i\u{10bb8b}:{&8\r𠒫*t�\u{1}H\u{98f18}\" \u{c92b6}\u{8})\u{7d0c3}<Ï": false, "?\u{69bad}I&¥@🕴n\0??C\u{607cf}\u{7f}/&\u{b}🕴..qѨ\u{1b}": "𘉢`\0=\u{cc51f}\u{a4fb8}\0\u{47e89}/\u{b}\u{a0a22}±\\\u{feff}\u{feff}/.ck.\t\u{e3434}j", "A\u{e0fe6}'\u{202e}`%\u{202e}\0<\u{c18d9}/:\u{10b6bc}b\r*R:\u{feff}\u{3}\u{10ba3c}\u{84727}\u{c8ebd}^": -6, "C\u{c5e80}*g🖻\u{66e08}\u{9d567}{'Ѩ\u{58cb7}\u{9a1ca}¥Ⱥ\u{19181}\u{b1066}\u{5b8ee}\u{b9fcf}= =\u{dfbde}\u{18d8b}": "\u{5}\u{4d180}M\u{b}\\.Ⱥ*è=.e", "I*\u{96}\t=$\u{4747f}\u{5}\u{1b}n=a&rȺȺ\u{6cd13}": [230, 44], "R´\t\u{1}&\\\u{1b}kN\u{1}{´<\0\u{95a25}d": "k*`$U,\\\u{feff}\t\u{916a3}\u{849e2}<䨑\u{a2dd6}\u{9126c}", "Yq🕴\u{c5293}y:": 21, "Y\u{101e9f}JE{\t\u{7f}\0\u{4a162}aѨ.\u{7b388}댦\u{81d62}Ѩ\u{a6fb0}\\\u{dec90}\u{1}\r\u{b}/": null, "\\": -7.477551823968463e-129, "\\<\t\u{e9e4c}\u{b}:\u{a258b}¦\u{53415}5G\u{7}": true, "\\O\u{9e0a1}K.𠓎\u{fb34d}\u{7469e}\u{4c980}.:\u{bbb4f}8\"J:/\u{829d7}:\u{1}𣂇Ⱥ*J?\t\\\0\u{b}Ⱥ\u{ea85b}`": bafyrwihiw4vja2le5bbo63lbzgzdc7npl6b6ixw6nlheeirllwo5zpuzuy, "_]:\u{e11f7}¥On/<:\t=\u{1b}": "G\u{b}\u{71804}'🕴Ѩc\u{feff}<%Ѩo\u{1b}¥", "e\u{70c00}\0¥\u{202e}": [207, 96, 93, 148, 103, 198, 140, 36, 26, 221, 215, 6, 202, 231, 233, 247, 107, 178, 240, 144, 50, 206, 143, 13, 95, 15, 171, 239, 158, 75, 248, 192], "f\08<\u{9c37d}?\u{10f8f1}=`\u{95}Ⱥ{.\u{1b}\u{15cdd}\u{202e}Ⱥy<$\u{8}$\u{6c5d0}K\u{c4f04}\u{19f0a}": bafykbzacede6nne55uave2wj6epfrbud4xcozirijfysx2p3a4isvzez7oiza, "gÔ\u{b}繎.U": null, "i\u{889f5}r\u{e98e9}<Å\u{ad256}\u{b}R\r_%\u{d8e2e}/%r\u{b908c}%\rz~'\u{e04db}.\0\u{d9659}$\u{7cda4}`)": null, "u\u{a1658}0\u{f2355}}🕴\r": -7.035669443392254e-42, "v\u{10b659}�\r!o=\u{a24cd}]XÂf(\u{859d0}w\0.:": bafkrmihatl6qchcaitd2pcbxodwzlgbynl4rpzu6fnfcy3wgudnxpnabpm, "{%\u{61d33}=/EѨ/\"?�\u{5ca71}0\r": null, "{/:/JK\u{62214}Ⱥ¢\u{b}1o\u{5d859}\\\\\u{b65b6}*¥Ⱥ\u{1b}ѨѨ": bafk6bzacebf3fnemqzl52apgxmcok4gweclupgjrxwtee6ci75qsawcnyjuw6, "{:\u{f901d}\u{da30b}*:𱪙\rF\t\u{405de}\u{feff}%~\u{7f}*=\u{46e3e}\u{b}\u{202e}": true, "{f\\\u{b}\u{1}\u{7f}\tD\u{b}&Ⱥ𣡼\u{e84fe}J\u{d2ef7}E\u{1}'\0f\r\u{6}\u{4a79f}Dx\u{2}": -13, "{\u{d9a04}&ö%\u{7f}\u{d686e}&´": 7.588162654188097e-308, "\u{7f}\t\tj\tw\u{feff}Ѩ�練\u{3}:𧸨\u{a3acd}\u{101251}'\u{42a8f}E\u{3e47c}\u{7f}\u{7}&¥%<𩌯c": bafyobzaceco6clzsn57sjq2u5patyvukq3ksi4wyrztinj5srwspsi63qyfqc, "\u{7f}c\0\u{ffc41}<\r^Æ\u{9f}Ñ\u{b6f14}U\\\u{c45cd}\\\u{1b}g\u{ed9b9}🕴{\"R\u{60150}\u{b54c0}I:\u{10a33a}": [126, 247, 86, 174, 51, 26, 21, 148, 201, 8, 130, 49, 162, 197, 14, 242, 15, 142, 249, 226, 194, 131, 116, 158, 109, 70, 138, 10, 113, 85, 166, 21, 217, 49, 112], "\u{7f}\u{39cc5}'🕴\"\u{71cc2}&\u{7f}'": -4.297679451122769e-29, "Â\u{4}\u{feff}<'\u{c2f64}ѨȺ\\2\\\u{41534}*{<� IntoIterator for Named { impl FromIterator<(String, T)> for Named { fn from_iter>(iter: I) -> Self { - Named(iter.into_iter().collect()) + let btree: BTreeMap = iter.into_iter().collect(); + Named(btree) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 41d3048b..e3486eac 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,5 +1,6 @@ use super::policy::Predicate; use crate::ability::arguments::Named; +use crate::time; use crate::{ capsule::Capsule, crypto::{varsig, Nonce}, @@ -8,9 +9,11 @@ use crate::{ }; use core::str::FromStr; use derive_builder::Builder; +use did_url::DID; use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -112,8 +115,11 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload { - type Error = (); // FIXME +impl TryFrom> for Payload +where + ::Err: Debug, +{ + type Error = ParseError; fn try_from(args: Named) -> Result { let mut subject = None; @@ -130,30 +136,36 @@ impl TryFrom> for Payload { for (k, ipld) in args { match k.as_str() { "sub" => { - subject = Some( - match ipld { - Ipld::Null => None, - Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + subject = Some(match ipld { + Ipld::Null => None, + Ipld::String(s) => { + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - .ok_or(())?, - ) + bad => return Err(ParseError::WrongTypeForField("sub".to_string(), bad)), + }) } "iss" => match ipld { - Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("iss".to_string(), bad)), }, "aud" => match ipld { - Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + audience = + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("aud".to_string(), bad)), }, "via" => match ipld { - Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + via = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("via".to_string(), bad)), }, "cmd" => match ipld { Ipld::String(s) => command = Some(s), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("cmd".to_string(), bad)), }, "pol" => match ipld { Ipld::List(xs) => { @@ -162,59 +174,138 @@ impl TryFrom> for Payload { .map(|x| Predicate::try_from(x.clone()).ok()) .collect(); } - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("pol".to_string(), bad)), }, - "metadata" => match ipld { + "meta" => match ipld { Ipld::Map(m) => metadata = Some(m), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("meta".to_string(), bad)), }, "nonce" => match ipld { Ipld::Bytes(b) => nonce = Some(Nonce::from(b).into()), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("nonce".to_string(), bad)), }, "exp" => match ipld { - Ipld::Integer(i) => expiration = Some(Timestamp::try_from(i).map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => { + expiration = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?) + } + bad => return Err(ParseError::WrongTypeForField("exp".to_string(), bad)), }, "nbf" => match ipld { - Ipld::Integer(i) => not_before = Some(Timestamp::try_from(i).map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => { + not_before = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?) + } + bad => return Err(ParseError::WrongTypeForField("nbf".to_string(), bad)), }, - _ => (), + other => return Err(ParseError::UnknownField(other.to_string())), } } Ok(Payload { - subject, - issuer: issuer.ok_or(())?, - audience: audience.ok_or(())?, + subject: subject.ok_or(ParseError::MissingSub)?, + issuer: issuer.ok_or(ParseError::MissingIss)?, + audience: audience.ok_or(ParseError::MissingAud)?, via, - command: command.ok_or(())?, - policy: policy.ok_or(())?, - metadata: metadata.ok_or(())?, - nonce: nonce.ok_or(())?, - expiration: expiration.ok_or(())?, + command: command.ok_or(ParseError::MissingCmd)?, + policy: policy.ok_or(ParseError::MissingPol)?, + metadata: metadata.unwrap_or_default(), + nonce: nonce.ok_or(ParseError::MissingNonce)?, + expiration: expiration.ok_or(ParseError::MissingExp)?, not_before, }) } } +#[derive(Debug, Error)] +pub enum ParseError +where + ::Err: Debug, +{ + #[error("Unknown field: {0}")] + UnknownField(String), + + #[error("Missing sub field")] + MissingSub, + + #[error("Missing iss field")] + MissingIss, + + #[error("Missing aud field")] + MissingAud, + + #[error("Missing cmd field")] + MissingCmd, + + #[error("Missing pol field")] + MissingPol, + + #[error("Missing nonce field")] + MissingNonce, + + #[error("Missing exp field")] + MissingExp, + + #[error("Wrong type for field {0}: {1:?}")] + WrongTypeForField(String, Ipld), + + #[error("Cannot parse DID")] + DidParseError(::Err), + + #[error("Cannot parse timestamp: {0}")] + BadTimestamp(#[from] time::OutOfRangeError), +} + +impl From> for Ipld { + fn from(payload: Payload) -> Self { + let named: Named = payload.into(); + Ipld::Map(named.0) + } +} + +impl TryFrom for Payload +where + DID: Did + FromStr, + ::Err: Debug, +{ + type Error = TryFromIpldError; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + let named = Named::(map); + Payload::try_from(named).map_err(TryFromIpldError::MapParseError) + } + _ => Err(TryFromIpldError::NotAMap), + } + } +} + +#[derive(Debug, Error)] +pub enum TryFromIpldError +where + ::Err: Debug, +{ + NotAMap, + MapParseError(ParseError), +} + impl From> for Named { fn from(payload: Payload) -> Self { let mut args = Named::::from_iter([ - ("iss".to_string(), Ipld::from(payload.issuer.to_string())), - ("aud".to_string(), payload.audience.to_string().into()), - ("cmd".to_string(), payload.command.into()), + ("iss".to_string(), Ipld::String(payload.issuer.to_string())), ( - "pol".to_string(), - Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()), + "aud".to_string(), + Ipld::String(payload.audience.to_string()), ), + ("cmd".to_string(), Ipld::String(payload.command)), + ("pol".to_string(), { + Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()) + }), ("nonce".to_string(), payload.nonce.into()), ("exp".to_string(), payload.expiration.into()), ]); if let Some(subject) = payload.subject { - args.insert("sub".to_string(), Ipld::from(subject.to_string())); + args.insert("sub".to_string(), Ipld::String(subject.to_string())); } else { args.insert("sub".to_string(), Ipld::Null); } @@ -248,12 +339,13 @@ where Nonce::arbitrary(), Timestamp::arbitrary(), Option::::arbitrary(), - prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..5).prop_map(|m| { m.into_iter() .map(|(k, v)| (k, v.0)) .collect::>() }), prop::collection::vec(Predicate::arbitrary_with(pred_args), 0..10), + Option::::arbitrary(), ) .prop_map( |( @@ -266,6 +358,7 @@ where not_before, metadata, policy, + via, )| { Payload { issuer, @@ -277,9 +370,82 @@ where nonce, expiration, not_before, + via, } }, ) .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod serialization { + use super::*; + + #[test_log::test] + fn test_into_ipld() -> TestResult { + proptest!(ProptestConfig::with_cases(100), |(payload: Payload)| { + let named: Named = payload.clone().into(); + let sub = named.get("sub".into()); + + if let Some(ref subject) = payload.subject { + let sub_ipld = &Ipld::String(subject.to_string()); + pretty::assert_eq!(sub, Some(sub_ipld)); + } else { + pretty::assert_eq!(sub, Some(&Ipld::Null)); + } + }); + + // proptest! { + // #![proptest_config(ProptestConfig { + // cases: 100, .. ProptestConfig::default() + // })] + + // #[test_log::test] + // fn test_into_ipld(payload: Payload) { + // dbg!(payload.clone()); + + // prop_assert_eq!(payload.clone(), payload.clone()) + // } + + // // #[test_log::test] + // // fn test_roundtrip_ipld() -> TestResult { + // // Ok(()) + // // } + // } + + Ok(()) + } + + #[test_log::test] + fn test_from_ipld() -> TestResult { + Ok(()) + } + + #[test_log::test] + fn test_ipld_round_trip() -> TestResult { + proptest!(ProptestConfig::with_cases(1), |(payload: Payload)| { + let ipld: Ipld = payload.clone().into(); + let parsed = Payload::::try_from(ipld); + + dbg!(parsed); + + // assert_matches!(parsed, Ok(payload)); + }); + + Ok(()) + } + + #[test_log::test] + fn test_from_invalid_ipld() -> TestResult { + Ok(()) + } + } +} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index d222cc3e..0f65eacb 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -834,19 +834,17 @@ impl Arbitrary for Predicate { fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { let leaf = prop_oneof![ - Just(Predicate::True), - Just(Predicate::False), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Newtype::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), String::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) ]; diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 6d36c02b..aec6fd31 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -3,18 +3,31 @@ use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorErro use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::fmt; use std::str::FromStr; #[cfg(feature = "test_utils")] use proptest::prelude::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone)] pub struct Select { filters: Vec, _marker: std::marker::PhantomData, } -impl Select { +impl fmt::Debug for Select { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Select({:?})", self.filters) + } +} + +impl PartialEq for Select { + fn eq(&self, other: &Self) -> bool { + Selector(self.filters.clone()) == Selector(other.filters.clone()) + } +} + +impl Select { pub fn new(filters: Vec) -> Self { Self { filters, @@ -28,7 +41,9 @@ impl Select { { Selector(self.filters.clone()).is_related(&Selector(other.filters.clone())) } +} +impl Select { pub fn get(self, ctx: &Ipld) -> Result { self.filters .iter() @@ -129,23 +144,20 @@ impl FromStr for Select { } } -impl PartialOrd for Select { +impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) } } #[cfg(feature = "test_utils")] -impl Arbitrary for Select { - type Parameters = T::Parameters; +impl Arbitrary for Select { + type Parameters = (); type Strategy = BoxedStrategy; - fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_params).prop_map(Select::Pure), - // FIXME add params that make this actually correspond to data - prop::collection::vec(Filter::arbitrary(), 1..10).prop_map(Select::Get), - ] - .boxed() + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + prop::collection::vec(Filter::arbitrary(), 1..10) + .prop_map(Select::new) + .boxed() } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 0a1be8d8..4aefcc40 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -2,6 +2,9 @@ use super::Signature; use blst::BLST_ERROR; use did_url::DID; use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; +use multibase; +use multibase::Base; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; use serde::{Deserialize, Serialize}; use signature as sig; @@ -126,71 +129,56 @@ impl signature::Verifier for Verifier { impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Verifier::EdDsa(ed25519_pk) => write!( - f, - "did:key:z6Mk{}", - bs58::encode(ed25519_pk.to_bytes()).into_string() - ), - Verifier::Es256k(secp256k1_pk) => write!( - f, - "did:key:zQ3s{}", - bs58::encode(secp256k1_pk.to_sec1_bytes()).into_string() - ), + let inner = match self { + Verifier::EdDsa(ed25519_pk) => { + let mut bytes = ed25519_pk.to_bytes().to_vec(); + let mut inner = vec![0xed]; + inner.append(&mut bytes); + multibase::encode(Base::Base58Btc, inner) + } + Verifier::Es256k(secp256k1_pk) => { + let mut bytes = secp256k1_pk.to_sec1_bytes().to_vec(); + let mut inner = vec![0xed]; + inner.append(&mut bytes); + multibase::encode(Base::Base58Btc, inner) + } Verifier::P256(p256_key) => { - write!( - f, - "did:key:zDn{}", - bs58::encode(p256_key.to_sec1_bytes()).into_string() - ) + multibase::encode(Base::Base58Btc, p256_key.to_sec1_bytes()) } - Verifier::P384(p384_key) => write!( - f, - "did:key:z82{}", - bs58::encode(p384_key.to_sec1_bytes()).into_string() - ), - Verifier::P521(p521_key) => write!( - f, - "did:key:z2J9{}", - bs58::encode(p521_key.0.to_encoded_point(true).as_bytes()).into_string() + Verifier::P384(p384_key) => { + multibase::encode(Base::Base58Btc, p384_key.to_sec1_bytes()) + } + Verifier::P521(p521_key) => multibase::encode( + Base::Base58Btc, + p521_key.0.to_encoded_point(true).as_bytes(), ), Verifier::Rs256(rsa2048_key) => { - write!( - f, - "did:key:z4MX{}", - bs58::encode( - rsa2048_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes() - ) - .into_string() - ) - } - Verifier::Rs512(rsa4096_key) => write!( - f, - "did:key:zgg{}", - bs58::encode( - rsa4096_key + multibase::encode( + Base::Base58Btc, + rsa2048_key .0 .to_pkcs1_der() .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes() + .as_bytes(), ) - .into_string() - ), - Verifier::BlsMinPk(bls_minpk_pk) => write!( - f, - "did:key:zUC7{}", - bs58::encode(bls_minpk_pk.serialize()).into_string() - ), - Verifier::BlsMinSig(bls_minsig_pk) => write!( - f, - "did:key:zUC7{}", - bs58::encode(bls_minsig_pk.serialize()).into_string() + } + Verifier::Rs512(rsa4096_key) => multibase::encode( + Base::Base58Btc, + rsa4096_key + .0 + .to_pkcs1_der() + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail + .as_bytes(), ), - } + Verifier::BlsMinPk(bls_minpk_pk) => { + multibase::encode(Base::Base58Btc, bls_minpk_pk.serialize()) + } + Verifier::BlsMinSig(bls_minsig_pk) => { + multibase::encode(Base::Base58Btc, bls_minsig_pk.serialize()) + } + }; + + write!(f, "did:key:{}", inner) } } @@ -203,83 +191,115 @@ impl FromStr for Verifier { return Err(FromStrError::TooShort); } - match s.split_at(9) { - ("did:key:z", more) => { - let bytes = more.as_bytes(); - match bytes.split_at(2) { + match s.split_at(8) { + ("did:key:", more) => { + let (_base, varint_bytes): (multibase::Base, Vec) = + multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; + + let bytes = varint_bytes.as_slice(); + + // FIXME also check max length on bytes + match varint_bytes.split_at(2) { ([0xed, _], _) => { - let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + let mut buf = vec![]; + + let mut working: Vec = varint_bytes[1..].to_vec(); + + while !working.is_empty() { + let (x, xs) = + unsigned_varint::decode::u8(working.as_slice()).expect("FIXME"); + buf.push(x); + working = xs.to_vec(); + } + + dbg!(buf.clone().len()); + + let vk = ed25519_dalek::VerifyingKey::try_from(buf.as_slice()) .map_err(FromStrError::CannotParseEdDsa)?; - return Ok(Verifier::EdDsa(vk)); + Ok(Verifier::EdDsa(vk)) } ([0xe7, _], _) => { let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) .map_err(FromStrError::CannotParseEs256k)?; - return Ok(Verifier::Es256k(vk)); + Ok(Verifier::Es256k(vk)) } ([0x12, 0x00], key_bytes) => { let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP256)?; - return Ok(Verifier::P256(vk)); + Ok(Verifier::P256(vk)) } ([0x12, 0x01], key_bytes) => { let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP384)?; - return Ok(Verifier::P384(vk)); + Ok(Verifier::P384(vk)) } ([0x12, 0x02], key_bytes) => { let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP521)?; - return Ok(Verifier::P521(es512::VerifyingKey(vk))); + Ok(Verifier::P521(es512::VerifyingKey(vk))) } ([0x12, 0x05], key_bytes) => match key_bytes.len() { 2048 => { let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) .map_err(FromStrError::CannotParseRs256)?; - return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + Ok(Verifier::Rs256(rs256::VerifyingKey(vk))) } 4096 => { let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) .map_err(FromStrError::CannotParseRs512)?; - return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + Ok(Verifier::Rs512(rs512::VerifyingKey(vk))) } - word => return Err(FromStrError::NotADidKey(word)), + word => Err(FromStrError::NotADidKey(word)), }, ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { 48 => { let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) .map_err(FromStrError::CannotParseBlsMinPk)?; - return Ok(Verifier::BlsMinPk(pk)); + Ok(Verifier::BlsMinPk(pk)) } 96 => { let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) .map_err(FromStrError::CannotParseBlsMinSig)?; - return Ok(Verifier::BlsMinSig(pk)); + Ok(Verifier::BlsMinSig(pk)) } - word => return Err(FromStrError::UnexpectedPrefix([word].into())), + word => Err(FromStrError::UnexpectedPrefix([word].into())), }, - (word, _) => { - return Err(FromStrError::UnexpectedPrefix( - word.iter().map(|u| u.clone().into()).collect(), - )); - } + (word, _) => Err(FromStrError::UnexpectedPrefix( + word.iter().map(|u| u.clone().into()).collect(), + )), } } - (s, _) => { - return Err(FromStrError::UnexpectedPrefix( - s.to_string().chars().map(|u| u as usize).collect(), - )); - } + (s, _) => Err(FromStrError::UnexpectedPrefix( + s.to_string().chars().map(|u| u as usize).collect(), + )), + } + } +} + +impl From for Ipld { + fn from(v: Verifier) -> Self { + v.to_string().into() + } +} + +impl TryFrom for Verifier { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::String(s) = ipld { + Verifier::from_str(&s).map_err(|_| ()) + } else { + Err(()) } } } @@ -321,6 +341,46 @@ pub enum FromStrError { #[error("cannot parse BLS min sig key: {0:?}")] CannotParseBlsMinSig(BLST_ERROR), + + #[error("cannot decode multibase")] + CannotDecodeMultibase, +} + +impl PartialEq for FromStrError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (FromStrError::NotADidKey(a), FromStrError::NotADidKey(b)) => a == b, + (FromStrError::UnexpectedPrefix(a), FromStrError::UnexpectedPrefix(b)) => a == b, + (FromStrError::TooShort, FromStrError::TooShort) => true, + (FromStrError::CannotParseEdDsa(a), FromStrError::CannotParseEdDsa(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseEs256k(a), FromStrError::CannotParseEs256k(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP256(a), FromStrError::CannotParseP256(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP384(a), FromStrError::CannotParseP384(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP521(a), FromStrError::CannotParseP521(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseRs256(a), FromStrError::CannotParseRs256(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseRs512(a), FromStrError::CannotParseRs512(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseBlsMinPk(a), FromStrError::CannotParseBlsMinPk(b)) => a == b, + (FromStrError::CannotParseBlsMinSig(a), FromStrError::CannotParseBlsMinSig(b)) => { + a == b + } + (FromStrError::CannotDecodeMultibase, FromStrError::CannotDecodeMultibase) => true, + _ => false, + } + } } impl Serialize for Verifier { @@ -358,35 +418,61 @@ impl Arbitrary for Verifier { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { // NOTE these are just the test vectors from `did:key` v0.7 prop_oneof![ - // did:key - Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), - - // secp256k1 - Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), - Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), - Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), - - // BLS - Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), - Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), - - // P-256 - Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), - Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), - - // P-384 - Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), - Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), - - // P-521 - Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), - Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), - - // RSA-2048 - Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + // ed25519 + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + // secp256k1 + // Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + // Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + // Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // // BLS + // Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + // Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // // P-256 + // Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + // Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // // P-384 + // Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + // Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // // P-521 + // Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + // Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // // RSA-2048 + // Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // // RSA-4096 + // Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ] + .prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")) + .boxed() + } +} - // RSA-4096 - Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") - ].prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")).boxed() +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod serialization { + use super::*; + + #[test_log::test] + fn test_string_round_trip() -> TestResult { + proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { + dbg!(v.clone()); + dbg!(v.to_string()); + let s = v.to_string(); + let observed = Verifier::from_str(&s); + pretty::assert_eq!(Ok(v), observed); + }); + Ok(()) + } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index 0b590412..635540f7 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -2,9 +2,13 @@ use super::key; use super::Did; use did_url::DID; use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// The set of [`Did`] types that ship with this library ("presets"). #[derive(Debug, Clone, EnumAsInner, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] #[serde(untagged)] @@ -15,6 +19,24 @@ pub enum Verifier { // FIXME Dns(did_url::DID), } +impl From for Ipld { + fn from(verifier: Verifier) -> Self { + match verifier { + Verifier::Key(verifier) => verifier.into(), + } + } +} + +impl TryFrom for Verifier { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + key::Verifier::try_from(ipld) + .map(Verifier::Key) + .map_err(|_| ()) + } +} + impl From for DID { fn from(verifier: Verifier) -> Self { match verifier { @@ -73,3 +95,17 @@ impl FromStr for Verifier { key::Verifier::from_str(s).map(Verifier::Key) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Verifier { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + key::Verifier::arbitrary().prop_map(Verifier::Key), + // FIXME did_url::DID::arbitrary().prop_map(Verifier::Dns), + ] + .boxed() + } +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index daf0ba2d..a4e5f7fd 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -608,13 +608,10 @@ mod tests { server: crate::did::preset::Verifier, server_signer: crate::did::preset::Signer, device: crate::did::preset::Verifier, - device_signer: crate::did::preset::Signer, dnslink: crate::did::preset::Verifier, - dnslink_signer: crate::did::preset::Signer, } fn setup_test_chain() -> Result> { - let (_nbf, now, exp) = setup_valid_time(); let (server, server_signer) = gen_did(); let (account, account_signer) = gen_did(); let (device, device_signer) = gen_did(); @@ -729,9 +726,7 @@ mod tests { server, server_signer, device, - device_signer, dnslink, - dnslink_signer, }) } diff --git a/src/ipld/number.rs b/src/ipld/number.rs index aced2a53..16c66d71 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -36,7 +36,10 @@ impl PartialOrd for Number { impl From for Ipld { fn from(number: Number) -> Self { - number.into() + match number { + Number::Float(f) => Ipld::Float(f), + Number::Integer(i) => Ipld::Integer(i), + } } } diff --git a/src/lib.rs b/src/lib.rs index 1c21c44a..a793a1ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![warn( - missing_debug_implementations, + // FIXME missing_debug_implementations, future_incompatible, let_underscore, - missing_docs, + // FIXME missing_docs, rust_2021_compatibility, nonstandard_style )] diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 2e0f60c4..e6b94e03 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -175,6 +175,11 @@ impl Arbitrary for Timestamp { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - any::().prop_map(Timestamp::postel).boxed() + (0..(u64::pow(2, 53) - 1)) + .prop_map(|secs| { + Timestamp::new(UNIX_EPOCH + Duration::from_secs(secs)) + .expect("the current time to be somtime in the 3rd millenium CE") + }) + .boxed() } } From 507507e03b648fe2f31e72a228f635deb7260a61 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 04:13:28 -0700 Subject: [PATCH 218/234] DID key test vectors all succeed --- proptest-regressions/did/key/verifier.txt | 3 + src/did/key/verifier.rs | 229 +++++++++++++--------- 2 files changed, 138 insertions(+), 94 deletions(-) diff --git a/proptest-regressions/did/key/verifier.txt b/proptest-regressions/did/key/verifier.txt index 875277c2..a7844e07 100644 --- a/proptest-regressions/did/key/verifier.txt +++ b/proptest-regressions/did/key/verifier.txt @@ -5,3 +5,6 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 8b31cbe3dc5bbf493b0a5297de083508d8a9d06338cf64ddad5fd31cdd3f2995 # shrinks to v = EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })) +cc 6ea19e6cd197da3336d5d42920c4e50ec90c702cae2ae5eea9ab5551cb46b702 # shrinks to v = Es256k(VerifyingKey { inner: PublicKey { point: AffinePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([1160505078199757, 2400895128406859, 1056877774124305, 252036312840791, 148760852692386]), magnitude: 1, normalized: true }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([1672816167198465, 3802075178366285, 1757485316837790, 3300793713825435, 65287802880411]), magnitude: 1, normalized: true }), infinity: 0 } } }) +cc b13317f06a1831e069511de070b2052b483170979a76512a545fc48786999a88 # shrinks to v = P521(VerifyingKey) +cc 49cfb2af63ad904088cd463d88858fec93d6eee6cb3e9f1e6dea921ed3bc3036 # shrinks to v = BlsMinSig(PublicKey { point: blst_p2_affine { x: blst_fp2 { fp: [blst_fp { l: [9365464022634913364, 2329345083104304319, 3580502722068130739, 9972366962423794647, 10538782185862554521, 127471334147109992] }, blst_fp { l: [17485101970769344385, 9326044252933771301, 8127522796136333012, 5303128957032042441, 15450070391313250774, 324970645356438016] }] }, y: blst_fp2 { fp: [blst_fp { l: [15398138763294838789, 13417264315786022471, 12534786867121880739, 17975267190571784400, 7951956564642531166, 1555059392695113505] }, blst_fp { l: [2922977713681283026, 6798107204462524282, 8480534491177560369, 8514377350654754744, 15498869007308154143, 1289849395897073958] }] } } }) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 4aefcc40..16563cb3 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -131,50 +131,96 @@ impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let inner = match self { Verifier::EdDsa(ed25519_pk) => { - let mut bytes = ed25519_pk.to_bytes().to_vec(); - let mut inner = vec![0xed]; - inner.append(&mut bytes); - multibase::encode(Base::Base58Btc, inner) + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xed, &mut buf); + + let mut payload: Vec = tag.to_vec(); + let bytes = ed25519_pk.to_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::Es256k(secp256k1_pk) => { - let mut bytes = secp256k1_pk.to_sec1_bytes().to_vec(); - let mut inner = vec![0xed]; - inner.append(&mut bytes); - multibase::encode(Base::Base58Btc, inner) + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xe7, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = secp256k1_pk.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::P256(p256_key) => { - multibase::encode(Base::Base58Btc, p256_key.to_sec1_bytes()) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1200, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = p256_key.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::P384(p384_key) => { - multibase::encode(Base::Base58Btc, p384_key.to_sec1_bytes()) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1201, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = p384_key.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) + } + Verifier::P521(p521_key) => { + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1202, &mut buf); + + let mut payload = tag.to_vec(); + let raw = p521_key.0.to_encoded_point(true); + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) } - Verifier::P521(p521_key) => multibase::encode( - Base::Base58Btc, - p521_key.0.to_encoded_point(true).as_bytes(), - ), Verifier::Rs256(rsa2048_key) => { - multibase::encode( - Base::Base58Btc, - rsa2048_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes(), - ) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1205, &mut buf); + + let mut payload = tag.to_vec(); + let raw = rsa2048_key.0.to_pkcs1_der().map_err(|_| std::fmt::Error)?; // NOTE: technically should never fail + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) + } + Verifier::Rs512(rsa4096_key) => { + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1205, &mut buf); + + let mut payload = tag.to_vec(); + let raw = rsa4096_key.0.to_pkcs1_der().map_err(|_| std::fmt::Error)?; // NOTE: technically should never fail + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) } - Verifier::Rs512(rsa4096_key) => multibase::encode( - Base::Base58Btc, - rsa4096_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes(), - ), Verifier::BlsMinPk(bls_minpk_pk) => { - multibase::encode(Base::Base58Btc, bls_minpk_pk.serialize()) + let bytes = bls_minpk_pk.compress(); + + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xeb, &mut buf); + + let mut payload = tag.to_vec(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::BlsMinSig(bls_minsig_pk) => { - multibase::encode(Base::Base58Btc, bls_minsig_pk.serialize()) + let bytes = bls_minsig_pk.compress(); + + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xeb, &mut buf); + + let mut payload = tag.to_vec(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } }; @@ -196,92 +242,80 @@ impl FromStr for Verifier { let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; - let bytes = varint_bytes.as_slice(); + let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes) + .map_err(|_| FromStrError::CannotDecodeMultibase)?; // FIXME also check max length on bytes - match varint_bytes.split_at(2) { - ([0xed, _], _) => { - let mut buf = vec![]; - - let mut working: Vec = varint_bytes[1..].to_vec(); + match tag { + 0xed => { + let arr: [u8; 32] = rest.try_into().map_err(|_| FromStrError::TooShort)?; - while !working.is_empty() { - let (x, xs) = - unsigned_varint::decode::u8(working.as_slice()).expect("FIXME"); - buf.push(x); - working = xs.to_vec(); - } - - dbg!(buf.clone().len()); - - let vk = ed25519_dalek::VerifyingKey::try_from(buf.as_slice()) + let vk = ed25519_dalek::VerifyingKey::from_bytes(&arr) .map_err(FromStrError::CannotParseEdDsa)?; Ok(Verifier::EdDsa(vk)) } - ([0xe7, _], _) => { - let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + 0xe7 => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&rest) .map_err(FromStrError::CannotParseEs256k)?; Ok(Verifier::Es256k(vk)) } - ([0x12, 0x00], key_bytes) => { - let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1200 => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP256)?; Ok(Verifier::P256(vk)) } - ([0x12, 0x01], key_bytes) => { - let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1201 => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP384)?; Ok(Verifier::P384(vk)) } - ([0x12, 0x02], key_bytes) => { - let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1202 => { + let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP521)?; Ok(Verifier::P521(es512::VerifyingKey(vk))) } - ([0x12, 0x05], key_bytes) => match key_bytes.len() { - 2048 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + 0x1205 => match rest.len() { + // 256-bytes plus params + 270 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(rest) .map_err(FromStrError::CannotParseRs256)?; Ok(Verifier::Rs256(rs256::VerifyingKey(vk))) } - 4096 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + // 512-bytes plus params + 526 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(rest) .map_err(FromStrError::CannotParseRs512)?; Ok(Verifier::Rs512(rs512::VerifyingKey(vk))) } - word => Err(FromStrError::NotADidKey(word)), + len => Err(FromStrError::InvalidRsaLength(len)), }, - ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 0xeb => match rest.len() { 48 => { - let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + let pk = blst::min_pk::PublicKey::deserialize(rest) .map_err(FromStrError::CannotParseBlsMinPk)?; Ok(Verifier::BlsMinPk(pk)) } 96 => { - let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + let pk = blst::min_sig::PublicKey::deserialize(rest) .map_err(FromStrError::CannotParseBlsMinSig)?; Ok(Verifier::BlsMinSig(pk)) } - word => Err(FromStrError::UnexpectedPrefix([word].into())), + len => Err(FromStrError::InvalidBlsLength(len)), }, - (word, _) => Err(FromStrError::UnexpectedPrefix( - word.iter().map(|u| u.clone().into()).collect(), - )), + word => Err(FromStrError::UnexpectedPrefix(word)), } } - (s, _) => Err(FromStrError::UnexpectedPrefix( - s.to_string().chars().map(|u| u as usize).collect(), - )), + (s, _) => Err(FromStrError::UnexpectedHeader(s.to_string())), } } } @@ -310,7 +344,16 @@ pub enum FromStrError { NotADidKey(usize), #[error("unexpected prefix: {0:?}")] - UnexpectedPrefix(Vec), + UnexpectedPrefix(u16), + + #[error("unexpected header: {0}")] + UnexpectedHeader(String), + + #[error("unexpected BLS length: {0}")] + InvalidBlsLength(usize), + + #[error("Invalid RSA length: {0}")] + InvalidRsaLength(usize), #[error("key too short")] TooShort, @@ -421,31 +464,31 @@ impl Arbitrary for Verifier { // ed25519 Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), // secp256k1 - // Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), - // Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), - // Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), - // // BLS - // Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), - // Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), - // // P-256 - // Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), - // Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), - // // P-384 - // Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), - // Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), - // // P-521 - // Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), - // Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), - // // RSA-2048 - // Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), - // // RSA-4096 - // Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") ] .prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")) .boxed() @@ -466,8 +509,6 @@ mod tests { #[test_log::test] fn test_string_round_trip() -> TestResult { proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { - dbg!(v.clone()); - dbg!(v.to_string()); let s = v.to_string(); let observed = Verifier::from_str(&s); pretty::assert_eq!(Ok(v), observed); From 3bba844f80c46fdb66fb43025a088b398c9a2a15 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 11:43:01 -0700 Subject: [PATCH 219/234] Manualy test all DID test vectors --- src/did/key/verifier.rs | 127 +++++++++++++++++++++++++++++++------- src/invocation.rs | 2 +- src/invocation/payload.rs | 5 -- src/invocation/store.rs | 4 +- src/receipt.rs | 2 +- 5 files changed, 109 insertions(+), 31 deletions(-) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 16563cb3..e120d05b 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -155,8 +155,8 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1200, &mut buf); let mut payload = tag.to_vec(); - let bytes = p256_key.to_sec1_bytes(); - payload.extend_from_slice(&bytes); + let point = p256_key.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); multibase::encode(Base::Base58Btc, payload) } @@ -165,8 +165,8 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1201, &mut buf); let mut payload = tag.to_vec(); - let bytes = p384_key.to_sec1_bytes(); - payload.extend_from_slice(&bytes); + let point = p384_key.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); multibase::encode(Base::Base58Btc, payload) } @@ -175,8 +175,10 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1202, &mut buf); let mut payload = tag.to_vec(); - let raw = p521_key.0.to_encoded_point(true); - payload.extend_from_slice(raw.as_bytes()); + let point = p521_key.0.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); + + dbg!(&payload); multibase::encode(Base::Base58Btc, payload) } @@ -239,11 +241,15 @@ impl FromStr for Verifier { match s.split_at(8) { ("did:key:", more) => { - let (_base, varint_bytes): (multibase::Base, Vec) = - multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; + match multibase::decode(more) { + Ok(_) => {} + Err(_) => { + dbg!(more); + } + } + let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more)?; - let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes) - .map_err(|_| FromStrError::CannotDecodeMultibase)?; + let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes)?; // FIXME also check max length on bytes match tag { @@ -385,8 +391,11 @@ pub enum FromStrError { #[error("cannot parse BLS min sig key: {0:?}")] CannotParseBlsMinSig(BLST_ERROR), - #[error("cannot decode multibase")] - CannotDecodeMultibase, + #[error("cannot decode multibase: {0}")] + CannotDecodeMultibase(#[from] multibase::Error), + + #[error("cannot parse tag: {0}")] + CannotParseTag(#[from] unsigned_varint::decode::Error), } impl PartialEq for FromStrError { @@ -420,7 +429,11 @@ impl PartialEq for FromStrError { (FromStrError::CannotParseBlsMinSig(a), FromStrError::CannotParseBlsMinSig(b)) => { a == b } - (FromStrError::CannotDecodeMultibase, FromStrError::CannotDecodeMultibase) => true, + ( + FromStrError::CannotDecodeMultibase(lhs), + FromStrError::CannotDecodeMultibase(rhs), + ) => lhs == rhs, + (FromStrError::CannotParseTag(lhs), FromStrError::CannotParseTag(rhs)) => lhs == rhs, _ => false, } } @@ -463,6 +476,7 @@ impl Arbitrary for Verifier { prop_oneof![ // ed25519 Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + // secp256k1 Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), @@ -498,22 +512,91 @@ impl Arbitrary for Verifier { #[cfg(test)] mod tests { use super::*; - use assert_matches::assert_matches; use pretty_assertions as pretty; - use proptest::prelude::*; use testresult::TestResult; mod serialization { use super::*; - #[test_log::test] - fn test_string_round_trip() -> TestResult { - proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { - let s = v.to_string(); - let observed = Verifier::from_str(&s); - pretty::assert_eq!(Ok(v), observed); - }); + fn roundtrip(s: &str) -> TestResult { + let v = Verifier::from_str(s)?; + let serialized = v.to_string(); + pretty::assert_eq!(s, serialized); Ok(()) } + + #[test_log::test] + fn test_ed25519_parse() -> TestResult { + roundtrip("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK") + } + + #[test_log::test] + fn test_secp256k_1_parse() -> TestResult { + roundtrip("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme") + } + + #[test_log::test] + fn test_secp256k_2_parse() -> TestResult { + roundtrip("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2") + } + + #[test_log::test] + fn test_secp256k_3_parse() -> TestResult { + roundtrip("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N") + } + + #[test_log::test] + fn test_bls_1_parse() -> TestResult { + roundtrip("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY") + } + + #[test_log::test] + fn test_bls_2_parse() -> TestResult { + roundtrip("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW") + } + + #[test_log::test] + fn test_p256_1_parse() -> TestResult { + roundtrip("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169") + } + + #[test_log::test] + fn test_p256_2_parse() -> TestResult { + roundtrip("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv") + } + + #[test_log::test] + fn test_p384_1_parse() -> TestResult { + roundtrip( + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + ) + } + + #[test_log::test] + fn test_p384_2_parse() -> TestResult { + roundtrip( + "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54", + ) + } + + #[test_log::test] + fn test_p521_1_parse() -> TestResult { + roundtrip("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7") + } + + #[test_log::test] + fn test_p521_2_parse() -> TestResult { + roundtrip("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f") + } + + #[test_log::test] + fn test_rs256_parse() -> TestResult { + roundtrip("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i") + } + + #[test_log::test] + fn test_rs512_parse() -> TestResult { + roundtrip("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + } } } diff --git a/src/invocation.rs b/src/invocation.rs index 1fa3a899..4c486070 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -154,7 +154,7 @@ impl, C: Codec + TryFrom + Into> did for Invocation { fn verifier(&self) -> &DID { - &self.verifier() + &self.payload.verifier() } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a16c77fb..40300b89 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -502,7 +502,6 @@ impl TryFrom> for Payload TryFrom> for Payload audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), _ => return Err(()), }, - "via" => match v { - Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), - }, "cmd" => match v { Ipld::String(s) => command = Some(s), _ => return Err(()), diff --git a/src/invocation/store.rs b/src/invocation/store.rs index b7acbd36..26618909 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -40,7 +40,7 @@ impl< cid: Cid, ) -> Result>, >::InvocationStoreError> { - (*self).get(cid) + (**self).get(cid) } fn put( @@ -48,7 +48,7 @@ impl< cid: Cid, invocation: Invocation, ) -> Result<(), >::InvocationStoreError> { - (*self).put(cid, invocation) + (**self).put(cid, invocation) } } diff --git a/src/receipt.rs b/src/receipt.rs index 38fbf4dd..537d5335 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -41,7 +41,7 @@ impl, C: Codec + TryFrom + Into did::Verifiable for Receipt { fn verifier(&self) -> &DID { - &self.verifier() + &self.payload.verifier() } } From 2ef67b3e55874aa08f8fec048e6ed606789eb84e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 14:17:49 -0700 Subject: [PATCH 220/234] working on filter parsing edge cases with proptests --- Cargo.toml | 3 +- .../delegation/policy/selector/filter.txt | 7 ++ src/delegation/payload.rs | 21 +++-- src/delegation/policy.rs | 2 +- src/delegation/policy/predicate.rs | 90 +++++++++++++----- src/delegation/policy/selector.rs | 29 +++++- src/delegation/policy/selector/filter.rs | 94 ++++++++++++++++--- src/delegation/policy/selector/select.rs | 9 +- src/did/key/verifier.rs | 9 -- src/ipld/number.rs | 9 +- 10 files changed, 210 insertions(+), 63 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector/filter.txt diff --git a/Cargo.toml b/Cargo.toml index 770279b8..3df1b306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ aquamarine = { version = "0.5", optional = true } # Encoding multibase = "0.9" base64 = "0.21" +nom = "7.1" +nom-unicode = "0.3" # Crypto blst = { version = "0.3.11", optional = true, default-features = false } @@ -58,7 +60,6 @@ libipld = { version = "0.16", optional = true } libipld-cbor = "0.16" libipld-core = { version = "0.16", features = ["serde-codec"] } multihash = { version = "0.18" } -nom = "7.1" nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } diff --git a/proptest-regressions/delegation/policy/selector/filter.txt b/proptest-regressions/delegation/policy/selector/filter.txt new file mode 100644 index 00000000..a478d733 --- /dev/null +++ b/proptest-regressions/delegation/policy/selector/filter.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e85947ad7c94a9c29fc57391d4e52cc023b1648997f55f55b2e980afaa3616db # shrinks to filter = ArrayIndex(0) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index e3486eac..5b7f4901 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::policy::Predicate; +use super::policy::{predicate, Predicate}; use crate::ability::arguments::Named; use crate::time; use crate::{ @@ -169,10 +169,14 @@ where }, "pol" => match ipld { Ipld::List(xs) => { - policy = xs - .iter() - .map(|x| Predicate::try_from(x.clone()).ok()) - .collect(); + let result: Result, ParseError> = + xs.iter().try_fold(vec![], |mut acc, ipld| { + let pred = Predicate::try_from(ipld.clone())?; + acc.push(pred); + Ok(acc) + }); + + policy = Some(result?); } bad => return Err(ParseError::WrongTypeForField("pol".to_string(), bad)), }, @@ -252,6 +256,9 @@ where #[error("Cannot parse timestamp: {0}")] BadTimestamp(#[from] time::OutOfRangeError), + + #[error("Cannot parse policy predicate: {0}")] + InvalidPolicy(#[from] predicate::FromIpldError), } impl From> for Ipld { @@ -435,9 +442,7 @@ mod tests { let ipld: Ipld = payload.clone().into(); let parsed = Payload::::try_from(ipld); - dbg!(parsed); - - // assert_matches!(parsed, Ok(payload)); + assert_matches!(parsed, Ok(payload)); }); Ok(()) diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index ae88cdef..2bbc2abe 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -6,5 +6,5 @@ pub mod selector; -mod predicate; +pub mod predicate; pub use predicate::*; diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 0f65eacb..abfb96a5 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -5,6 +5,7 @@ use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -692,7 +693,7 @@ pub fn glob(input: &String, pattern: &String) -> bool { } impl TryFrom for Predicate { - type Error = (); // FIXME + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { @@ -703,58 +704,71 @@ impl TryFrom for Predicate { } [Ipld::String(op_str), Ipld::String(sel_str), val] => match op_str.as_str() { "==" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidIpldSelector)?; Ok(Predicate::Equal(sel, ipld::Newtype(val.clone()))) } ">" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; - let num = ipld::Number::try_from(val.clone())?; Ok(Predicate::GreaterThan(sel, num)) } ">=" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; Ok(Predicate::GreaterThanOrEqual(sel, num)) } "<" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; + Ok(Predicate::LessThan(sel, num)) } "<=" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; + Ok(Predicate::LessThanOrEqual(sel, num)) } "like" => { - let sel = Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidStringSelector)?; + if let Ipld::String(s) = val { Ok(Predicate::Like(sel, s.to_string())) } else { - Err(()) + Err(FromIpldError::NotAString(val.clone())) } } "every" => { let sel = Select::::from_str(sel_str.as_str()) - .map_err(|_| ())?; + .map_err(FromIpldError::InvalidCollectionSelector)?; let p = Box::new(Predicate::try_from(val.clone())?); Ok(Predicate::Every(sel, p)) } "some" => { let sel = Select::::from_str(sel_str.as_str()) - .map_err(|_| ())?; + .map_err(FromIpldError::InvalidCollectionSelector)?; + let p = Box::new(Predicate::try_from(val.clone())?); Ok(Predicate::Some(sel, p)) } - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedTripleTag(op_str.to_string())), }, [Ipld::String(op_str), lhs, rhs] => match op_str.as_str() { "and" => { @@ -767,15 +781,45 @@ impl TryFrom for Predicate { let rhs = Box::new(Predicate::try_from(rhs.clone())?); Ok(Predicate::Or(lhs, rhs)) } - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedTripleTag(op_str.to_string())), }, - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedShape), }, - _ => Err(()), + _ => Err(FromIpldError::NotATuple(ipld)), } } } +#[derive(Debug, PartialEq, Error)] +pub enum FromIpldError { + #[error("Invalid Ipld selector {0:?}")] + InvalidIpldSelector( as FromStr>::Err), + + #[error("Invalid ipld::Number selector {0:?}")] + InvalidNumberSelector( as FromStr>::Err), + + #[error("Invalid ipld::Collection selector {0:?}")] + InvalidCollectionSelector( as FromStr>::Err), + + #[error("Invalid String selector {0:?}")] + InvalidStringSelector( as FromStr>::Err), + + #[error("Cannot parse ipld::Number {0:?}")] + CannotParseIpldNumber(>::Error), + + #[error("Not a string: {0:?}")] + NotAString(Ipld), + + #[error("Unrecognized triple tag {0}")] + UnrecognizedTripleTag(String), + + #[error("Unrecognized shape")] + UnrecognizedShape, + + #[error("Not a predicate tuple {0:?}")] + NotATuple(Ipld), +} + impl From for Ipld { fn from(p: Predicate) -> Self { match p { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index e88cd17f..3ca846be 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -147,7 +147,11 @@ impl fmt::Display for Selector { let mut ops = self.0.iter(); if let Some(field) = ops.next() { - field.fmt(f)?; + if !field.is_dot_field() { + write!(f, ".")?; + } + + write!(f, "{}", field)?; } else { write!(f, ".")?; } @@ -164,10 +168,27 @@ impl FromStr for Selector { type Err = nom::Err; fn from_str(s: &str) -> Result { - match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - ("", selector) => Ok(selector), - (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + if s == "." { + return Ok(Selector(vec![])); } + + todo!(); + + // let mut working = s; + // let mut acc = vec![]; + + // alt((parse_dot_field, tag('.')); + + // match many0(filter::parse)(s) { + // Ok((rest, ops)) => { + // dbg!(rest); + // dbg!(&ops); + // Ok(Selector(ops)) + // } + // // Ok(("", ops)) => Ok(Selector(ops)), + // // Ok((rest, _)) => Err(nom::Err::Error(ParseError::TrailingInput(rest.to_string()))), + // Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), + // } } } diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index a1e054a3..0173b8b1 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -37,6 +37,20 @@ impl Filter { _ => false, } } + + pub fn is_dot_field(&self) -> bool { + match self { + Filter::Field(k) => { + if let Some(first) = k.chars().next() { + (first.is_alphabetic() || first == '_') + && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') + } else { + false + } + } + _ => false, + } + } } impl fmt::Display for Filter { @@ -45,7 +59,10 @@ impl fmt::Display for Filter { Filter::ArrayIndex(i) => write!(f, "[{}]", i), Filter::Field(k) => { if let Some(first) = k.chars().next() { - if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + if (first.is_alphabetic() || first == '_') + && k.is_ascii() + && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') + { write!(f, ".{}", k) } else { write!(f, "[\"{}\"]", k) @@ -61,16 +78,19 @@ impl fmt::Display for Filter { } pub fn parse(input: &str) -> IResult<&str, Filter> { + dbg!("PARSE", input); let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { + dbg!("NON_TRY", input); let p = alt((parse_values, parse_array_index, parse_field)); context("non_try", p)(input) } pub fn parse_try(input: &str) -> IResult<&str, Filter> { + dbg!("TRY", input); let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { Ok::(Filter::Try(Box::new(found))) }); @@ -79,12 +99,11 @@ pub fn parse_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { - let num = map_opt(tag("-"), |s: &str| { - let (_rest, matched) = digit1::<&str, ()>(s).ok()?; - matched.parse::().ok() - }); + dbg!("ARRAY_INDEX", input); + let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); - let array_index = map_res(delimited(char('['), num, char(']')), |idx| { + let array_index = map_res(delimited(char('['), num, char(']')), |found| { + let idx = i32::from_str(found).map_err(|_| ())?; Ok::(Filter::ArrayIndex(idx)) }); @@ -92,21 +111,26 @@ pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { } pub fn parse_values(input: &str) -> IResult<&str, Filter> { + dbg!("VALUES", input); context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } pub fn parse_field(input: &str) -> IResult<&str, Filter> { + dbg!("FIELD", input); let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT", input); let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); + context("dot_field", p)(input) } pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT_ALPHA", input); let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { Ok::(Filter::Field(found.to_string())) }); @@ -115,6 +139,7 @@ pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { } pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT_UNDERSCORE", input); let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); Ok::(Filter::Field(key)) @@ -123,13 +148,34 @@ pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { context("dot_field", p)(input) } -pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { - let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { - let field = String::from_iter(found); - Ok::(Filter::Field(field)) +pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { + dbg!("EMPTY_QUOTES", input); + let p = map_res(tag("[\"\"]"), |_: &str| { + Ok::(Filter::Field("".to_string())) }); - context("delimited_field", p)(input) + context("empty_quotes_field", p)(input) +} + +pub fn unicode_or_whitespace(input: &str) -> IResult<&str, Vec> { + nom::multi::many0(nom::character::complete::satisfy(|c| { + nom_unicode::is_alphanumeric(c) || c == ' ' + }))(input) +} + +pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { + dbg!("DELIM", input); + let p = map_res( + delimited(tag("[\""), unicode_or_whitespace, tag("\"]")), + |found: Vec| { + dbg!("$$$$$$$$$$$$$$$$$$$", &found); + + let field = found.iter().collect::(); + Ok::(Filter::Field(field)) + }, + ); + + context("delimited_field", alt((p, parse_empty_quotes_field)))(input) } impl FromStr for Filter { @@ -137,8 +183,10 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - ("", found) => Ok(found), - (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + (_, found) => Ok(found), + // ("", found) => Ok(found), + // FIXME + // (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } } @@ -164,9 +212,29 @@ impl Arbitrary for Filter { prop_oneof![ i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), String::arbitrary().prop_map(Filter::Field), + "[a-zA-Z_][a-zA-Z0-9_]*".prop_map(Filter::Field), Just(Filter::Values), // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), ] .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + mod serialization { + use super::*; + + proptest! { + #[test] + fn test_filter_round_trip(filter: Filter) { + let serialized = filter.to_string(); + let deserialized = serialized.parse(); + prop_assert_eq!(Ok(filter), deserialized); + } + } + } +} diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index aec6fd31..8be9fb08 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -133,10 +134,10 @@ where } impl FromStr for Select { - type Err = (); + type Err = ParseError; fn from_str(s: &str) -> Result { - let selector = Selector::from_str(s).map_err(|_| ())?; + let selector = Selector::from_str(s).map_err(ParseError)?; Ok(Select { filters: selector.0, _marker: std::marker::PhantomData, @@ -144,6 +145,10 @@ impl FromStr for Select { } } +#[derive(Debug, PartialEq, Error)] +#[error("Failed to parse selector: {0}")] +pub struct ParseError(#[from] nom::Err); + impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index e120d05b..4dcced0e 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -178,8 +178,6 @@ impl Display for Verifier { let point = p521_key.0.to_encoded_point(true); payload.extend_from_slice(point.as_bytes()); - dbg!(&payload); - multibase::encode(Base::Base58Btc, payload) } Verifier::Rs256(rsa2048_key) => { @@ -241,14 +239,7 @@ impl FromStr for Verifier { match s.split_at(8) { ("did:key:", more) => { - match multibase::decode(more) { - Ok(_) => {} - Err(_) => { - dbg!(more); - } - } let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more)?; - let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes)?; // FIXME also check max length on bytes diff --git a/src/ipld/number.rs b/src/ipld/number.rs index 16c66d71..e7c3c611 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -3,6 +3,7 @@ use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -44,17 +45,21 @@ impl From for Ipld { } impl TryFrom for Number { - type Error = (); // FIXME + type Error = NotANumber; fn try_from(ipld: Ipld) -> Result { match ipld { Ipld::Integer(i) => Ok(Number::Integer(i)), Ipld::Float(f) => Ok(Number::Float(f)), - _ => Err(()), + _ => Err(NotANumber(ipld)), } } } +#[derive(Debug, Clone, PartialEq, Error)] +#[error("Expected Ipld numeric, got: {0:?}")] +pub struct NotANumber(Ipld); + impl From for Number { fn from(i: i128) -> Number { Number::Integer(i) From 02b37bdb5f61173078fdb75d934c0855d7b9fc84 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 17:11:31 -0700 Subject: [PATCH 221/234] I guess manually parsing is the right thing sometimes --- .../delegation/policy/selector.txt | 8 ++ .../delegation/policy/selector/filter.txt | 1 + src/delegation/policy/selector.rs | 77 +++++++--- src/delegation/policy/selector/error.rs | 3 + src/delegation/policy/selector/filter.rs | 132 +++++++++++++----- 5 files changed, 167 insertions(+), 54 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector.txt diff --git a/proptest-regressions/delegation/policy/selector.txt b/proptest-regressions/delegation/policy/selector.txt new file mode 100644 index 00000000..caf39773 --- /dev/null +++ b/proptest-regressions/delegation/policy/selector.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 7d58a6b190e172b6e8336a5ee53de75608728b67f299b38738cac62d7bfc9633 # shrinks to sel = Selector([ArrayIndex(0)]) +cc a896af81439f52a08c2a281bfeebaf4427d3ee97a99f77440deee54c8a8ae7ff # shrinks to sel = Selector([Field("_")]) diff --git a/proptest-regressions/delegation/policy/selector/filter.txt b/proptest-regressions/delegation/policy/selector/filter.txt index a478d733..11f79cd7 100644 --- a/proptest-regressions/delegation/policy/selector/filter.txt +++ b/proptest-regressions/delegation/policy/selector/filter.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc e85947ad7c94a9c29fc57391d4e52cc023b1648997f55f55b2e980afaa3616db # shrinks to filter = ArrayIndex(0) +cc 86f7fcbfdc14a9aa56666c04370cbb2d82532c332ef68e6f4657edc672991bf7 # shrinks to filter = Field("A_") diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 3ca846be..0489fa5a 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -27,6 +27,9 @@ use std::cmp::Ordering; use std::{fmt, str::FromStr}; use thiserror::Error; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, Default)] pub struct Selector(pub Vec); @@ -168,30 +171,39 @@ impl FromStr for Selector { type Err = nom::Err; fn from_str(s: &str) -> Result { - if s == "." { - return Ok(Selector(vec![])); + if !s.starts_with(".") { + return Err(nom::Err::Error(ParseError::MissingStartingDot( + s.to_string(), + ))); } - todo!(); + if s.len() == 0 { + return Err(nom::Err::Error(ParseError::MissingStartingDot( + s.to_string(), + ))); + } - // let mut working = s; - // let mut acc = vec![]; + let working; + let mut acc = vec![]; - // alt((parse_dot_field, tag('.')); + if let Ok((more, found)) = filter::parse_dot_field(s) { + working = more; + acc.push(found); + } else { + working = &s[1..]; + } - // match many0(filter::parse)(s) { - // Ok((rest, ops)) => { - // dbg!(rest); - // dbg!(&ops); - // Ok(Selector(ops)) - // } - // // Ok(("", ops)) => Ok(Selector(ops)), - // // Ok((rest, _)) => Err(nom::Err::Error(ParseError::TrailingInput(rest.to_string()))), - // Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), - // } + match many0(filter::parse)(working) { + Ok(("", ops)) => { + let mut mut_ops = ops.clone(); + acc.append(&mut mut_ops); + Ok(Selector(acc)) + } + Ok((more, _ops)) => Err(nom::Err::Error(ParseError::TrailingInput(more.to_string()))), + Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), + } } } - impl Serialize for Selector { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) @@ -238,3 +250,34 @@ impl PartialOrd for Selector { None } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Selector { + type Parameters = ::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + prop::collection::vec(Filter::arbitrary_with(args), 0..12) + .prop_map(|ops| Selector(ops)) + .boxed() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + mod serialization { + use super::*; + + proptest! { + #[test] + fn test_selector_round_trip(sel: Selector) { + let serialized = sel.to_string(); + let deserialized = serialized.parse(); + prop_assert_eq!(Ok(sel), deserialized); + } + } + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index 75a96a58..f1363f07 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -8,6 +8,9 @@ pub enum ParseError { #[error("unknown pattern: {0}")] UnknownPattern(String), + + #[error("missing starting dot: {0}")] + MissingStartingDot(String), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 0173b8b1..016fad93 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -58,15 +58,8 @@ impl fmt::Display for Filter { match self { Filter::ArrayIndex(i) => write!(f, "[{}]", i), Filter::Field(k) => { - if let Some(first) = k.chars().next() { - if (first.is_alphabetic() || first == '_') - && k.is_ascii() - && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') - { - write!(f, ".{}", k) - } else { - write!(f, "[\"{}\"]", k) - } + if self.is_dot_field() { + write!(f, ".{}", k) } else { write!(f, "[\"{}\"]", k) } @@ -78,19 +71,16 @@ impl fmt::Display for Filter { } pub fn parse(input: &str) -> IResult<&str, Filter> { - dbg!("PARSE", input); let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { - dbg!("NON_TRY", input); - let p = alt((parse_values, parse_array_index, parse_field)); + let p = alt((parse_values, parse_field, parse_array_index)); context("non_try", p)(input) } pub fn parse_try(input: &str) -> IResult<&str, Filter> { - dbg!("TRY", input); let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { Ok::(Filter::Try(Box::new(found))) }); @@ -99,7 +89,6 @@ pub fn parse_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { - dbg!("ARRAY_INDEX", input); let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); let array_index = map_res(delimited(char('['), num, char(']')), |found| { @@ -111,35 +100,65 @@ pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { } pub fn parse_values(input: &str) -> IResult<&str, Filter> { - dbg!("VALUES", input); context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } pub fn parse_field(input: &str) -> IResult<&str, Filter> { - dbg!("FIELD", input); let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT", input); let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); - context("dot_field", p)(input) } +fn dot_starter(input: &str) -> IResult<&str, &str> { + if input.len() < 2 { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + } + + let bytes = input.as_bytes(); + + if bytes[0] == b'.' { + if char::from(bytes[1]).is_alphabetic() || bytes[1] == b'_' { + return Ok((&input[2..], &input[..2])); + } + } + + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) +} + +fn is_allowed_in_dot_field(c: char) -> bool { + c.is_alphanumeric() || c == '_' +} + pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT_ALPHA", input); - let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { - Ok::(Filter::Field(found.to_string())) - }); + let p = map_res( + preceded( + dot_starter, + nom::multi::many0(nom::character::complete::satisfy(is_allowed_in_dot_field)), + ), + |found: Vec| { + let inner = [input.as_bytes()[1] as char] + .iter() + .chain(found.iter()) + .collect::(); + Ok::(Filter::Field(inner)) + }, + ); context("dot_field", p)(input) } pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT_UNDERSCORE", input); let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); Ok::(Filter::Field(key)) @@ -149,7 +168,6 @@ pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { } pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { - dbg!("EMPTY_QUOTES", input); let p = map_res(tag("[\"\"]"), |_: &str| { Ok::(Filter::Field("".to_string())) }); @@ -157,22 +175,62 @@ pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { context("empty_quotes_field", p)(input) } -pub fn unicode_or_whitespace(input: &str) -> IResult<&str, Vec> { - nom::multi::many0(nom::character::complete::satisfy(|c| { - nom_unicode::is_alphanumeric(c) || c == ' ' - }))(input) +pub fn unicode_or_space(input: &str) -> IResult<&str, &str> { + #[derive(Copy, Clone, PartialEq, Debug)] + enum Status { + Looking, + FoundQuote, + Done, + Failed, + } + + let (status, len) = + input + .as_bytes() + .iter() + .fold((Status::Looking, 0), |(status, len), byte| { + if status == Status::Failed { + return (status, len); + } + + if status == Status::Done { + return (status, len); + } + + let c = char::from(*byte); + + if status == Status::FoundQuote { + if c == ']' { + return (Status::Done, len + 1); + } else { + return (Status::Looking, len + 1); + } + } + + if c == '"' { + return (Status::FoundQuote, len + 1); + } + + if c == ' ' || (!nom_unicode::is_whitespace(c) && !nom_unicode::is_control(c)) { + return (Status::Looking, len + 1); + } + + (Status::Failed, 0) + }); + + match (status, len) { + (Status::Done, len) => Ok((&input[len - 2..], &input[..len - 2])), + _ => Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::TakeWhile1, + ))), + } } pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { - dbg!("DELIM", input); let p = map_res( - delimited(tag("[\""), unicode_or_whitespace, tag("\"]")), - |found: Vec| { - dbg!("$$$$$$$$$$$$$$$$$$$", &found); - - let field = found.iter().collect::(); - Ok::(Filter::Field(field)) - }, + delimited(tag(r#"[""#), unicode_or_space, tag(r#""]"#)), + |found: &str| Ok::(Filter::Field(found.to_string())), ); context("delimited_field", alt((p, parse_empty_quotes_field)))(input) @@ -211,7 +269,7 @@ impl Arbitrary for Filter { fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { prop_oneof![ i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), - String::arbitrary().prop_map(Filter::Field), + "[a-zA-Z_ ]*".prop_map(Filter::Field), "[a-zA-Z_][a-zA-Z0-9_]*".prop_map(Filter::Field), Just(Filter::Values), // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), From 8c70158177700071000b915e1e350e2dcaa67e38 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 21:56:29 -0700 Subject: [PATCH 222/234] More tests, mainly fixing arbitrary instances --- Cargo.toml | 3 +- proptest-regressions/invocation/payload.txt | 7 + src/ability/msg.rs | 38 ++- src/ability/msg/receive.rs | 4 + src/ability/msg/send.rs | 11 +- src/delegation/payload.rs | 121 +++++---- src/invocation/payload.rs | 260 ++++++++++++++++---- src/time/timestamp.rs | 6 + src/url.rs | 4 +- 9 files changed, 348 insertions(+), 106 deletions(-) create mode 100644 proptest-regressions/invocation/payload.txt diff --git a/Cargo.toml b/Cargo.toml index 3df1b306..648b98f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, def p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } proptest = { version = "1.1", optional = true } +proptest-derive = { version = "0.4", optional = true } rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" @@ -119,7 +120,7 @@ default = [ # "test_utils", ] -test_utils = ["dep:proptest", "dep:libipld"] +test_utils = ["dep:proptest", "dep:proptest-derive", "dep:libipld"] eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] diff --git a/proptest-regressions/invocation/payload.txt b/proptest-regressions/invocation/payload.txt new file mode 100644 index 00000000..e39051d7 --- /dev/null +++ b/proptest-regressions/invocation/payload.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2936cf4d3882dd47c00298c35d9dd5a2bf1d22ef4492544b4b9ff3b1f34acb6f # shrinks to payload = Payload { subject: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: None, ability: Send(Send { to: Newtype(Url { scheme: "a", cannot_be_a_base: true, username: "", password: None, host: None, port: None, path: "aa", query: None, fragment: None }), from: Newtype(Url { scheme: "a", cannot_be_a_base: true, username: "", password: None, host: None, port: None, path: "_a", query: None, fragment: None }), message: "" }), proofs: [], cause: Some(Cid(bafybmiaabtpci7k3q4d3icxddhztzjx5tg72nv22yj7wrzlivewbgfd7ve)), metadata: {"\"($\u{680fa}蟧`\r`\u{b1d50}\u{4a196}'8$\u{f88db}\u{feff}\u{feff}\u{9e1a6}*\u{cc465}\u{101f58}\u{dc98c}\u{b}ȺP\u{87281}", -29, bafkrmigi5ucblo6m6dbnw2y7po27ahssm6r6gwfog5w2eaxd7ze64u3vki, "(", -50, true, 4, null, null, false, bafyr4id3ardj34fsaufowtepdegrlkvrkv4jmyiva54gz4afht26prarau, [206, 72, 170, 13, 12, 162, 106, 248, 83, 67, 95, 57, 191, 163, 90, 243, 125, 164, 14, 6, 150, 168, 71, 206, 239, 41, 111, 27, 246, 79, 53, 193, 98, 168, 210, 107, 64, 164], -3.33502927060181e-309, -49, null, 3.5445664834722476e19, -1.563337693800014e-308, bafy2bzacedec3zioqwt4bdhiel5jmdrot2fhkwtx4u3fqug5x5djkkgop6rbm, -7, -18, [216, 49, 164, 168, 254, 77, 204, 85, 63, 36], baguqfyheaiqipcrhl2ltrwj5ykc3gnwp2i4i6m3wqqrmfi4ap7uchecatjrmp7y, -0.0, [64, 148, 93, 100, 165, 243, 24, 181, 67, 141, 104, 128, 194, 110, 216, 253, 234, 40, 83, 43, 194, 82, 152, 207, 225, 141, 174, 148, 236, 77, 110, 231, 110, 107, 120, 161, 188, 242, 70, 187, 10, 8], null, null, bafyrmigihlvyxai27ay3ws4ytyoickvlnpli3rxing2zlvcrwklqgvhp6m, null, 8.12819559637677e-221, 14, -2.3233856055803314e-132, true, [33, 203, 100, 207, 226, 57, 37, 141, 9, 44, 127, 89, 88, 208, 26, 54, 107, 192, 81, 78, 170, 47, 90, 250, 222, 53, 73, 136, 60, 141, 50, 179, 193, 1, 240, 61, 212, 102, 158, 81, 130, 103, 57, 0, 144, 186, 253, 79, 200, 167, 150, 245, 107, 237, 87, 24, 222, 200, 67, 180, 136, 164, 87, 137, 92, 199, 194, 10, 174, 65, 1, 168, 43, 25, 36, 186, 55, 153, 217, 27, 78, 197, 170, 47, 74, 87, 78, 123], "&t\u{51b31}\u{10b5b7}·Ѩ$\u{491b7} \u{e82db}S`\u{13ba2}$\u{feff}\u{3}🕴V¥\t{*\u{79d01}", true, 32, true, 9.168173353619497e259, null, 0, 41, "🕴\u{6ad7f}/V\u{8d90e}\u{7}\0$\\\u{1de07}ѨDw'\u{5f7ad}�$$🕴qa", [183, 153, 172, 129, 204, 68, 233, 142, 34, 132, 183, 94, 210, 53, 181, 118, 12, 36, 147, 178, 163, 147, 226, 164, 22, 84, 47, 52, 68, 11, 3, 202], true, -47, "'\u{7f}\u{9f}{\u{4bd1b}${{\u{9f533}\u{4bd4c}$\u{49671}`\u{5}🕴|�k\u{c46c0}\u{1}🕴*\u{feff}7\u{c7f8f}\ra:", 24, [124, 23, 6, 17, 136, 174, 8, 255, 36, 94, 92, 161, 178, 30, 64, 18, 140, 103, 208, 64], "\u{41adf}\u{b}\"\u{bb2f8}\u{7f}Ⱥl**", bafybmic7lxxicy772aqqwgxzod5qnr2q42gvznf7ybo3focmp5garzkofe, false, null, -1.453409372917667e275, -4, 37, -6, false, "H\0O\u{de3c0}I 욽;\u{e55dc}/qY*\u{9b}$\tHô^\u{c1add}.\rȺ:&_\0�\t\u{f05bb}\u{a5ba9}\u{fac7d}", null, true, "U\rȺ\rÖU\u{3e4ac}:\u{a58ed}/|\u{b}\u{74e95}$\\[x\u{b652f}'/", -48, -3.21819364036035e-56, 9.213513078060828e-91, "'\u{890b8}=\tx\u{d5bf7}?\u{a4dd2}\u{9811e}\u{a5c67}": 6.664526628123302e214, "&\u{54d6c}//\u{d211d}\u{7f}�": "¨\u{202e}\u{413db}T\u{1027f3}\u{b}O`#\\\u{a494a}\u{3b0c6}s.\t\u{103dc}:u/\u{a5e48}A\u{202e}\u{77c5c}🕴ȺmM'\u{6a3fe}K", "'\u{1}": "\t🕴\u{eab5e}\\\u{bab9b}\u{ee77b}\u{52d9f}\u{ab141}{\u{6}", "'\"9%?": null, "'#\u{6}\u{bdef5}¥\u{ca946}\u{7f}\u{202e}": 1.7292683522733168e-61, "'M\u{d3d34}\u{202e}$\u{3}\u{42800}𗸴\u{7f}\u{62be1}<\0¥<\u{202e}\u{feff}/Ñ4\u{f92f7}{´": 9.820150129594855e-167, ")C\u{ddeef}\"\u{c2e4f}": [219, 75, 110, 213, 246, 194, 60, 45, 146, 98, 88, 191, 123, 141, 55, 76, 57, 2, 29, 8, 227, 206, 96, 136, 115, 139, 137, 243, 222, 156, 251, 57, 28, 97, 118, 49, 218, 195, 184, 189, 188, 165, 205, 70, 190, 145, 58, 42, 104, 26, 29, 3, 218, 118, 2, 133, 228, 7, 235, 110, 91, 232, 215, 78, 99, 2, 64, 17, 72, 56, 70, 160, 255, 37, 168, 19, 29, 27], "*´\\yn<%\u{3cb12}\u{99}&\u{5}Ⱥ/\u{7}𭆼\u{4263d}\u{88dca}Q<\u{efab0}\u{f6ec2}": -1.1059496664576365e153, ".\"\\\u{3}{\r\r:�\u{81c10}\0Ⱥ\t>{\\:\u{e877b}\u{5e93f}": [65, 186, 167, 66, 51, 133, 164], ".e$Ⱥ6\u{63ca7}Ѩ`u\u{33bd2}'n\\\u{48590}\u{e646}Q\u{a08f7}.Uf": null, ".\u{9d849}\u{f94cd}=𖩡\u{bc869}¥+$5?\u{1016e0}`䎟\u{1043c3}\u{6c91d}\u{667af}🕴": false, "/\0\rѨ^\u{1a826}\u{202e}\u{7f}\u{fb3dc}\r_�\u{3}\r\u{7f}&ï\u{3}:8\u{9ed75}q𪥇\u{1b}\u{e8fa3}\u{4}𧽨\u{4efe1}\u{977d9}\u{3c6dd}\u{cfeed}X": [17, 67, 94, 98, 195, 0, 112, 199, 95, 233, 81, 197, 28, 69, 112, 207, 197, 72, 18, 50, 114, 108, 53, 31, 197, 3, 225, 75, 134, 216, 253, 203, 70, 71, 48, 33, 235, 32, 133, 245, 9, 224, 57, 91, 77, 233, 171, 9, 39, 186, 240, 49], "8:\u{c9e98}\"%1🕴<🕴\u{977c5}띦b4'": true, "9Ⱥ\u{84761}a\t\0": "î+%\u{202e}\"\u{c9bb9}*\u{2}\u{eb474}\u{4c05d}R{=\u{feff}\u{e7f1b}\u{1}\u{2}<Ⱥ-=", ":<'": bafy2bzacebdndpakl7grp6t2qzddcmrtpw4klywtvk77qimmd5fya7havzbyk, ";\u{8}�\u{1b}P'Ⱥ": null, "=$ȺN\u{e377a}\u{5a12b}`fW\u{7f}\u{2}:\".": -1.284865551557179e224, ">:O\u{3a925}ÿ\u{5}�{": -20, "A\u{7f}$'\u{b}¥\u{7}\"\0\\$=\u{b}\u{1b}�\u{feff}\u{b}\u{7e7a2}\u{b5184}\":\u{feff} D\0=\u{7f}耋Ⱥð`ࢁ": -41, "J\u{1008ed}\"\rLq\u{5}7": true, "[啡🕴mѨ®\"\u{100589}{\u{8}u\r'\u{b}\u{47a49}}\u{7}\u{1b}R\u{b4ad6}\u{b45f5}\\Ⱥ`'?": [234, 239, 156, 159], "].`'&\u{7f}^&.\u{202e}\0?%?¥\u{9929e}\u{7dd49}?a": 6.8070914168451965e-68, "_¦\u{ccda8}O\u{10bdee}fk$\u{feff}`픰>\r'�f➦\r|{\u{feff}laA=D\u{dc4db}\u{5f977}z": [142, 251, 14, 32, 228, 8, 15, 49, 217, 216, 50, 236, 66, 67, 25, 55, 193, 159, 240, 75, 84, 154, 198, 182, 176, 139, 245, 12, 189, 16, 217, 208, 9, 19, 164], "f\u{3f22c}\u{7f}\0.Y?'.*\rѨ)Q¥=�\u{6b46c}\u{42bbe}\u{ee66}": bafkr4idibino2zrwzvmr7jw2oqbramjdedkkhhexizf2kgnxongndjyvvm, "k\u{e0bf5}Ã\u{202e}<\u{202e}**\t\u{656ee}<&𘞄\0\u{b}*.b\u{1b}\u{b}\u{8}\u{c9220}&\u{36fa6}.?]G<ȺG": bafk2bzacec3uxd2irqv3kuoh6433iq7cdb2rkr4jes4ibxqlagobh7kcbz7b4, "pmOAB\u{b}\u{1094f0}\u{c3774}?\u{10918f}¥": true, "v\\\u{109f70}7`@\u{4}c'%!\u{7d32d}Ѩ\u{1f298}i\u{4d1e4}'�\"]�.\t𝦈": null, "v`\u{1b}\u{58c73}\0\u{101d7f}\0\u{d946b}\u{7f}\t\u{f2bf}\"\u{831aa}\u{60049}\u{51864}&?祕u\u{10403a}D]�\u{578ee}\u{ab4b8}:/ 𦁋R7Ѩ": -24, "{\"C썶\t\u{eec8b}<\u{ee555}\u{7574e}Ѩ\u{7f588}\rѨ*'\u{5eccf}%w{\r\r\u{b67d7}\0^kᗩ\u{1c3dd}G\u{8}`": 18, "{%🕴\0Ѩl%𤍭\u{feff}🕴\u{96}I\r=": "¥\u{93d84}y:w={", "¥~\u{566dc}9\u{ec076} \u{bd2c1};": false, "¥ȺD\u{1}\u{6b992}\u{434c4}\u{3}\t\u{1b}?\u{2}𥹩\u{9cb8c}\u{7f}𢲦N\u{e11b5}Q^{Ѩ\"w\u{505da}{": null, "Á-`^�\"\t\"`\"/<\u{1}=%�=\u{f8043}\t\u{3cb4c}\u{af74a}^\u{356ed}.:d?\u{65101}\t;\u{d0319}\u{b6b3a}$q\u{b}": [163, 128, 176, 15, 181, 109, 115, 80, 122, 244, 94, 32, 58, 251, 121, 38, 145, 131, 101, 44, 148, 129, 190, 123, 55, 19, 64, 213, 134, 70, 238, 108, 62, 157, 43, 176, 76, 240, 174, 14, 182, 98, 16, 190, 79, 42, 142, 212, 204, 192, 112, 130, 89, 121, 0], "�*??*e": [172, 83, 171, 22, 2], "�\u{9b09a}\u{15e59}\u{7}/\0E\u{6}\u{3cb2b}\u{4645e}\u{7f}\u{2}": true, "🕴": 41, "🕴\t\u{f0635}`Ⱥ\u{c865b}h\u{f39b}\u{fd001}\u{f197b}¥F/?\0\u{52376}%Ѩ/\t\u{b}\u{9a1af}\u{9b1fd}\u{feff}�\u{8f}/bt`k": [235, 24, 242, 136, 3, 90, 188, 21, 23, 10, 85, 3, 90, 163, 176, 209, 186, 96, 160, 240, 221, 232, 34, 228, 54, 35, 11, 200, 6, 93, 92, 140, 92, 36, 62, 178, 157, 50, 12, 66, 22, 208, 28, 217, 177, 9, 79, 248, 77, 198, 73, 78, 120, 28, 102, 200, 197, 67, 232, 133, 41, 38], "𥇖`": -3.1043921612514823e111, "\u{fdac9}\u{2}.𘠺🕴:'\r\t\u{3}嬊": bafk2bzacedwvgub3e2lthnyghripac4wx5ld6z3tavnb3iws5vdjukng5fwsk}, "$\u{890b7}&🕴=Ѩ\u{e0cc8}\u{7f}\u{8}\u{b}\\\u{ed02b}🕴°d\u{7}\u{202e}L\u{6d2ae}<\ta{*": {"\0&/\0&\u{8999c}.`%\u{feff}": baguqehraxkytankub52h3iajclairokepzb3skwifyzrg5qnfmbz4g4oodza, "\0🕴\r\u{2}`%O$%\u{de829}": bafkrmiht3e7seikbsbinibsxaqh4je5pzdcd4urxrwswdctilymrairlqy, "$\u{1e406}\u{1ea29}\u{7f}\u{c9cd8}𮖲X": [168, 224, 17, 123, 200, 96, 201, 98, 223, 37, 175, 231, 138, 28, 166, 190, 55, 123, 102, 20, 44, 208, 18, 184, 161, 233, 19, 136, 101, 164, 157, 248, 80, 214, 88, 79, 112, 146, 242], "=`C": -23, "\\8\u{d9453}($�[\u{539d4}\u{90f0d}\\u�\"$\u{bf277}\"\u{749e6}": 9, "wѨ&\u{f18dc}\u{8f209}\u{8}/\t\\": bafkr4ibmkkkbmtivel5pdx3jrmhp4wb5bd7rqzvoovp2j4b5hn2rtovype, "Ⱥ\u{b}\u{10433a}\\\u{1b}{`!\u{cd71b}¥<`ȺDȺ\u{d310e}\u{fee4b}𭨠0\u{b6c98}": true, "Ѩu鉏<}\u{8}Q\u{5ef3a}:Cm&\\\u{3}Q':@\u{f9a57}'\u{3f836}c<🕴P": false, "\u{202e}\u{6899a}<\u{b}/Ѩ&\u{2edba}\u{103edb}\u{9e328}`\u{4979f};\u{a6f16}!`\u{feff}w`": -32, "�Ѩ\u{8}�\"\u{fc9b3}\t": null}, "L_Ⱥ=,\u{4}\u{71158}'�\u{d01e8}/$\u{202e}\u{3b573}:`{w'": [-48, [93, 196, 141, 154, 134, 176, 139, 189, 180, 134, 124, 223, 175, 198, 167, 42, 110, 24, 222, 67, 196, 65, 202, 107, 120, 13, 17, 105, 196, 172, 101, 89, 218, 26, 140, 88, 96, 162, 89, 106, 160, 211, 109, 26, 157, 200, 132, 194, 38, 44, 145, 80, 249, 47, 202, 234, 243, 149, 231, 133, 231, 1, 121, 28, 157, 175, 187, 89, 100, 158, 213, 243, 162, 111, 87, 174], bafk2bzacecsuznauruwgtuwqc6nidbjw7qp5usnb5vr3w7lxuvebwffbqe3t4, -1.8324536891683285e-30, bafk2bzaceadjwnpm762btxzz5nfl24lfog3wm5ldrl7q7namae7ysv5647h76, -6.370155741536763e-309, null, null, 23, false, true, -41, 48, "`\t\u{cd15e}<{\u{ad7da}%W*腁=\t¯\u{7}-?,\\", 5, [238, 157, 140, 174, 18, 122, 121, 22, 84, 53, 144, 119, 53, 216, 130, 89, 6, 121, 229, 24, 153, 6, 11, 186, 92, 6, 52, 44, 74, 132, 139, 202, 28, 213, 100, 205, 127, 222, 130, 104, 210, 158, 235, 223, 136, 77, 58, 114, 135, 100, 151, 103, 91, 82, 150, 91, 149, 124, 36, 26, 78, 105, 207, 54, 253, 9, 201], [195, 193], -2.0736431262565832e-7, null, -7.471855593429185e234, [212, 22, 11, 89, 86, 227, 1, 226, 174, 110, 146, 235, 217, 246, 70, 133, 107, 165, 157, 151, 202, 116, 160, 243, 169, 65, 185, 193, 131, 184, 185, 157, 65, 108, 39]], "y#\u{49798}\u{202e}G<%r\u{1b}\u{c2763}\u{1b}\u{d7645}$M🕴\u{b}:": baguqegza3fmjvuxyw67cjennqkgi6ipcsxboanp4xjz4prjmp7h4r5muxaja, "¥\u{6}xS%\u{a21c0}Ѩ\0\u{b}\u{e52b5}\u{8cd0b}:\u{feff}\tS¨\u{dc97a}%$\u{8a51a}\u{8945b}\u{3c40a}🕴\u{feff}\u{feff}": "\u{b}\u{fbd6c}*", "🕴$�\u{1c50d}\t\u{2}": {"": 14, "\0�&\u{feff}\u{685bb}YVE9c\"|\"<[�K𤅰\u{cd669}}\"": bafy6bzaceak3lys2lo5m7woe2mfjyxxl3ore3aixosi5uajethhkjyze72xpm, "\u{1}\u{d0b74}\u{b6c77}y?[\u{202e}¥~{T\u{1b}\u{feff}\u{f8717}\u{a0f04}:\u{10f8b6}Ⱥ🕴\"Q\u{df178}\"\u{fe2ee}u": -1.5028963084100644e18, "\u{4}`\u{100473}\u{f434d}=�*/🦲Y\u{a0}W\u{a0e46}\u{5}": false, "\u{5}4\u{33c5d}\u{4}": 6, "\u{5}ZѨh\r&": true, "\t(/=:<\u{7}🕴.'Z": bafyb4ig3qyn3653hzem53fsfvx27lsgdkfmln4vgzjnu7bhb22rr6h5nvq, "\u{b}\u{5}\"\r𐴅\u{a3132}z$8\u{74e26}\0F\0\"*�J\u{7d9f5}\u{ad7c2}\u{f3faf}?\u{f35ed}": [158, 45, 189, 198, 136, 12, 91, 193, 206, 13, 142, 218, 17, 126, 95, 11, 139, 29, 191, 233, 236, 221, 187, 109, 193, 176, 213, 129, 96, 120, 253, 24, 3, 65, 228, 116, 138, 102, 127, 19, 54, 235, 37, 201, 238, 25, 41, 26, 248, 239, 191], "\u{b}Á\u{4}\0$\u{baa23}N\u{105c4d}\u{feff}g": 6.651792988712289e-76, "\r&\u{7f}𬛩\r*m\\𭘺\t": [7, 86, 209, 90, 4, 82, 142, 122, 123, 191, 41, 84, 37, 155, 147, 150], "\u{1b}=\u{ae8cb}🕴🕴YѨ": [15, 58, 171, 250, 163, 49, 118, 146, 209, 32, 161, 202, 1, 216, 170, 61, 6, 250, 37, 28, 65, 69, 84, 230, 70, 124, 99, 121, 212, 211, 35, 15, 202, 210, 87, 237, 13, 14, 12, 203], " n𦜹": false, "\"I¥": null, "'\u{202e}\u{1bfda}¥\r\u{e18c}{\u{4a199}Ⱥ\u{98ba2}{\u{202e}🕴f\\": false, "'🕴": [0, 125, 38, 17, 120, 195, 201, 150, 64, 36, 204, 245, 82, 31, 21, 85, 169, 69, 249, 10, 19, 33], ")�\r\"\r¥\u{202e}\u{cbc20}\u{5a381}\u{202e}𱙤{'": false, "*\rȺ\u{9af1d}g�.*🕴\u{1b}\u{feff}\u{6ecf1}`\rY{\u{88}\\*.\u{f8037}($\\¥.\r&ÑD`": null, "*'\u{fe9ce}\t𪡏h'Y\\{\0IWë\rU?$\u{7}IQi!%": bafkrwif2qr2egu3en3ttpfc277nx7wbogbs3exytgbmipe2ev4itu4abxi, "*{U\u{7f}3\u{feff}🕴%*``/ꁏw\u{88393}\u{1b}>": 16, "-&\u{588d4}J": -136947584516.8358, "/": bafybmigzjzmiin3bgn6qms6vavgzn6iyqjuo4onptcetzu657kebbwt5hu, "2\r\u{8c375}i\u{99}&🕴0J\t$Á\u{66ed4}/": "¾\t\t🕴$A\u{136fd}¥=l\u{8937a}\"\u{db049}`Ⱥ>@\u{b}\t\u{feff}%.\t\u{8ec09}\u{c5f5f}/$", "<\u{1b}/\\?z9\u{dbde9}\t?𦤐\u{1b}&H\u{1b}\u{125ec}\u{cf4c4}\t\u{b}\0.\t§\u{4a3f5}\\�": bafkr4id2n26zagqdmec7pq53l34j6st3x3xzqualxr3kl57litjvhiqjku, "=]\u{1b}\u{f411d}\t\"`\u{a4a2c}\u{6}": [238, 35, 78, 136, 226, 234, 214, 233, 77, 30, 183, 34, 221, 130, 60, 71, 179, 70, 33, 252, 49, 64, 114, 217, 11, 226, 152, 252, 143, 174, 237, 102, 78, 100, 138, 217, 6, 74, 250, 0, 234, 244, 152, 245, 69, 148, 151, 8, 51, 18, 105, 167, 200, 213, 240, 64, 138, 104], "=\u{8bf06}?x\u{1}𡊽@\u{6}.<%": "L\u{79877}\u{eccd7}*", ">=\u{feff}\u{8}à\u{1b}vL0\t\u{7f}I\u{feff}\u{7aba3}b$$Sv~i\t錦🕴&\u{709ee}.": null, ">`:𦎼\u{7f}º\u{be352}n$\u{eafbd}\u{cddd8}jw\u{1b}\u{feff}YT": true, "I韸\u{53960}\u{feff}?\u{7f}.\u{cf92c}Q\u{4}.\u{b}\u{8}🕴Ѩ%": -26, "M\u{2}𬗃\u{9082e}\u{e23ca}\u{4}\t\u{feff}\u{5bde3}*\u{e514b}x,\u{48624}\u{1b}\u{b}+\u{f8546}\u{8a2a6}\u{d4710}'🕴f;\u{46c31}\u{1004de}y{b:": true, "M&\u{e303}?&�\u{7f}j;\"\u{202e}픢\u{b}🕴": null, "W*/X\u{7f}^\u{7dd38}Þ7%.*3\t\u{ce607}:\u{d3d17}c\t\r\"\u{84fa3}\u{202e}=\u{92}": null, "Y*\u{c2e6a}?<~\u{feff}P'¥e¥\u{43a8e}\u{fa00a}`'": baguqehra4nft63u5d2aawrcgvhart2zja5ogq6c624azqqxgbgtoxekvlnpa, "Y\u{202e}'?": "\u{3}\u{cc1ea}\u{c7649}\u{a973e}\u{8}0\r^u\u{8eff2}l\r<]\u{5e264}&\u{f3e11}\t\r\06S", "\\\u{5}.\u{7}\u{b69af}*\u{19abc}]¥\u{202e}{🕴'\u{e46c7}`.xs\u{b23ec}\u{10beed}": -21, "\\*\r¢峭ìE\u{d96e1}\u{f8ff9}^\u{bc6ac}%:\u{50965}$6\u{8119f}¥\u{f67d0}y~\u{6c741}\u{3beb4}🕴": [126, 126, 7, 32, 17, 6, 234, 156, 163, 166, 193, 46, 81, 206, 32, 125, 166, 57, 195, 140, 39, 47, 171, 70, 117, 175], "\\\\/&Ѩ\\<": [92, 59, 122, 162, 91, 48, 33, 29, 29, 249, 254, 64, 21, 67, 100, 54, 206, 184, 116, 163, 175, 47, 42, 243, 205, 18, 136, 16, 249, 116, 141, 232, 122, 115, 123, 202, 72, 56, 116, 229, 177, 60, 49, 83, 199, 52, 23, 85, 114, 147, 155, 50, 31], "`¥?쉲G": "\t\u{4095b}\u{6c6a4}𮭀\u{2}\\\u{da8f7}8\u{90a53}.痗\\\u{202e}\u{56f97}{E]\u{492cd}&f\u{7f}¥D\\]Ⱥ?🕴", "`嬥\u{db443}.\u{ce5}\u{12eae}\u{108af8}\u{1b}\u{7f}\r\u{60df2}¥%&\u{5b0fe}-.&\u{3}\u{e2c1d}DR&:�\u{10c17b}Èc\u{8}U%": [49, 169, 171, 64, 201, 141, 228, 46, 7, 206, 13, 162, 170, 144, 168, 169, 198, 15, 58, 71, 112, 228, 149, 218, 144, 84, 70, 253, 39, 168, 223, 3, 232, 33, 162, 234, 136, 215], "`�`\u{7f}{\u{5c37e}\u{2}\u{202e}?": "/\"\u{3}𪜥N\u{57f77}h$\u{d1d92}𐊘�\u{7f}*¥<.\u{9e397}<\u{1}g\u{d0ad6}/\u{11ede}\u{7f}\u{ae007}\0$\u{3afa0}🕴", "c��W|`�*@.aѨN\u{feff}\u{f92a0}t$\u{f53cc}1\u{c8fbe}\r\u{19667}\t\r\r-\u{7c67c}": 2, "o\u{1}\u{1}\u{1}=\u{e8927}I\u{f584}&\u{a0}:9\0a𠿭/": true, "q$Ѩ\08{\"鉱[?ÀzY\u{9b8c9}%\u{3}{%\u{5}\0.\\\u{6c540}\u{7f}s\u{c05e8}`\u{47f61}=)🕴": -12, "s*¥\u{9a}\u{feff}": -1.6309188456998563e185, "v=<¥'<\\`\u{cc9eb}\u{798aa}\u{64542}:®3\u{202e}\tD&𰓏%�\u{7f}LF𒉠\t�\u{ee420}/'D": true, "v陈'\u{d832c}R??\u{7f}/\u{6}?\u{7d4d1}\u{dca1e}~🕴\u{aca77}:ç\t\rѨt?\u{10f5f3}\u{9db4d}": [35, 3, 217, 123, 77, 104, 154, 141, 118, 202, 130, 101, 62, 0, 182, 199, 130, 209, 149, 102, 212, 81, 90, 149, 217, 96, 127, 42, 251, 151, 46, 49, 33, 84, 28, 55, 109, 95, 208, 174, 193, 163, 28, 103, 91, 127, 159, 204, 177, 182, 31, 112, 61, 243, 10, 202, 83, 234, 176, 174, 61, 22], "v\u{43b04}6\u{f16c9}\u{2}\u{ad277}{Ã={/\u{202e}": [130, 234, 7, 201, 230, 244, 129, 23, 205, 123, 90, 239, 235, 197, 10, 88, 224, 76], "y/\u{1b}Ѩ\u{1b}": "?;*\u{156a5}🕴\u{1ad1f}:-\u{a1bea}r&\u{eb06}\u{a5275},:R\u{7f}&<\"®\"w\u{202e}:Ó.:\u{202e}\t.", "{B\u{7f}𲌜\u{9a6bf}.\0\u{4a3ca}🕴\u{7}\u{35765}/": -29, "{`=\t;\u{b}*EX\u{1b}鋪\u{61ce8}\u{b742a}/s🕴\u{5}\u{db6e7}'\u{10aa16}q?\u{8}\u{8d}\u{518fe}": "?\u{8d694}🕴", "{b\u{fe666}䀾\u{feff}]y\u{1b}[$\u{202e}\u{202e}🕴㞔\u{c132a}": true, "\u{7f}\"P=\u{555f9}\r\u{f034e}j\u{67c81}\t)Ø\t\u{a2116}\u{bcb49}<\u{108172}\u{489d6}3": [11, 195, 207, 131, 8, 71, 197, 12, 152, 172, 119, 96, 131, 191, 170, 2, 143, 112, 247, 189, 191, 156, 180, 76, 57, 86, 59, 6, 237, 106, 34, 175, 98, 78, 217, 192, 190, 63, 247, 202, 245, 190, 185, 58, 157, 147, 177, 129, 101, 197, 232, 22, 220, 131, 53, 79, 127, 137, 106, 0, 139, 8, 238, 179, 248, 40, 242, 178, 191, 193, 47, 56, 113], "\u{7f}_%\u{e1aa4}\u{c1f6f}/$s:\u{1010bd}*j\u{b}nêeȺ$\u{7f}\u{202e}\u{feff}\u{1b}\u{feff}%\u{9f08d}w\u{e8557}<$<\u{3e78b}": "Ⱥ\u{feff}$", "\u{7f}\u{9a}\0�\u{9c}'$=\u{4}&\u{94281}\t?\"Ѩ\u{654e1}": false, "\u{99}.\u{9d66b}z\u{db26c}\0\u{109c92}.W\u{ca505}\\¥\u{e0cf8}\0`4x\u{feff}×ꡔL\u{b16bc}\u{c1e8f}TYu\u{e7dae}\u{49a55}: ": 43, "\u{9e}%MWȺ`$\u{202e}:z?🕴\u{6}\u{a0}㶬{\u{5}<\u{6}*": bafk6bzaceaont65a5awr6tb55msdtvepr4xwa62mgsp4gyyfmbd62ey4ts4z6, "¥`?=%🕴`L": true, "¥\u{7aff2}Òb[?\u{8}\u{923a6}»\\»": false, "¦\u{6a307}": null, "Ѩ🕴T¥$*?\u{6}𪵅\u{98110}": -6.640335043931752e20, "\u{1bd53}Ѩd%@": null, "🕴3\u{feff}&\u{dd024}Ѩ=\u{c62d5}p\u{7f}�'AѨ|`g\u{635c5}h": 22, "𣅝\"\u{8b95b}\u{fbfeb}\u{cd3f3}A'\u{167f7}𘑦&🕴\u{94f3b}H⼣\u{95077}": "", "𮎾ýK\u{feff}qȺ\"\u{64e75}\u{70dd2}¥\u{41885}l": bafy6bzacedp25hwogiwuqmaa52iwzgx42gznm36zcpjwo2togshwzpsae6p26, "𲊳": [226, 239, 217, 38, 87, 223, 233, 194, 149, 219, 168, 60, 210, 19, 135, 178, 111, 92, 80, 108, 128, 121, 241, 63, 190, 148, 170, 24], "\u{3393b}\0¥\u{3b6e9}F\u{93d46}X=\u{d5dd8}:\u{feff}%'\t\u{95303}'*\u{aca4a} for arguments::Named { } } +impl From for Ipld { + fn from(msg: Msg) -> Self { + match msg { + Msg::Send(send) => send.into(), + Msg::Receive(receive) => receive.into(), + } + } +} + /// A promised version of the [`Msg`] ability. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedMsg { @@ -83,15 +99,6 @@ impl ParsePromised for PromisedMsg { } } -// impl From for arguments::Named { -// fn from(promised: PromisedMsg) -> Self { -// match promised { -// PromisedMsg::Send(send) => send.into(), -// PromisedMsg::Receive(receive) => receive.into(), -// } -// } -// } - impl Resolvable for Msg { type Promised = PromisedMsg; } @@ -123,3 +130,16 @@ impl ParseAbility for Msg { Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } + +// #[cfg(feature = "test_utils")] +// impl Arbitrary for Payload +// where +// T::Strategy: 'static, +// DID::Parameters: Clone, +// { +// type Parameters = (T::Parameters, DID::Parameters); +// type Strategy = BoxedStrategy; +// +// fn arbitrary_with((t_args, did_args): Self::Parameters) -> Self::Strategy { +// } +// } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 2d59ea0b..73f2b504 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -8,6 +8,9 @@ use crate::{ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "test_utils")] +use proptest_derive::Arbitrary; + #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages /// @@ -37,6 +40,7 @@ use serde::{Deserialize, Serialize}; /// style rec stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "test_utils", derive(Arbitrary))] #[serde(deny_unknown_fields)] pub struct Receive { /// An *optional* URL (e.g. email, DID, socket) to receive messages from. diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c85dd4a7..ff97b16f 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -7,7 +7,9 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; + +#[cfg(feature = "test_utils")] +use proptest_derive::Arbitrary; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -37,6 +39,7 @@ use std::collections::BTreeMap; /// style sendrun stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "test_utils", derive(Arbitrary))] #[serde(deny_unknown_fields)] pub struct Send { /// The recipient of the message @@ -62,6 +65,12 @@ impl From for arguments::Named { } } +impl From for Ipld { + fn from(send: Send) -> Self { + arguments::Named::from(send).into() + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5b7f4901..e75bb60b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -115,7 +115,7 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload +impl TryFrom> for Payload where ::Err: Debug, { @@ -317,6 +317,10 @@ impl From> for Named { args.insert("sub".to_string(), Ipld::Null); } + if let Some(via) = payload.via { + args.insert("via".to_string(), Ipld::String(via.to_string())); + } + if let Some(not_before) = payload.not_before { args.insert("nbf".to_string(), Ipld::from(not_before)); } @@ -393,64 +397,91 @@ mod tests { use proptest::prelude::*; use testresult::TestResult; - mod serialization { - use super::*; + proptest! { + #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] - fn test_into_ipld() -> TestResult { - proptest!(ProptestConfig::with_cases(100), |(payload: Payload)| { - let named: Named = payload.clone().into(); - let sub = named.get("sub".into()); - - if let Some(ref subject) = payload.subject { - let sub_ipld = &Ipld::String(subject.to_string()); - pretty::assert_eq!(sub, Some(sub_ipld)); - } else { - pretty::assert_eq!(sub, Some(&Ipld::Null)); - } - }); - - // proptest! { - // #![proptest_config(ProptestConfig { - // cases: 100, .. ProptestConfig::default() - // })] - - // #[test_log::test] - // fn test_into_ipld(payload: Payload) { - // dbg!(payload.clone()); + fn test_ipld_round_trip(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); + let parsed = Payload::::try_from(observed); + prop_assert!(matches!(parsed, Ok(payload))); + } - // prop_assert_eq!(payload.clone(), payload.clone()) - // } + #[test_log::test] + fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); - // // #[test_log::test] - // // fn test_roundtrip_ipld() -> TestResult { - // // Ok(()) - // // } - // } + if let Ipld::Map(named) = observed { + prop_assert!(named.len() <= 10); - Ok(()) + for key in named.keys() { + prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); + } + } else { + panic!("Expected Ipld::Map, got {:?}", observed); + } } #[test_log::test] - fn test_from_ipld() -> TestResult { - Ok(()) - } + fn test_ipld_field_types(payload in Payload::::arbitrary()) { + let named: Named = payload.clone().into(); + + prop_assert!(named.len() <= 10); + + let iss = named.get("iss".into()); + let aud = named.get("aud".into()); + let cmd = named.get("cmd".into()); + let pol = named.get("pol".into()); + let nonce = named.get("nonce".into()); + let exp = named.get("exp".into()); + + // Required Fields + prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); + prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); + prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); + prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); + + // Optional Fields + match (payload.subject, named.get("sub")) { + (Some(sub), Some(Ipld::String(s))) => { + prop_assert_eq!(&sub.to_string(), s); + } + (None, Some(Ipld::Null)) => prop_assert!(true), + _ => prop_assert!(false) + } - #[test_log::test] - fn test_ipld_round_trip() -> TestResult { - proptest!(ProptestConfig::with_cases(1), |(payload: Payload)| { - let ipld: Ipld = payload.clone().into(); - let parsed = Payload::::try_from(ipld); + match (payload.via, named.get("via")) { + (Some(via), Some(Ipld::String(s))) => { + prop_assert_eq!(&via.to_string(), s); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } - assert_matches!(parsed, Ok(payload)); - }); + match (payload.metadata.is_empty(), named.get("meta")) { + (false, Some(Ipld::Map(btree))) => { + prop_assert_eq!(&payload.metadata, btree); + } + (true, None) => prop_assert!(true), + _ => prop_assert!(false) + } - Ok(()) + match (payload.not_before, named.get("nbf")) { + (Some(nbf), Some(Ipld::Integer(i))) => { + prop_assert_eq!(&i128::from(nbf), i); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } } #[test_log::test] - fn test_from_invalid_ipld() -> TestResult { - Ok(()) + fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { + // Just ensuring that a negative test shows up + let parsed = Payload::::try_from(ipld.0); + prop_assert!(parsed.is_err()) } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 40300b89..0d71a412 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,6 +1,9 @@ use super::promise::Resolvable; use crate::ability::command::Command; +use crate::ability::parse::ParseAbilityError; +use crate::delegation::policy::selector; use crate::invocation::Named; +use crate::time; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, @@ -20,6 +23,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::collections::BTreeSet; +use std::str::FromStr; use std::{collections::BTreeMap, fmt}; use thiserror::Error; use web_time::SystemTime; @@ -274,17 +278,15 @@ where { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.to_string().into()), - ("sub".into(), payload.subject.to_string().into()), - ("cmd".into(), payload.ability.to_command().into()), - ( - "args".into(), - arguments::Named::::from(payload.ability).into(), - ), - ( - "prf".into(), - Ipld::List(payload.proofs.iter().map(Into::into).collect()), - ), + ("iss".into(), { payload.issuer.to_string().into() }), + ("sub".into(), { payload.subject.to_string().into() }), + ("cmd".into(), { payload.ability.to_command().into() }), + ("args".into(), { + Ipld::Map(arguments::Named::::from(payload.ability).0) + }), + ("prf".into(), { + Ipld::List(payload.proofs.iter().map(Into::into).collect()) + }), ("nonce".into(), payload.nonce.into()), ]); @@ -304,6 +306,15 @@ where } } +impl From> for Ipld +where + arguments::Named: From>, +{ + fn from(payload: Payload) -> Self { + arguments::Named::from(payload).into() + } +} + impl Serialize for Payload where A: ToCommand + Into + Serialize, @@ -495,8 +506,12 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload { - type Error = (); // FIXME +impl TryFrom> for Payload +where + ::ArgsErr: fmt::Debug, + ::Err: fmt::Debug, +{ + type Error = ParseError; fn try_from(named: arguments::Named) -> Result { let mut subject = None; @@ -513,84 +528,131 @@ impl TryFrom> for Payload { - subject = Some( - match v { - Ipld::Null => None, - Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + subject = Some(match v { + Ipld::String(s) => { + DID::from_str(s.as_str()).map_err(ParseError::DidParseError)? } - .ok_or(())?, - ) + _ => Err(ParseError::WrongTypeForField(k, v))?, + }) } "iss" => match v { - Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "aud" => match v { - Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + audience = + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "cmd" => match v { Ipld::String(s) => command = Some(s), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "args" => match v.try_into() { Ok(a) => args = Some(a), - Err(_) => return Err(()), + _ => Err(ParseError::ArgsNotAMap)?, }, "meta" => match v { Ipld::Map(m) => metadata = Some(m), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "nonce" => match v { Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "exp" => match v { - Ipld::Integer(i) => expiration = Some(i.try_into().map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => expiration = Some(i.try_into()?), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "iat" => match v { - Ipld::Integer(i) => issued_at = Some(i.try_into().map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => issued_at = Some(i.try_into()?), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, - "prf" => match v { + "prf" => match &v { Ipld::List(xs) => { proofs = Some( - xs.into_iter() + xs.iter() .map(|x| match x { - Ipld::Link(cid) => Ok(cid), - _ => Err(()), + Ipld::Link(cid) => Ok(*cid), + _ => Err(ParseError::WrongTypeForField(k.clone(), v.clone())), }) - .collect::, ()>>() - .map_err(|_| ())?, + .collect::, ParseError>>()?, ) } - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, - _ => return Err(()), + _ => Err(ParseError::UnknownField(k.to_string()))?, } } - let cmd = command.ok_or(())?; - let some_args = args.ok_or(())?; - let ability = ::try_parse(cmd.as_str(), some_args).map_err(|_| ())?; + let cmd = command.ok_or(ParseError::MissingCmd)?; + let some_args = args.ok_or(ParseError::MissingArgs)?; + let ability = ::try_parse(cmd.as_str(), some_args) + .map_err(|e| ParseError::AbilityError(e))?; Ok(Payload { - issuer: issuer.ok_or(())?, - subject: subject.ok_or(())?, + issuer: issuer.ok_or(ParseError::MissingIss)?, + subject: subject.ok_or(ParseError::MissingSub)?, audience, ability, - proofs: proofs.ok_or(())?, + proofs: proofs.ok_or(ParseError::MissingProofsField)?, cause: None, - metadata: metadata.ok_or(())?, - nonce: nonce.ok_or(())?, + metadata: metadata.unwrap_or_default(), + nonce: nonce.ok_or(ParseError::MissingNonce)?, issued_at, expiration, }) } } +#[derive(Debug, Error)] +pub enum ParseError +where + ::ArgsErr: fmt::Debug, + ::Err: fmt::Debug, +{ + #[error("Unknown field: {0}")] + UnknownField(String), + + #[error("Missing sub field")] + MissingSub, + + #[error("Missing iss field")] + MissingIss, + + #[error("Missing cmd field")] + MissingCmd, + + #[error("Missing args field")] + MissingArgs, + + #[error("Unable to parse ability: {0:?}")] + AbilityError(ParseAbilityError<::ArgsErr>), + + #[error("Missing nonce field")] + MissingNonce, + + #[error("Wrong type for field {0}: {1:?}")] + WrongTypeForField(String, Ipld), + + #[error("Cannot parse DID")] + DidParseError(::Err), + + // FIXME + #[error("Cannot parse timestamp: {0}")] + BadTimestamp(#[from] time::OutOfRangeError), + + #[error("Args are not a map")] + ArgsNotAMap, + + #[error("Misisng proofs field")] + MissingProofsField, +} + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise @@ -661,3 +723,105 @@ where .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ability::msg::Msg; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(100))] + + #[test_log::test] + fn test_inv_ipld_round_trip(payload in Payload::::arbitrary()) { + let observed: Named = payload.clone().into(); + let parsed = Payload::::try_from(observed); + + dbg!(&parsed); + prop_assert!(matches!(parsed, Ok(payload))); + } + + // #[test_log::test] + // fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { + // let observed: Ipld = payload.clone().into(); + + // if let Ipld::Map(named) = observed { + // prop_assert!(named.len() <= 10); + + // for key in named.keys() { + // prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); + // } + // } else { + // panic!("Expected Ipld::Map, got {:?}", observed); + // } + // } + + // #[test_log::test] + // fn test_ipld_field_types(payload in Payload::::arbitrary()) { + // let named: Named = payload.clone().into(); + + // dbg!(payload.issuer.to_string()); + + // prop_assert!(named.len() <= 10); + + // let iss = named.get("iss".into()); + // let aud = named.get("aud".into()); + // let cmd = named.get("cmd".into()); + // let pol = named.get("pol".into()); + // let nonce = named.get("nonce".into()); + // let exp = named.get("exp".into()); + + // // Required Fields + // prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + // prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); + // prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); + // prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); + // prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + // prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); + + // // Optional Fields + // match (payload.subject, named.get("sub")) { + // (Some(sub), Some(Ipld::String(s))) => { + // prop_assert_eq!(&sub.to_string(), s); + // } + // (None, Some(Ipld::Null)) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.via, named.get("via")) { + // (Some(via), Some(Ipld::String(s))) => { + // prop_assert_eq!(&via.to_string(), s); + // } + // (None, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.metadata.is_empty(), named.get("meta")) { + // (false, Some(Ipld::Map(btree))) => { + // prop_assert_eq!(&payload.metadata, btree); + // } + // (true, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.not_before, named.get("nbf")) { + // (Some(nbf), Some(Ipld::Integer(i))) => { + // prop_assert_eq!(&i128::from(nbf), i); + // } + // (None, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + // } + + // #[test_log::test] + // fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { + // // Just ensuring that a negative test shows up + // let parsed = Payload::::try_from(ipld.0); + // prop_assert!(parsed.is_err()) + // } + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index e6b94e03..30f62f29 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -141,6 +141,12 @@ impl TryFrom for Timestamp { } } +impl From for i128 { + fn from(timestamp: Timestamp) -> i128 { + timestamp.to_unix() as i128 + } +} + impl TryFrom for Timestamp { type Error = OutOfRangeError; diff --git a/src/url.rs b/src/url.rs index 6cca29b2..dc8bd9c6 100644 --- a/src/url.rs +++ b/src/url.rs @@ -51,7 +51,7 @@ impl fmt::Display for Newtype { impl From for Ipld { fn from(newtype: Newtype) -> Self { - newtype.into() + Ipld::String(newtype.to_string()) } } @@ -86,7 +86,7 @@ impl Arbitrary for Newtype { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - let url_regex: &str = &"\\w+:(\\/?\\/?)[^\\s]+"; + let url_regex: &str = &r#"[a-zA-Z]+[a-zA-Z0-9]*:(//)?[a-zA-Z0-9._]+(#)?[a-zA-Z0-9_]"#; url_regex .prop_map(|s| { Newtype(Url::parse(&s).expect("the regex generator to create valid URLs")) From 71ee7f330483d3bac1a12b1a55713b655541b83a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 17 Mar 2024 13:15:48 -0700 Subject: [PATCH 223/234] Fixed errors in Nonce comparison --- proptest-regressions/delegation/payload.txt | 1 + proptest-regressions/invocation/payload.txt | 3 + src/ability/arguments/named.rs | 2 +- src/ability/msg/receive.rs | 14 +- src/ability/msg/send.rs | 3 +- src/crypto/nonce.rs | 61 ++++-- src/delegation/payload.rs | 9 +- src/invocation/payload.rs | 220 ++++++++++---------- 8 files changed, 184 insertions(+), 129 deletions(-) diff --git a/proptest-regressions/delegation/payload.txt b/proptest-regressions/delegation/payload.txt index a1f73406..03657047 100644 --- a/proptest-regressions/delegation/payload.txt +++ b/proptest-regressions/delegation/payload.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 2219b6f1cfd2b9c29cdae1174fd8633335cb25947b15ef61c3df44f2664175c3 # shrinks to payload = Payload { subject: None, issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), via: Some(Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })))), command: "eâ'Ⱥ{Ⱥ𞹒/𖿠¦꧊=:Z꧉&ceG4ᅲQ&]%", policy: [And(LessThan(Select([Values]), Integer(-17977310508110653107821168443657725762)), GreaterThan(Select([ArrayIndex(-2063331637), Values, ArrayIndex(1729047197)]), Integer(-71863900340009931549720359637876494783))), LessThanOrEqual(Select([ArrayIndex(1500241979), ArrayIndex(-659046873), Field("`Ⴧ*ᧂ:𑛄&"), Values, Values, ArrayIndex(1750800327), Field("Ⱥd"), ArrayIndex(-1789308745)]), Integer(70158225305663682194208457939428168215)), LessThanOrEqual(Select([Values, Field("*🫁𝕄L𞸧/ຄ𑻤$u\"*𝼥m':ᨼ𑾰/\\"), ArrayIndex(-554915756)]), Float(9.08306675974206e-234))), Or(LessThanOrEqual(Select([Values]), Float(1.003428263794859e-307)), And(Equal(Select([ArrayIndex(-233587294), Field("𝒫𞻰& キ<&ஞ臨2m𞺣'𞋿&{\u{ac3}{D8𒑀𑰈5\u{1ac0}\""), Values, Values, Field("G.吸aj𑿤q^a9%.<\u{84}%.𝝹𔓶": true, "*E🕴5/.`\\x=\u{7a853}&\u{5fd16}𩟨\u{b}\u{b3012}\u{9e345}/A\u{1b}Dts&E{w}\u{d8473}": 51, ".Z\u{795c2}?*\u{bffa7}B¥-/\tb阂?": 50, ".\u{7f}": baguqegzax2jx7mre4zpv63vto6u3mtjy4ivcrpnsnrgfcb6bymbpbwz5ky2q, ".\u{80}%\u{b}$\u{c6cf9}=&&\u{4}↜$'N\u{7f}\u{b}2\u{2}?\u{65b02}Uð𫻓\u{b}\u{feff}": -1.3122304768215823e-86, ".𗨥Ø\u{7f}\u{fe136}g\u{880c8}0O\\è": false, "1={\u{4fc0c}/\u{37be6}+\u{73284}.{\r�腄/_%�?\r\u{b022e}H": false, "2𱟴.": null, "4'\u{70fad}\\�o{\u{ce77b}\u{7f}~\u{d4dac}\u{1b}V\u{b}𩱋�\t&\\\u{aee01}`🕴\u{ee7dd}": null, "5$w\u{df9bd}\\\u{6}\tѨ\u{1b}M\u{cd289}:U%\u{202e}I\u{ea2ae}\r\u{d0993}D\u{feff}Ñ": null, "5\u{db9ee}\u{feff}\"�Ⱥ🕴z\0v<<\"5": 0.0, ":": bafy2bzacecp4g47wlyry7zukgmgp6xoxsh5aqlhn2vtxbpk2j77pzmx5w5xci, "<\0\u{3fe95}\t=`Z𰲨pdËf\u{5}�\u{1b}30\u{a2f77}\t:|%\u{a65a3}": 25, "<\u{2}¥?\u{9ae9b}'?q\u{7f}\u{84177}/?d🕴\u{d0dee}_x\u{a73f9}=`/\"Ѩ¥+\u{1}\u{202e}+Ⱥ/": "%){\u{f34d0}\u{91bf2}{]¥?<\u{7e5a3}\\\u{7f}¥", "<\"k&🕴Q\u{a0cc1}\u{193c1}\u{6fef1}\u{57765}=\u{b04a9}\u{1ee38}*$i�趼\r:^�\t\u{2}/": null, "<'\u{202e}": [246, 133, 18, 125, 51, 172], "<*\r\u{feff}'\u{b}\u{feff},\t\u{202e}\u{b}": 1.9096030614441494e34, "\0#.\u{e50a7}`\u{6b781}\u{72c56}IѨTȺ:\u{202e}\u{c1d84}\u{d1ebf}F\u{bc6f1}P�\u{1b}}?d\u{feff}Ⱥ\u{96517}\u{7}": -21, ">\u{7f}h)\t&'\u{b}": "p$\u{ce2d9}T\t\u{6486c}\"\u{af416}", "?*=\u{eea8d}\u{a8f3d}\u{479bf}\\\u{577da}<Ѩ&\u{202e}\u{ceb29}\u{8}/\u{7f}\u{7f}𘂤*\u{91}\u{96d48}\u{202e}\u{60a35}6\u{7cd5b}\u{b}~7\u{feff}\u{fc2b3}\u{6ca08}": bafykbzacedzd5rmt4wtpaenao7lyjfstkk3babvem5zvxsx66esxsdwdmnuok, "?\u{f5061}\u{6}\u{adb94}\tz<\u{8e6f5}\t]\"\u{1b}\u{50c21}?=\u{feff}\"'\u{cdff0}{�\u{11b58}\u{7f}ñ¥'\"I\u{6efe5}\0": "M\u{d7240}\u{5fa2f}\"\t\\Ⱥ\u{8}\r𓂂E\u{615a1}$ :\0\u{4b6de}2�&::", "D\u{834c0}": bafybwictq6cimsvk5g36pcopei5vzwuqjgz6zab4qnlw6h6pexvenpnuz4, "G{\0P\u{202e}z?<*\u{e4d94}\u{caeb6}c\u{b}\"\u{d4fa0}\0\u{3d0e1}\u{c315a}": true, "O:{.:6\u{1}𒋵=": [215, 19, 250, 35, 145, 227, 89, 97, 226, 22, 100, 90, 212, 248, 231, 66, 1, 74, 222, 19, 252, 253, 50, 43, 95, 28, 217, 195, 43, 193, 37, 211, 223, 168, 162, 177, 117, 244, 235, 50, 129, 137, 255, 246], "P\u{7f}m[*^\u{d4b73}\u{a4603}\u{cae36}\u{202e}C\u{2}\u{99a81}/<": "\t=/\u{202e}{?=z𣿸&Ѩ\t\u{10fefb}:`\\\u{88684}ᇵ&<'IB9Ѩ", "Q=/\u{2}[W$🕴Ѩi`\u{6b1f0}g\0\u{202e}&äq\0\\\u{e7b6e}\u{7}2": 0.0, "S\u{feff}¥\u{363f5}\\�\"|/$\u{1b}🕴\u{d61d7}\u{9c5fc}\u{5}%": true, "\\": 1797410591.2173085, "\\'\\\u{72082}9\r\u{fd777}&'\u{8}-\t$Ѩ\u{85}&'\\$\u{202e}\u{4}\u{d59a8}𐋪": null, "\\*\t&>ó": [197, 70, 103, 161, 242, 103, 93, 85, 220, 20, 175, 146, 70, 248, 167, 201, 81, 105, 89, 137, 113, 31, 52, 197, 100, 44, 200, 169, 146, 8, 75, 185, 88, 27, 164, 65, 95, 19, 82, 178, 129, 49], "\\¥𧷋%\u{7f}/%": 5.3388979855107657e-222, "]\u{2},n{\t": 4.726007655014673e-268, "]\u{e5bc2}:\u{67d79}": [192, 72, 37, 153, 92, 243, 115, 45, 85, 213, 125, 24, 161, 32, 231, 155, 146, 251, 181, 211, 205, 114], "^": false, "`\"\rBbIU\u{b38e7}~{\u{1b}<": [166, 52, 57, 140, 164, 87, 17, 112, 52, 240, 111, 77, 237, 27, 236, 44, 43, 106, 52, 249, 5, 150, 136, 195, 238, 123], "`=": 37, "`=\0\u{ae1db}$'\u{b}<{\u{1}\u{2};": true, "`~HѨ\r=": null, "`\u{91304}8?=\u{456a5}?\u{ee878}`": [72, 35, 50, 245, 247, 42, 83, 78, 154, 184, 6, 235, 171, 159, 23, 112, 160, 167, 206, 73, 56, 6, 234, 198, 253, 149, 213, 217, 121, 184, 227, 205, 182, 89, 67, 116, 130, 153, 30, 111, 90, 28, 205, 170, 195, 105, 252, 13], "`\u{b62b4}l\rȺ\0䱮Ѩ\u{325fa}://%.H*\r4\u{67fc7}f\u{feff}\u{97c83}d_z𒋤𩞬<": "{\u{7}\u{1b}\u{1}\t\"Ѩr\u{5}", "`\u{db05f}\u{82068}%\u{e3259}\u{202e}&\u{8916b}r\u{b}\u{eda56}�7\u{7f}\u{800bc}": null, "a\u{7f}": -4.342818332244186e254, "e;='\u{66fb7}¦_.R\u{6}S\u{99a3c}1*'d�\u{aecf1}?¥:\u{cd4f6}🕴¥{\u{202e}V'": null, "i\u{7acc3}\0*\u{3a0a4};\"G\0x\u{202e}\u{202e}\u{a6ef1}\u{7}y\u{c4c0c}*=\u{202e}Ñ|]\u{b}`S�\u{63b03}&¦🕴|": "\u{9b}\u{feff}/\u{2}\u{32ba0}$5N\u{c18b1}\u{6e305}", "i\u{b88de}:%<:\u{b}i¥\\%&\u{e9081}\u{36b96}\\wa\u{53cd7}\u{7f}\u{441e2}": [254, 111, 111, 15, 235, 43, 5, 115, 184, 222, 104, 79, 29, 45, 167, 151, 205, 114, 64, 194, 79, 2, 66, 154, 153, 108, 151, 126, 250, 233, 134], "z": true, "{\u{7f}Ⱥ&��0a]\u{7f}\u{b3046}\u{2fa93}\u{7f}<`a\u{10b100}b\u{e36be}-:\u{b}}I\\\u{c99dc}(�\u{d4177}<": 5.69430214693307e-309, "{\u{90}�\r¥%`": null, "\u{7f}=n\r\u{b904f}m\\`&\u{8d}&\u{8}q\u{10fb4a}\u{202e}\u{feff}": 30, "\u{7f}\u{6677f}\"\t\t𥡏&\u{929fa}%\u{1b}": false, "¥\u{4b398}$\u{b}.\u{10edc3}.v\u{4}\u{c8f3b}`\u{1b}o\u{b}B�.\u{8a4e7}": true, "¦\u{567e2}\u{789fb}.\u{1}\u{bac03}FeLѨ": [46, 154, 121, 10, 113, 153, 177, 226, 108, 199, 94, 194, 182, 56, 189, 253, 144, 68, 220, 178, 227, 39, 5, 27, 46, 171, 2, 171, 100, 125, 136, 48, 185, 143, 175, 224, 38, 180, 89, 73, 253, 97, 152, 89, 126, 226, 69, 159, 39, 4, 5, 151, 166, 157, 129, 18, 74, 141, 30, 245, 18, 68, 59, 4, 84, 71, 127, 83, 157, 160, 70, 180, 19, 67, 237, 156, 119, 247, 69, 10, 220, 32, 51, 35], "Ê`/\u{e98d1}`8\u{5a946}¥\tѨ\\\u{71794}\u{1b}\u{5d467}\u{5f80a}\u{2}o\\\u{78e03}\u{1b}$V\0,Ⱥu<\t\u{4}": "l\u{80}/H", "Û\u{a246c}\0\u{cc1d1}\u{1b}": -2.3446004632097955e-204, "Ⱥ\u{48608}�\u{1}Ѩ\t\"\\\rRx\u{feff}𱋭🕴.>.\r?/Ѩe": bafybmibqonatpssw5xwj3ckqwghe3uv52fbojbwo6wevvgyxhhi6is34iy, "ЏѨ$N==jX\u{ef98d}?{Ⱥk\u{4c9b3}\u{a5c43}'\rx": true, "Ѩ?=Ⱥ\u{7f}\0🕴$�\u{39466}\u{1b}�\"`\0.\u{feff}*.x-\u{7f}EѨÔ\u{7}\u{10f98d}$P": false, "Ѩ뀳🕴\t\u{202e}<{\u{950c8}\0\u{6abe2}2¥G\0": bafkrmicucdpcfbvzkhxwm7wgcnbx7wbbsx7yf4xs3xnebcwqv4fzfnyeui, "\u{202e}\u{5}\u{f93a8}�\t\u{93751}DX.4\u{b}\u{47079}\u{cb546}Ê\u{3c9c6}('Q\u{df412}^(?%\u{1b}\u{d4087}\u{87657}\u{69291}": bafyr4igptyd3pys2jr5l7f6wly2ixknaqjhxa2goliyvnoxef4q5npn2wq, "\u{202e}//)H\u{489d0}\u{b}\"\u{7f}7\u{fd8c1}\u{52967}": -49, "퀹\u{1e584}Ⱥ\u{b5e48}?\u{d70a5}\u{202e}\u{feff}o&Ⱥ\t2&$TѨ\u{c7d9d}¥z9�#B,\u{7d2c6}\u{a9082}": -39, "�\u{b}\u{9b}:\u{afa9e}\u{8b}\"G%=\u{6}\u{b1e04}𫃏\u{202e}{:Ⱥ'\0[;\u{cbe4b}Ѩ¥V\r:e\r�\"\u{36274}{\"\u{feff}\t\u{1b},\\�鴇\\µ": "*\u{b2484}(j'0\\�\t\u{feff}~I*\u{1abf0}鲫!¥í\u{7f}\t\t\u{feff}", "\u{2}¥@@x\u{7}o<>\u{b}>�\"=\u{4a714}H\u{356a6}Y\u{feff}¥\0Ѩ`\u{8586b}�@\u{86433}8\u{b5eb0}U\t\u{928a7}?:3V\r\0\u{e963}\u{91606}\u{f5d9a}`&&\t\\{$&`": [203, 219, 129, 233, 167, 86, 133, 68, 30, 123, 72, 152, 245, 36, 18, 51, 184, 218, 78, 248, 74, 234, 85, 239, 225, 163, 68, 62, 10, 51, 41, 233, 241, 66, 225, 241, 213, 58, 199, 207, 196, 20, 9, 130, 162, 125, 157, 172, 66, 51, 176, 230, 83, 217, 243, 126, 106, 4, 227, 168, 130], "?\t#?\u{c8453}l<\u{ebb70}<\u{202e}\u{1}\u{4a0a6}\u{bb34d}𧆽{\"\u{202e}\u{a6e5e}<\u{2}$\u{1b}{\u{1b}\u{a7345}\u{bcba2}=\u{55f80}𠘓`": false, "@\0 \u{e2e8b}\u{feff}&{¸\u{1b}`Ã`<\u{c80a3}\"h%V\\:\r:{%\u{b}\u{91b96}\0": 16, "A\0\0%Ⱥ\u{7f}o6Ѩ\ra\t\u{fa6d1}": 0.0, "E5\u{3ef04}\\iѨ4==\u{ad632}\u{103a8b}\0\u{882ea}\u{3fb8f}\u{80a35}{\r\"\u{9afa0}\\<3*\u{e368f}\"{Ѩ\"\u{85b40}/": [62, 206, 252, 139, 165, 123, 83, 85], "K?\u{15ea9}%/Ⱥ\u{39c0e}Ѩ\tM/\u{806b5}𣎂+": "\u{7b58c}\u{eb27}", "Py/\u{eeae7}\t:z/u&\u{fdecd}\\`<": 2, "P🕴EU\t$\0V\"*": true, "S\u{6}?'{¦~:¥\"\u{797af}:Ⱥ": false, "W/\u{7d22d}\u{7f}k\u{6f5bc}\"/\u{7ac51}\u{feff}\r\u{1b}\u{b}`\r\u{16d8a}\"\u{feff}\u{4e68c}": null}, 3.7320199453131945e18, true, "\td", -2.1302548429054803e-66, [9, 130, 254, 29, 224, 58, 3, 226, 45, 154, 180, 58, 156, 107, 43, 160], [184, 58, 122, 21, 191, 153, 91, 193, 125, 41, 253, 9, 18, 17, 100, 75, 60, 215, 91, 98, 131, 82, 225, 220, 226, 154, 246, 54, 253, 108, 18, 104, 142, 238, 92, 249, 74, 194, 85, 60, 101, 114, 120, 130, 76, 198, 254, 54, 43, 192, 5], -3.035213207805024e-151, true, null, true, [144, 5, 247, 45, 158, 101, 252, 143, 38, 225, 226, 145, 14, 34, 204, 157, 201, 182, 152, 54, 29, 245, 248, 233, 203, 103, 50], 7.126162207025803e98, 0.0, 38, 44, false, 1.442945330979412e-308, -3.2821103465088214e185, false, -0.0])), GreaterThanOrEqual(Select([Field("\u{dca}?i%ѨѨ<\\{Ѩৡ=ᨁr\"T\u{1a7f}🛞{༴Ⱥ:ᛧ"), Values, Values, ArrayIndex(-206845918), ArrayIndex(284566073)]), Integer(-130442174005234510445148006169593931048)))), Every(Select([Values, ArrayIndex(-1235287252), ArrayIndex(1235974543), Field(":�𐔆Ѩ1ꬓ*%𝔹⑩%By�ࡤ{6&🃅%ᒜ!*t\u{cd5}ܔÈ🃌K-:f"), ArrayIndex(-74267187), Values, Field("𐝧¥ѨK𛄲ﹰ~/𞺀Ⱥ𐁑{A℈ꧻxኁ$X")]), Every(Select([ArrayIndex(-835767092), ArrayIndex(-90842294), Values, Field("&Z𛱺\u{1a6b}¥¡\u{2002}O�Eࡪ(/ѨѨ\u{fb3}ö2<=u,ⶣb"), Field("হ\u{1d242}]𐺱'<×\".᪗\u{cc6}%\u{9e2}/$`+ꚠ𞹲ѨRr'7\"\u{1921}"), ArrayIndex(-1657524013), ArrayIndex(-1358049927)]), Equal(Select([ArrayIndex(-2133215332), Field("�ѨA$=%0𑒌$𞴻{ῚL𒿎<*Hÿ)HಯmJ᧗ૉvকj¥𑶄ዅ"), ArrayIndex(1372867808), Field("9U𒐽ᅥטּ:^�ཬ{L\u{1a7f}🛞e{𝔊sை8%$-SޥJ=\u{1e136}&"), ArrayIndex(-1515605936), Values]), Newtype([[220, 112, 193, 144, 130, 2, 201, 3, 173, 97, 18, 34, 79, 189, 93, 185, 99, 8, 146, 72, 228, 21, 109, 226, 107, 211, 58, 9, 68, 90, 109, 0, 69, 37, 94, 48, 179, 143, 33, 56, 70, 63, 33, 221, 197, 203, 25, 245, 187, 130, 222, 127, 221, 178, 88, 248, 218, 158, 87, 24, 127, 138, 100, 104], bafkr4ighuk7gjom3gactib6iatiimrm4ytvtdk7y5k4pzutsg56i77lanm, -8.793256611809324e-208, [49, 12, 111, 228, 6, 196, 193, 26, 82, 212, 21, 13, 44, 254, 161, 77, 168, 161, 151, 85, 22, 53, 168, 74, 48, 15, 119, 171, 245], 3.675952639990706e158, false, null, null, null, null, -156.1567975459344, "\u{1070c5}\u{202e}m\u{7f}'#\t\":\u{dad72}ü4\u{202e}\u{7f}'涛\0$E{-/\u{202e}\u{7f}b", "\u{13e62}$e<\0&<\u{c5acc}ny\u{9a704}\"\u{202e}𔖚D", bafy6bzaceagsds7mbaud2m6qhdohvpgwuhfeedqwrntvioyma3kuo5v6vtyt6, null, null, "\r\tg:\u{46cc2}", [142, 185, 121, 120, 78, 34, 236, 132, 123, 49, 125, 116, 146, 139, 230, 17, 174, 47, 83, 146, 252, 30, 220, 161, 163, 207, 136, 121, 230, 68, 139, 61, 190, 89, 223, 74, 72, 219], [28, 249, 124, 123, 205, 232, 92, 8, 106, 47, 104, 13, 104, 201, 76, 228, 38, 128, 160, 168, 2, 34, 83, 71, 248, 91, 111, 250, 225, 49, 225, 194, 169, 133, 253, 173, 44, 47, 27, 4, 181, 140, 138, 211, 3, 136, 21, 139, 138, 196, 191, 191, 76, 163], true, [87, 3, 80, 153, 162, 125, 138, 138, 127, 8, 103, 89, 128, 18, 40, 41, 190, 156, 216, 106, 118, 162, 241, 107, 134, 144, 107, 52, 51, 165, 85, 124, 197, 155], "\u{2f39d}{¥\u{40daf}ѨS\u{a44e7}{\u{a7dc1}�\u{cee1b}'", [235, 139], false, "", "\u{a1d4d}\u{feff}\u{4}\u{feff}3\u{1494d}\u{e1172}.\u{1b}`\u{d2482}{\u{9e}\u{e1d5d}\"3/{\u{5}2].Ⱥ¤<\u{96fa3}Ⱥ\u{49ea4}*\"p\u{b83db}\u{6}\u{6}": null, "?/\u{bdd3c}\u{370b4}\0*\u{102d37}\u{cbc47}.\\Zâ\u{1ced4}KѨ🕴::\u{7f}/": 44, "?8?A:/'H\u{b}\tr\"¥=qu\u{b}¢\u{fde59}\"\u{2}ye'?\t\u{ea1b7}K`\t": -3.9299672377331247e-146, "?{\u{7f}%\u{7f}\r%\u{d3348}\u{b}\u{eb7b2}%¥\u{9406d}\rѨѨ],\u{202e}'\u{105dc6}<^úr\u{8015f}": "\u{9d}\u{891c2}𫩼\u{606aa}\u{45288}p!/&G:㝸ã\u{97}Ⱥ\u{b}", "?Ѩᆬq7/%\\\t\u{feff}\u{da5c3}\"\u{8509f}\"H`\u{1b}%\u{4d464}\u{479f4}\"\u{c147b}\u{1b}/𧳓*,|": "Y\\\u{7f}\u{b1fcf}\u{e99b9}7\u{9e18b}:?\u{ede3}$W", "?\u{5d644}\u{5a09a}\"�l\u{1b}/\06Ѩ.&{\u{10a396}": bafyrmihqvy27erjh5esd3x6wdgnr6z6lru7wfcmkuuvzp7h7am6z63mbia, "A\u{763e5}& \u{d7169}\u{e8031}{\u{b}*\"\t𰩧\u{548b4}?u`/\r🕴\u{7c626}$å🕴\u{10dd6}:🕴": null, "DѨ\u{6ebc8}\u{7f}<\\[?\u{7f}&ኵ:f=𫗿\0�\u{202e}": "s\\/;*\\\u{4150f}E", "F\u{1b}\u{10ea63}\r": -3.881911088191245e-265, "H.¥$": -21, "K\u{cbff5}\u{202e}\u{1260e}\u{feff}&:%\t$\"|&\u{342e6}*:\"&ju/c": true, "N\u{b}&\u{feff}\u{691d4}\u{6}\u{b}:&*Ѩ\u{6f0dd}w\u{e581c}¥z�\u{9cce5}\u{16d5d}\u{4f30a}**\u{93d8c}\u{202e}\u{7}{\u{1b}{±'{K": "%\u{feff}¥", "[𪲁@\u{1387b}\0]\u{6fca2}Bi.\u{89a95}\u{6a578}\0\u{4a64d}\u{b}\t\u{647d9}n\0^\u{8ff74}Yk\u{9c}\u{9c}(": [116, 173, 176, 186, 52, 23, 252, 79, 37, 2, 204, 64, 93, 133, 149, 198, 164, 255, 254, 251, 238, 69, 160, 225, 130, 239, 127, 62, 193, 179, 31, 95, 161, 114], "\\\u{6d15d}\u{b}&\\Ѩã\u{e6f74}<\u{a15a9}\tѨ,\u{b}\u{819b6}\u{3ef57}/\u{4ddf0}Y(\u{5}\u{100a6f}\u{b}/.:": -37, "]\u{3}\t\u{e5fb9}?{\u{202e}z\u{44ab3}\u{39431}\u{e8c44}¥\u{79b81}'\u{777e6}�:{%\u{3900c}{%\u{108b7b}<{`\u{7f}Ó\t🕴T.": 29, "`\u{6}.:🕴{\u{9c4a2}\\<*:": bafy6bzaced7s7j4l6dybvc55yypxhw5em5sg2ciws33rjhgce6hmfmpgf4fdu, "`o=\u{6}3\u{e81b2}\u{1b}G&\0tw\u{3}/\u{3d977}`㊈)=Y\u{2}\0": false, "`\u{44886}/\u{a1f9b}\u{5f978}:\r\u{95be2}/🕴": 0.0, "c\u{5}\u{4bbc4}b\u{c96f8}+%$\u{202e}'<\u{e078c}\u{ac7cc}x\"Ѩ": 53, "n": "\0?\u{efd30}\u{686e5}\"\r\u{5fe32}\u{b5b0c}\u{51f54}\t\u{89}=\u{33916}ZM¥\u{4}\u{7}m�}\u{91c01}\u{6839a}*\u{c59e9}", "oѨ`": null, "w\t\\`\u{da1d2}*&.$E;\u{62ec7})\"Y\r\"5\u{bd563}\u{326cb}{å?/\u{b}\u{98e3b}": "D::\u{202e}F\u{d9e2c}J'=\u{ec5db}\0'\"Ѩ\u{1b}\u{419e5}𮞿?E*mb\u{feff}_Z\u{ab92a}=\u{89c4e}逋\0\u{7f}J", "y6\t<": [179, 71, 5, 85, 100, 29, 222, 53, 97, 142, 194, 243, 96, 220, 13, 106, 64, 105, 167, 218, 123, 136, 220, 228, 82, 153, 8, 92, 185, 11, 112, 146, 197, 109, 163, 11, 117, 83, 66, 85, 178, 149, 17, 95, 212, 87, 96, 62, 216, 80, 38, 36, 236, 156, 22, 40, 44, 133, 95, 15], "{Ⱥ/&\u{7f}\u{5795a}_*\0*\u{a13ce}f?`/\u{1bd42}<𮧏<\\U(n\u{2ffdf}\u{bad0b}&]\u{feff}U🕴$": 9.93350022419413e112, "{\u{39ee3}TI\u{ea081}%Pï>\"Ⱥ\u{feff}{\\j": {"": "\u{feff}\u{1}\u{1}&=¥\u{68854}&/ö\\\u{33a28}\u{d8908}f4]Y\u{39e57}**Ѩ\\*Ѩ\u{5270a}{B¥+x\u{feff}", "\0${\u{8f006}:f<\u{3}\u{ae249}\u{a3dc8}\u{5eb29}\u{b67f9}%:\u{37105}\r\u{1b}'$\u{2faa8}$/A\tW'\u{909b7}\0\u{2}": 2.914324980421285e-195, "\0$\u{ac00f}\u{1b}9\r\u{3}\u{2fd7f}=:R.\u{e3705}\u{c4450}NG\u{73b7c}&$\u{202e}\u{b527c}GL": "\u{a3b69}6a*==?\u{feff}\u{1a1cf}\u{a82da}`/==�🕴\u{6b826}\rD?A\u{feff}\u{1}", "\0*{\u{202e}\t�\u{1b}/|Ѩ%©%": baguqefragwjjb3m3ljrschzrusb2unylas52kabmwfr34wqmvo6rkg2avvuq, "\u{3}\t\0\u{6695e}\"{»1\u{202e}\u{68a4a}N$": [233, 212, 54, 104, 52, 88, 23, 191, 247, 21, 223, 50, 174, 43, 59, 131, 37, 131, 62, 190, 142, 176, 67, 43, 184, 235, 120, 202, 175, 190, 189, 145, 211, 136, 59, 252, 222, 235, 131, 213, 187, 34, 118, 61, 42, 93, 166, 43, 180, 114, 34, 166, 57, 195, 172, 167, 175, 177, 81, 106, 26, 118, 209, 95, 105, 177, 234, 126, 58], "\t\u{1b}7🕴¥qb\u{8}\r\t\u{1062f0}%'\u{b}.g\u{411f2}𭕨B{Ⱥ\u{77264}/\u{b}D\u{3d766}<": null, "\u{b}/<\u{34eed}\0S&>�텐$$": ".\u{7ebf4}Z\u{d3912}\u{feff}?*H\"\u{202e}𦊇\u{91}Ⱥ\u{7f}\r?{)<\"\u{42b46}?T\\\rg\u{ec834}\\\u{7f}\u{7f}¥", "\u{b}?\t\u{2}�4¥𞸩*\"\u{578ba}\0G%��\\r%«=🕴=\\&*=": "\u{8237a}\u{8a}<\u{8ab59}.?<.tàô\u{945d6}\u{feff}`\\#\u{ce61d}c:)?.`a<\u{9d398}\u{962eb}\"&", "\u{b}ÞѨ¾y狦\0🕴\t\u{19d5e}\u{f0148}": bafykbzacebxelvnoczrdap55pukvsxpc6gicbmnqzgsouqceppvlhsicwet6e, "\u{b}ãx\"\\\u{4}`\r:\u{feff}ᦇ\r": 22, "\u{b}�`+\".?hC\u{88713}:\t\0N¥/Ⱥ\u{1d2b9}1&`Ѩ.": 0.0, "\rAf7\06`\u{ff186}\u{9f715}\t": "/$\u{eaf96}\rW.{{.l흄*\u{1b}\"🕴!?\u{a64ef}\\\u{7f}/\u{109cc5}\u{5}'\u{b}\u{fc0c5}𱇴\u{d1388}", "\rUKG/\u{3}\u{2}ꕻ\u{b}\t": -1.3995651248504633e262, "\r\u{72228}\u{3}\t'\t\u{c5bb7}\tv": [207, 209, 38, 232, 225, 181, 157, 174, 248, 85, 75, 104, 14, 234, 9, 86, 149, 189, 217, 176, 122, 32, 78, 186, 174, 19, 241, 185, 202, 135, 32, 184, 35, 47, 78, 189, 35, 153, 142, 128, 46, 7, 65, 55, 37, 215, 0, 109, 138, 244, 78, 202, 124, 93, 146, 143, 199, 70, 40, 153, 254, 139, 213, 103, 34, 98, 157, 146, 23, 126, 115, 247, 59, 186, 119, 63, 16, 179, 123], "\"%3\r\0\t*\u{74f05}\u{10ecdf}%\u{56b43}\u{e2041}\u{b}Ѩ🕴%\u{47ac1}\u{b4501}\u{10314f}\u{b}": 8.190682906409504e77, "\"üE8À%": -5.01070380812316e-309, "\"🕴¿%z4q\u{f5f81}\"\u{202e}\\": [99, 154, 31, 116, 21, 233, 127, 12, 159, 139, 166, 170, 113, 31, 35], "$&\u{81e34}�\u{feff}\u{3}🕴\u{b}\u{3e109}\u{8e8b1}A\u{1b}Ѩ)\r¥\u{c68f3}\u{10081f}\u{10dd8f}": bafyrwibh3uv7biwsbpe37hkwovhrpkm3l7s5icdgji6imq5fjykcl7p24e, "$'Ⱥ':\u{f6dca}{&\u{7d81d}GQѨ𧗿~.Ѩ\u{3f8b4}\u{feff}\u{2}\u{2}`\u{b}\u{202e}\u{7f}\u{8}\u{b45ad}": bafyrwiczadd6c3wkaraxq3lg5clhqyngn6fprrjscfzxfe2mp2kg4pvxba, "$}\u{4998f}}\u{7afe3}\\\0n?\"V\u{1}\"{!*y%\u{1b}\rꢢѨI\u{f13fb}X\u{1061f7}\u{7f}%'l\0=\0^)\u{5ac7e}<": [60, 118, 165, 67, 27, 119, 21, 252, 153, 133, 75, 209, 126, 27, 107, 9, 142, 133, 201, 242, 96, 56, 139, 85, 39, 185, 191, 172, 121, 16, 61, 101, 100, 93, 23, 114, 153, 201, 213, 220, 14, 1, 73, 194, 75, 71, 226, 125, 205, 187, 97, 24, 229, 247, 75, 87, 172, 90], ".`\u{bec17}Ç'\0/\u{d491b}𘅣🕴.\r": 1.2131909958473984e-266, "/\"": bafkr4ifg4p3nsgepq6lvarhhrt4lxydl5arndmqieaknvnqhyvw4l62eau, "/,/\u{b}\u{a9092}\u{3}.\u{7f}ñu?Ⱥ'&\u{e2082}\u{3d612}u": baguqfiheaiqibdayxvhi3ktwv4rfqhx7adreiuo2muqdzc72zj7wydxi2ircenq, "/.\t¬79\u{202e}<%Z\0\u{7}\u{b}&\\🕴\u{c07fd}j=(1\0𮟻mJ�.\u{fe739}ȺXB\u{feff}": [35, 209, 94, 4, 132, 12, 218, 13, 76, 94, 110, 176, 233, 104, 74, 8, 231, 103, 204, 48, 35, 1, 208, 81, 103, 39, 15, 209, 174, 232, 89, 234, 13, 222, 88, 45, 83, 226, 52, 81, 83, 59, 125, 241, 159, 38, 79, 5, 21, 197, 38, 169, 183, 154, 154, 183, 251, 209, 151, 212, 196, 132, 155, 189, 109, 45, 206, 253, 141, 141, 226, 210, 69, 80, 142, 13, 168, 40, 84, 52, 228, 159, 231, 76, 229, 248, 47, 21, 115, 15, 247, 164], "/.\u{b}\0gMg%\0\"n\u{1b}�\u{dc8ef}": null, "0\\\u{b2c71}:.\u{7f}.q\u{ee1a4}\u{3}.`:=\u{7f}shÕ\0'\u{ff5c2}\0<\u{48d2d}": null, "6\u{7f}z\u{fb43d}\0\u{e71c2}n\t\u{1b492}\u{be144}%m\0O£{\u{4}ß`S\u{cc20a}\t\r@\u{1b}\u{2f392}T\0�:4i": [184, 132, 8, 217, 132, 246, 183, 246, 210, 254, 92, 125, 142, 179, 49, 205, 173, 48, 36, 66, 57, 14, 184, 195, 88, 109, 101, 153, 91, 53, 120, 69, 198, 5, 81, 144, 203, 189, 119, 182, 143, 191, 14, 61, 34, 36], ":(`T\0\u{1b}\u{1b}\t$¥z\u{36613}\u{a5f2d}Ѩ\u{d1ec0}Ⱥ\u{7f}\"'u\u{b7fb7}ë` \u{feff}\u{5d6e0}f\rê🕴": [191, 71, 93, 227, 249, 253, 186, 190, 214, 13, 71, 250, 111, 182, 66, 219, 22, 139, 183, 203, 250, 58, 184, 20, 20, 213, 80, 32, 66, 40, 214, 69, 53, 203, 170, 113, 150, 95, 7, 238, 77, 250, 5, 90, 119, 215, 135, 80, 225, 91, 49, 36, 143, 153, 196, 238, 205, 224, 18, 127, 173, 184, 246, 90, 134, 17, 119], "=@\u{8f9f3}7?\\𫥂\u{1}": "i|*\u{c09ed}'\u{b871f}\u{4}*\u{202e}&\\\u{105cbd}bѨ\\*?{F\u{feff}uȺz\0`\u{feff}{+", "={": null, "=\u{a02a9}\\\":¥🕴\"\u{202e}P\u{7f}\u{b7f71}\0\u{3a7e9}Y<î\u{157d9}Å": [19, 138, 165, 245, 200, 2, 67, 217, 45, 240, 188, 107, 149, 30, 61, 57, 176, 165, 123, 163, 219, 5, 81, 31, 155, 252, 139, 204, 155, 201, 120, 3, 32, 18, 218, 98, 147, 233, 22, 106, 60, 86, 204, 202, 147, 32, 223, 159, 66, 107, 250, 251, 19, 167, 246, 252, 90, 205, 212, 54, 251, 5, 241, 43, 213, 120, 23, 1, 29, 163, 69, 234, 99], "?&🕴.*.\u{605a3}¥&j/=a\\\u{b}*Ѩ*'¾�ธ#Ⱥ\t": "\u{7f}\u{8}Y{5\u{60a62}jN=f\0'𩧦\u{feff}\u{7}//a¥kK\u{48989}&\u{f203b}\"\"`", "@Ѩ": "/*\u{95147}\u{b}{`\u{4e71c}", "C\u{3}<Ò?B¥@&": [130, 96, 105, 54, 223, 53, 178, 56, 46, 187, 18, 221, 159, 48, 247, 89, 117, 41, 108, 127, 91, 200, 247, 120, 240, 237, 155, 170, 65, 55, 58, 141, 247, 254, 60, 102, 29, 148, 211, 55, 4, 14, 68, 42, 90, 173, 222, 142, 20, 22, 68, 185, 132, 255, 132, 185, 189, 197], "D\u{a65a4}$\u{df148}\\F\rntѨK\u{1b}jd\u{8ea98}\u{b106e}�": 1.3784978135410346e-170, "Eò<.z\u{781d5}\u{2}\"�^": null, "H\r\u{361f5}{\"�\"\u{41b3d}\\'𦀳¥\u{80}Ⱥ\u{c26fd}�6-`N\u{dca3b}G\u{1de7a}=\\": true, "L\u{7f}:¥=t*{_AP\u{366cc}?wȺ\u{eb31}o.\u{881ae}\u{77df7}t\u{b}\0{\tѨ\u{d6c3a}": [97, 251, 86, 194, 220, 214, 153, 209, 190, 118, 25, 200, 75, 133, 240, 162, 84, 193, 128, 3, 238, 222, 6, 149, 222, 157, 239, 202, 16, 102, 50], "S=U\u{bff90}/?\u{da120}.\u{633c3}a|\u{1c959}\u{3be11}\u{feff}X\u{a9622}?\t.Å.🕴=\u{73c63}𰰅\u{61725}¥\u{a6664}<\u{895f4}i": [140, 113, 176, 106, 69, 9, 192, 221, 52, 44, 56, 187, 134, 114, 29, 65, 208, 39, 0, 95, 237, 224, 76, 195, 8, 225, 21, 98, 228, 60, 95, 240, 189, 156, 136, 235, 72, 132, 236, 170, 1, 250, 184, 134, 77, 48, 249, 199, 172, 3, 66, 201, 75, 15, 29, 254, 104, 111, 158, 53, 80, 85, 74, 36, 20, 15, 150, 52, 31, 50, 233, 6, 228, 244, 185, 202, 142, 56, 126, 47, 69, 35, 225, 162, 147, 42, 172, 213, 71], "\\\u{1b}Ç'(\u{76ac1}\r\u{202e}NÙ\u{87e35}`{\u{52666}Ѩ\u{7f}\u{e28bd}?窣\0\u{e4901}🕴\\": 1.307777027892035e-308, "\\V$\u{71773}\0.H𘑞\u{202e}`&)'\u{cc7d7}4_�📲j.\u{c5777}:\u{b}{\t?\u{abcf6}\u{2}\u{f114a}": null, "`\u{1}7\u{dc7f6}è\u{1b}\u{e6461}", "bF罩`/?{\u{6}m�🕴\u{a0dcf}\u{1b}\u{85683}\r*z\u{3}`\u{202e}\t\0¥\0ó�\u{fa710}": null, "f\u{71088}\u{eeba2}/\u{1b}\u{b}>\r:$H\"ï\\\u{7f}\0¥{+?": null, "k:\t<鏁\t^:u碗\tñr:": bafkrwidekl2tflko33qlm47p7adikj4q6bdju2kq5elnuipzykk565mhby, "s<Ⱥ*[L*<ѨÉ\u{88d19}": [88, 175, 1, 80, 241, 221], "u\r": -2.6904580729605092e122, "u¥\u{feff}\u{1b}": [1, 36, 101, 136, 222, 223], "x`\u{6d787}\u{e5350}\u{df1fb}(>qP🕴\u{ba6ac}𮠰🕴\\\u{4b5e0}J<": false, "{\0\u{3b4bd}=g¥\u{107402}\u{5c069}Ѩ=5k*$`<%p": [42, 216, 219, 136, 90, 214, 91, 194, 12, 10, 62, 81, 119, 54, 126, 138, 227, 206, 148, 138, 95, 191, 247, 89, 212, 142, 18, 121, 148, 163, 152, 177, 37, 69, 222, 22, 226, 117, 153, 5, 89, 234, 92, 155, 223, 10, 158, 200, 18, 37, 110, 103, 181, 109, 202, 35, 39, 117, 93, 5, 90], "\u{7f}\u{7e6e9}\u{8b}𥚝": baguqehrahkajpop4geeiwsi7gu2wdsuybkf6lbq6rfen4f7v3vrtqbyazd3a, "\u{7f}\u{b601e}'\rK/\t\u{feff}¤?\rp\u{af66a}": [73, 33, 204, 97, 67, 215, 167, 191, 121, 17, 85, 93, 75, 141, 150, 250, 223, 157, 229, 29, 48, 217, 58, 27, 191, 36, 145, 93, 14, 17, 12, 32, 61, 36, 83, 58, 181, 136, 104, 35, 237, 219, 146, 240, 223, 170, 203, 45, 27, 109, 190, 5, 96, 122, 180, 241, 211, 211, 147, 12, 136, 219, 25, 19, 108, 191, 73, 165, 95, 129, 7, 16, 40, 44, 123, 182, 100, 246, 148, 80, 99, 191, 137, 144, 177, 240, 82, 242, 163, 30, 210, 13, 45, 140, 6, 196, 122], "¥O\u{7f}Ⱥ𗢎e/\u{10697e}\u{202e}\u{7f}0�Ⱥ𤂹\u{e098b}\u{8383e}`G<<\u{b94d8}Q\u{e6032}\u{cf7bc}\0:\"?\u{1}\u{1a8a3},": null, "¥\u{202e}'\u{8d073}\u{78d6b}\u{6e610}\":E?\u{9bb82}Ѩ<�>\u{4c02a}//\u{a27e4}=\u{f456f}<3𧉟/\u{98}\u{f3520}": "x=`ᒼ\u{12af8}\"=", "Ò\u{51a90}�\u{1b}\"䩖𦄱\u{b3b7f}\u{aee50}<🕴{\\A\\\"X\u{f32ab}::\u{4}\u{c7c01}`\u{1b}\u{4}&\u{ff44f}": 0.0, "\u{86b18}Ⱥ<": null, "\u{a0feb}&¥\u{106aba}{B\u{be082}1\u{62718}\tS\u{aa928}\u{2}\u{3d1dc}\u{f5e89}": [16, 76, 209, 135, 206, 142, 16], "\u{afe7a}A`>\u{dafee}𡪩b\u{77678}%\r劗?\u{10819a}^.S\u{7f}\u{94083}$�\rA": 5, "\u{b2ccd}ë9('*\u{b}2\u{8}\u{c9f32}\u{5a462}\"?2\u{b}\0W�\u{3e6e8}th\\=\"F": null, "\u{e6ebb}r\u{202e}\u{80bd8}:🁚\t�\u{fd427}\u{b2fb5}\u{4badf}w/\"0\u{202e}<\u{15010}^v\u{53eb1}\0='": null, "\u{f31e7}41\r\u{68a9f}\u{202e}\t.p\u{4ea54}\u{860c8}\u{19987}l": -0.0, "\u{faf52}\u{6}¥\r\u{202e}䲖\u{202e}R&\r\u{973bd}\u{feff}l\\\"$\u{e5961}¥\u{d805a}\0+OB*tu\u{eb5a9}\u{109146}Ⱥ\u{759f5}`\0": null, "\u{fc398}\u{2}\\.\u{ee493}\u{d0de6};\u{ed8fa}🕴<": null, "\u{10ed00}?\u{7f}�`\u{b}L*\0®": -0.0}, "\u{7f}':e/Ѩ\u{36fde}`/\u{202e}\u{7f}=\u{feff}🕴\u{f28b6}\r": null, "¥": -0.0, "¥ :?\r\u{70234}": "�¥", "¥i🕴\0\u{93569}\u{71ace}\t:\u{75563}*+": false, "¥\u{be29b}\u{202e}\u{7ef68}5\u{1b}`\u{202e}\"\0\u{feff}\u{10e7da}�\u{7f123}|\u{8c8c5}n": 11, "±:𢽭🕴t�%r\u{8}?`^'r": false, "Ⱥ/\u{882e4}:\u{e0f71}$%/{\u{36b45}:,z\u{8}[1\u{7f})%\t\u{b}v\r\u{aaa37}\u{8}𫷝\u{7f}\u{46db0}¥\u{ad5a1}ke": -12, "🕴\u{feff}\\x\t\u{6}\t~\u{39f68}\u{202e};�\u{1b}6:/3\t\u{7f}": bafkrmiepyixjho2atqqcqgtd575iqsam6klfzdtkx5g4wummups24pdqcu, "🕴\u{5de4c}\\�𐝣\u{feff}𱴜\u{feff}": "\u{6}\r:\u{190a7}�%Ѩ𲇭:\u{6}_🕴Ò%\"'\u{e1b6d}F'\u{f7ad8}\u{57ad7}`\u{9c70b}$`\u{202e}\u{4b5bb}UK\u{90}", "𣉓��e\"$0R\u{a837d}\0<🕴?@,": true, "\u{2fa70}\\𪚅¯\u{108509}\u{4ab0e}\u{4ecc6}\u{bebaa}B\u{1}\\\u{f24f8}'%ð\u{202e}¥\u{3a476}/%Ⱥ\t$vyC]>+\u{87304}": [94, 245, 167, 7, 68, 250, 234, 108, 122, 64, 21, 238, 150, 215, 116, 48, 232, 184, 232, 43, 30, 213, 149, 183, 215, 1, 242, 95, 189, 228, 190, 9, 43, 222, 14, 254, 203, 238, 30, 51, 216, 40, 125, 87, 240, 71, 185, 206, 114, 87, 214, 85, 33, 163], "\u{373f7}": true, "\u{4f31f}*A?\\${\u{91276}\u{52eb1}#\u{b}\u{1b}": -47, "\u{5e975}🕴z${'\u{5d19a}b\u{aa904}'J}`Ѩ9": "\t=\u{1}±\u{84533}T*\u{4d0eb}\"A\u{8}<", "\u{69742}o\u{81}n<@\u{feff}L:\u{1041f7}🕴\u{8}𬾧\u{7f}\u{108007}?[\u{feff}\r": [31, 200, 160, 81, 110, 124, 249, 10, 85, 215, 192, 121, 17, 28, 185, 121], "\u{6cfa0}\u{6}𡽫{\u{feff}bJ&\u{5}`x\u{d2999}d&\u{badf8}\u{1b}\u{365dc}'�\u{5a37e}": null, "\u{a202e}~QE?\u{b}\u{7}/Ⱥ/\\&\u{fb894}\u{e0565}\u{2}�^?Ѩ'": [146, 41, 216, 116, 157, 252, 155, 192, 137, 113, 110, 231, 49, 142, 206, 2, 93, 201, 68, 48, 93, 58, 173, 197, 93, 40, 41, 112, 193, 244, 79, 228, 221, 71, 177, 129, 102, 97, 55, 137, 3, 148, 166, 101, 253, 166, 170, 139, 23, 120, 178, 140, 132, 104, 89, 46, 120, 30, 154, 194, 138, 39, 103, 152, 6, 136, 237, 241, 138, 193, 248, 33, 185, 56, 220, 118, 23, 17, 132, 220, 239, 90, 207, 237, 94, 75, 90, 222, 46, 180], "\u{b2ebe}*\u{84}.\u{a909e}7ѨjV𠷃Ⱥ?ü\u{6}u𝣒L\u{34303}:\u{bc294}?DZ\0\u{7f}/\u{103c3f}\t\\": bafkr4ihqz56mmzigglx6apxygvxcnreb7hjve6eftqfcooxtdzyzahyil4, "\u{c3588}*\"\u{8}\u{51cd1}躅\u{8f2d6}u🕴*\r\u{1}7🕴\u{71aa7}{\u{77c1d}%\u{cf015}\u{7a969}\u{8}\u{3cbf9}\u{92}\u{b}*\t\u{15db3}": "/{ªN?\u{8711e}=\0g{\u{202e}\u{202e}¥.\u{1b}H%\u{95}\0r%:", "\u{d7ffe}\u{44f52}\u{1b}\"%ỵ*m\u{5}$\r¥Ⱥ\u{1b}Uo':\r/%'<Ⱥ": -7, "\u{e0680}={\u{fd1e9}j<'CѨ�3\u{ce0a4}Ѩ\u{4b081}C\"`%9GGv\t\u{10322d};Ⱥ\\\u{e1a9c}\u{202e}\\": true, "\u{e5e60}\u{b}{\u{e3551}=\u{b5edf}<Ⱥ\"#?\u{7}\u{80}\\'5f=\":𪟤=\t\u{dc111}*<\u{346be}": true, "\u{ebf8a}Y\u{8}\u{f98c9}\\\u{7f}9🕴\u{4e9df}=\"t": [222, 226, 183, 11, 204, 93, 208, 118, 17, 243, 19, 124, 63, 137, 157, 195, 178, 3, 70, 223, 216, 164, 164, 66, 246, 151, 76, 100, 93, 11, 7, 234, 146, 38, 18, 169, 97, 221, 8, 133, 14, 197, 108, 213, 201, 214, 93, 202, 188, 165, 171, 86, 195, 223, 164, 6, 51, 234, 214, 26, 158, 32, 12, 35, 10, 245, 171], "\u{ffc11}#\u{b}¾": null, "\u{101fde}\u{b6522}'.&$": {"": 1.5658615101276272e160, "\u{2}=\u{5bd45}?o뗡p\u{2}\u{b}\\Ѩ¥\u{8b3a4}ib=": null, "\u{3}𤥺": null, "\u{6}¥\u{feff}%\u{202e}�Ѩ%\u{10df7a}$%\u{2}\u{b}\u{59972}𝝊\u{5107d}\u{202e}\0.k%V\u{55f7d}±": "`\u{7f}\u{4e0cf}\u{f7dab}\u{7f}zw[Ѩ.\u{b}Ⱥ\u{202e}\u{1b}#�\u{61e5a}\u{569c2}:\u{51a0f}\u{1b}*R\u{bb37d}:🕴{+Rf\u{ea9f}", "\u{7}Ѩ\u{1}\u{dca54}t": 17, "\t`": [25, 228, 9, 39, 85, 50, 121], "\t\u{2ef3e}\0": bafkrwibffxly7lxjvxuvp2ic3ao6f2icoflqqiadi5dvz4yvpdsyd27jne, "\r*\u{1b}\u{4}{`_\u{feff}\u{b}¥.K\t<\u{3}&\u{7}$🕴\u{7f}": -40, "\r\u{587e8}Ⱥ": [207, 53, 166, 39, 184, 202, 32, 200, 7, 155, 18, 5, 102, 226, 211, 93, 116, 183, 163, 131, 161, 249, 63, 112, 198, 231, 166, 86, 176, 31, 56, 43, 28, 142, 150, 56, 244, 64, 75, 60, 122, 214, 109, 138, 103, 217, 188, 127, 42, 7, 30, 47, 190, 51, 7, 110, 190, 106, 215, 102, 86, 94, 41, 76, 159, 221, 62, 236, 96, 83, 215, 26, 102, 233, 126, 117, 233, 43], "\"z=%`\r�ñ%:Ⱥ\u{ee132}.}Ue'\u{8}\u{a6c62}": bafyrwiefif6srseryrwhg7lx5ddm3cuzdwepr4rmvorftfhq2vj57d2gsq, "#\u{75c49}!": "\u{7f}\u{5a95d}L\u{32a81}\"{\u{7}¥²�\u{e3087}O'!\u{ab00c}\r./ \u{202e}h🕴'", "$\0\"\u{ca931}\u{2}\u{1}:\r\u{3}\u{63225}g¥Ñ": true, "$Ѩ\u{bd470}û54Ⱥ%¥\u{feff}": null, "$�0\u{a8b97}𱗶\\0\t$": 6.488444060884696e306, "&'\\w:<<%\u{feff}\u{5}\u{1b}`¥/?n𡚋&,4\u{5e1f3}\u{4b359}\u{fbe37}i\r\\\t:": true, "&?\u{6}r\u{c3665}\u{7f}i%": baguqefranz7i2sd2lryjkxvq7st4ujh3xjdttm6fldqgbci5rlbzwbet2dma, "&?%": bafykbzacea3napu4rwrzbqevwhiaptytsgo3gscof2glvjfje6h75nyc5npb6, "&\u{7f}\r}>`\"%\rY\")>\0\u{1b}X\u{b65b4}": [93, 89, 232, 156, 166, 193, 205, 122, 207, 235, 2, 186, 202, 177, 228, 245, 238, 222, 129, 191, 32, 176, 162, 238, 204, 162, 152, 39, 105, 236, 197, 76, 201, 44, 148, 170, 224, 150, 87, 94, 134, 189, 238, 51, 118, 124, 144, 5, 252, 27, 17, 111, 250, 236, 173, 159, 46, 45, 170, 32, 133, 60, 134, 50, 28, 47, 178, 197, 160, 126, 143, 31, 160, 25, 175], "'d'\u{4e27c}\0\u{b4b7c}`{?\u{3cff7}Â<\0": "h\u{feff}\u{15730}\u{c364f}]WV.TH\u{feff}.Z\0.\u{9d3ae}$\u{e5559}?\u{d403d}\u{7f}%(k𠢘", "'i🕴\tb{\u{7}\u{feff}&\u{49079}'S/\\\u{10600a}\u{af8aa}\u{3a6a6}\r": false, "'ѨH8'Ѩ{\u{f5f05}ѨQ\u{d5ba2}\u{3773e}::\u{eb168}:)CȺѨѨ": baguqehra3ml5lg6x5oaboqvr65pulvfntbvdrj6v5zyvuaf2j7bpx7wkhbpa, "*I¥w\u{b}\u{7b9a4}\u{3e747}\u{b2e81}/y<5'*S?$$O\"\u{7}\u{9f10a}\u{8}S\u{7a8bd}I\u{7f}U%": null, "*\u{202e}": [61, 242, 249, 132, 34, 222, 153], "*\u{861a1}//\u{10ca95}`'𓀒\\'=Q;\u{1073cb}M\u{5dbd9}": [34, 41, 251, 216, 124, 171, 190, 175, 12, 20, 65, 118, 149, 193, 33, 184, 96, 7, 181, 39, 92, 97, 36, 84, 72, 99, 69, 241, 55, 2, 31, 74, 55, 172, 146, 32, 230, 228, 234, 148, 164, 27, 55, 71, 222, 203, 70, 239, 15, 85, 157], ". \u{202e}v'*<\u{3c008}\u{ea8b}c¥?g:5$Y¥\u{5a3ec}": [60, 209, 45, 129, 120, 83, 199, 46, 198, 62, 131, 13, 210, 117, 41, 136, 9, 59, 142, 242, 198, 194, 181, 206, 19, 221, 114, 94, 216, 25, 58, 191, 30, 252, 131, 130, 133, 109, 116, 32, 231, 108, 160, 173, 195, 103, 78, 179, 165, 157, 227, 152, 245, 222, 164, 242, 208, 136, 66, 6, 54, 146], "/'%\u{3b4df}`𫨁e*\u{71a70}=\u{c5203}\rȺ궖JȺ/\u{4bb7c}\u{3}*#\u{d3bde}p\0qÙ\u{b}🕴\u{202e}\u{202e}r.": false, "/{H\u{10c9ea}\u{79be5}?\u{ca12d}": 8.6395723193706e-309, "4\u{54c08}'`?": -8.973748901720123e-29, "6\u{883eb}Ⱥ$\u{1b}": -1.0199828187670042e-159, ":<\u{1a04a}U�": -1.8179682492493788e-167, ":n\u{8a}\u{8}?𡜬\u{7f}F'ýr0\u{1edbd}%\r8oѨ\u{1b}": "", "<=🕴\u{dd6ce}\u{3}\u{65218}�\u{b3002}\u{81}<\\//z=\u{6106d}*Ѩ\u{8090a}hC\u{88ce1}\u{1b}\0🕴\u{ac262}\r/": null, "?*\u{a6066}aѨ\u{7f}\u{3}\u{1b}`'\u{aa717}\u{14b1c}o&\u{8d098}É?Sv\u{7f}¥`\0\0<]\u{15ce3}\u{1}=:$=": [66, 253, 182, 100, 225, 215, 132, 63, 183, 229, 8, 172, 23, 12, 49, 232, 47, 74, 175, 130, 83, 252, 88, 185, 110, 27, 188, 151, 251, 208, 54, 250, 230, 62, 210, 35, 23, 251, 21, 206, 161, 29, 185, 225, 190, 48, 125, 83, 49, 116, 98, 148, 46, 18, 204, 43, 23, 115, 8, 245, 113, 58, 152, 205, 222, 26, 169, 223, 244, 99, 43, 185, 3, 45, 50, 190, 66, 69, 188, 185, 150, 232, 128, 102, 195, 99, 78, 226, 251, 253, 84, 106, 110, 178, 192, 203], "?\u{7f}\u{b}\u{9f2c9}i\u{10bb8b}:{&8\r𠒫*t�\u{1}H\u{98f18}\" \u{c92b6}\u{8})\u{7d0c3}<Ï": false, "?\u{69bad}I&¥@🕴n\0??C\u{607cf}\u{7f}/&\u{b}🕴..qѨ\u{1b}": "𘉢`\0=\u{cc51f}\u{a4fb8}\0\u{47e89}/\u{b}\u{a0a22}±\\\u{feff}\u{feff}/.ck.\t\u{e3434}j", "A\u{e0fe6}'\u{202e}`%\u{202e}\0<\u{c18d9}/:\u{10b6bc}b\r*R:\u{feff}\u{3}\u{10ba3c}\u{84727}\u{c8ebd}^": -6, "C\u{c5e80}*g🖻\u{66e08}\u{9d567}{'Ѩ\u{58cb7}\u{9a1ca}¥Ⱥ\u{19181}\u{b1066}\u{5b8ee}\u{b9fcf}= =\u{dfbde}\u{18d8b}": "\u{5}\u{4d180}M\u{b}\\.Ⱥ*è=.e", "I*\u{96}\t=$\u{4747f}\u{5}\u{1b}n=a&rȺȺ\u{6cd13}": [230, 44], "R´\t\u{1}&\\\u{1b}kN\u{1}{´<\0\u{95a25}d": "k*`$U,\\\u{feff}\t\u{916a3}\u{849e2}<䨑\u{a2dd6}\u{9126c}", "Yq🕴\u{c5293}y:": 21, "Y\u{101e9f}JE{\t\u{7f}\0\u{4a162}aѨ.\u{7b388}댦\u{81d62}Ѩ\u{a6fb0}\\\u{dec90}\u{1}\r\u{b}/": null, "\\": -7.477551823968463e-129, "\\<\t\u{e9e4c}\u{b}:\u{a258b}¦\u{53415}5G\u{7}": true, "\\O\u{9e0a1}K.𠓎\u{fb34d}\u{7469e}\u{4c980}.:\u{bbb4f}8\"J:/\u{829d7}:\u{1}𣂇Ⱥ*J?\t\\\0\u{b}Ⱥ\u{ea85b}`": bafyrwihiw4vja2le5bbo63lbzgzdc7npl6b6ixw6nlheeirllwo5zpuzuy, "_]:\u{e11f7}¥On/<:\t=\u{1b}": "G\u{b}\u{71804}'🕴Ѩc\u{feff}<%Ѩo\u{1b}¥", "e\u{70c00}\0¥\u{202e}": [207, 96, 93, 148, 103, 198, 140, 36, 26, 221, 215, 6, 202, 231, 233, 247, 107, 178, 240, 144, 50, 206, 143, 13, 95, 15, 171, 239, 158, 75, 248, 192], "f\08<\u{9c37d}?\u{10f8f1}=`\u{95}Ⱥ{.\u{1b}\u{15cdd}\u{202e}Ⱥy<$\u{8}$\u{6c5d0}K\u{c4f04}\u{19f0a}": bafykbzacede6nne55uave2wj6epfrbud4xcozirijfysx2p3a4isvzez7oiza, "gÔ\u{b}繎.U": null, "i\u{889f5}r\u{e98e9}<Å\u{ad256}\u{b}R\r_%\u{d8e2e}/%r\u{b908c}%\rz~'\u{e04db}.\0\u{d9659}$\u{7cda4}`)": null, "u\u{a1658}0\u{f2355}}🕴\r": -7.035669443392254e-42, "v\u{10b659}�\r!o=\u{a24cd}]XÂf(\u{859d0}w\0.:": bafkrmihatl6qchcaitd2pcbxodwzlgbynl4rpzu6fnfcy3wgudnxpnabpm, "{%\u{61d33}=/EѨ/\"?�\u{5ca71}0\r": null, "{/:/JK\u{62214}Ⱥ¢\u{b}1o\u{5d859}\\\\\u{b65b6}*¥Ⱥ\u{1b}ѨѨ": bafk6bzacebf3fnemqzl52apgxmcok4gweclupgjrxwtee6ci75qsawcnyjuw6, "{:\u{f901d}\u{da30b}*:𱪙\rF\t\u{405de}\u{feff}%~\u{7f}*=\u{46e3e}\u{b}\u{202e}": true, "{f\\\u{b}\u{1}\u{7f}\tD\u{b}&Ⱥ𣡼\u{e84fe}J\u{d2ef7}E\u{1}'\0f\r\u{6}\u{4a79f}Dx\u{2}": -13, "{\u{d9a04}&ö%\u{7f}\u{d686e}&´": 7.588162654188097e-308, "\u{7f}\t\tj\tw\u{feff}Ѩ�練\u{3}:𧸨\u{a3acd}\u{101251}'\u{42a8f}E\u{3e47c}\u{7f}\u{7}&¥%<𩌯c": bafyobzaceco6clzsn57sjq2u5patyvukq3ksi4wyrztinj5srwspsi63qyfqc, "\u{7f}c\0\u{ffc41}<\r^Æ\u{9f}Ñ\u{b6f14}U\\\u{c45cd}\\\u{1b}g\u{ed9b9}🕴{\"R\u{60150}\u{b54c0}I:\u{10a33a}": [126, 247, 86, 174, 51, 26, 21, 148, 201, 8, 130, 49, 162, 197, 14, 242, 15, 142, 249, 226, 194, 131, 116, 158, 109, 70, 138, 10, 113, 85, 166, 21, 217, 49, 112], "\u{7f}\u{39cc5}'🕴\"\u{71cc2}&\u{7f}'": -4.297679451122769e-29, "Â\u{4}\u{feff}<'\u{c2f64}ѨȺ\\2\\\u{41534}*{<�\u{4a196}'8$\u{f88db}\u{feff}\u{feff}\u{9e1a6}*\u{cc465}\u{101f58}\u{dc98c}\u{b}ȺP\u{87281}", -29, bafkrmigi5ucblo6m6dbnw2y7po27ahssm6r6gwfog5w2eaxd7ze64u3vki, "(", -50, true, 4, null, null, false, bafyr4id3ardj34fsaufowtepdegrlkvrkv4jmyiva54gz4afht26prarau, [206, 72, 170, 13, 12, 162, 106, 248, 83, 67, 95, 57, 191, 163, 90, 243, 125, 164, 14, 6, 150, 168, 71, 206, 239, 41, 111, 27, 246, 79, 53, 193, 98, 168, 210, 107, 64, 164], -3.33502927060181e-309, -49, null, 3.5445664834722476e19, -1.563337693800014e-308, bafy2bzacedec3zioqwt4bdhiel5jmdrot2fhkwtx4u3fqug5x5djkkgop6rbm, -7, -18, [216, 49, 164, 168, 254, 77, 204, 85, 63, 36], baguqfyheaiqipcrhl2ltrwj5ykc3gnwp2i4i6m3wqqrmfi4ap7uchecatjrmp7y, -0.0, [64, 148, 93, 100, 165, 243, 24, 181, 67, 141, 104, 128, 194, 110, 216, 253, 234, 40, 83, 43, 194, 82, 152, 207, 225, 141, 174, 148, 236, 77, 110, 231, 110, 107, 120, 161, 188, 242, 70, 187, 10, 8], null, null, bafyrmigihlvyxai27ay3ws4ytyoickvlnpli3rxing2zlvcrwklqgvhp6m, null, 8.12819559637677e-221, 14, -2.3233856055803314e-132, true, [33, 203, 100, 207, 226, 57, 37, 141, 9, 44, 127, 89, 88, 208, 26, 54, 107, 192, 81, 78, 170, 47, 90, 250, 222, 53, 73, 136, 60, 141, 50, 179, 193, 1, 240, 61, 212, 102, 158, 81, 130, 103, 57, 0, 144, 186, 253, 79, 200, 167, 150, 245, 107, 237, 87, 24, 222, 200, 67, 180, 136, 164, 87, 137, 92, 199, 194, 10, 174, 65, 1, 168, 43, 25, 36, 186, 55, 153, 217, 27, 78, 197, 170, 47, 74, 87, 78, 123], "&t\u{51b31}\u{10b5b7}·Ѩ$\u{491b7} \u{e82db}S`\u{13ba2}$\u{feff}\u{3}🕴V¥\t{*\u{79d01}", true, 32, true, 9.168173353619497e259, null, 0, 41, "🕴\u{6ad7f}/V\u{8d90e}\u{7}\0$\\\u{1de07}ѨDw'\u{5f7ad}�$$🕴qa", [183, 153, 172, 129, 204, 68, 233, 142, 34, 132, 183, 94, 210, 53, 181, 118, 12, 36, 147, 178, 163, 147, 226, 164, 22, 84, 47, 52, 68, 11, 3, 202], true, -47, "'\u{7f}\u{9f}{\u{4bd1b}${{\u{9f533}\u{4bd4c}$\u{49671}`\u{5}🕴|�k\u{c46c0}\u{1}🕴*\u{feff}7\u{c7f8f}\ra:", 24, [124, 23, 6, 17, 136, 174, 8, 255, 36, 94, 92, 161, 178, 30, 64, 18, 140, 103, 208, 64], "\u{41adf}\u{b}\"\u{bb2f8}\u{7f}Ⱥl**", bafybmic7lxxicy772aqqwgxzod5qnr2q42gvznf7ybo3focmp5garzkofe, false, null, -1.453409372917667e275, -4, 37, -6, false, "H\0O\u{de3c0}I 욽;\u{e55dc}/qY*\u{9b}$\tHô^\u{c1add}.\rȺ:&_\0�\t\u{f05bb}\u{a5ba9}\u{fac7d}", null, true, "U\rȺ\rÖU\u{3e4ac}:\u{a58ed}/|\u{b}\u{74e95}$\\[x\u{b652f}'/", -48, -3.21819364036035e-56, 9.213513078060828e-91, "'\u{890b8}=\tx\u{d5bf7}?\u{a4dd2}\u{9811e}\u{a5c67}": 6.664526628123302e214, "&\u{54d6c}//\u{d211d}\u{7f}�": "¨\u{202e}\u{413db}T\u{1027f3}\u{b}O`#\\\u{a494a}\u{3b0c6}s.\t\u{103dc}:u/\u{a5e48}A\u{202e}\u{77c5c}🕴ȺmM'\u{6a3fe}K", "'\u{1}": "\t🕴\u{eab5e}\\\u{bab9b}\u{ee77b}\u{52d9f}\u{ab141}{\u{6}", "'\"9%?": null, "'#\u{6}\u{bdef5}¥\u{ca946}\u{7f}\u{202e}": 1.7292683522733168e-61, "'M\u{d3d34}\u{202e}$\u{3}\u{42800}𗸴\u{7f}\u{62be1}<\0¥<\u{202e}\u{feff}/Ñ4\u{f92f7}{´": 9.820150129594855e-167, ")C\u{ddeef}\"\u{c2e4f}": [219, 75, 110, 213, 246, 194, 60, 45, 146, 98, 88, 191, 123, 141, 55, 76, 57, 2, 29, 8, 227, 206, 96, 136, 115, 139, 137, 243, 222, 156, 251, 57, 28, 97, 118, 49, 218, 195, 184, 189, 188, 165, 205, 70, 190, 145, 58, 42, 104, 26, 29, 3, 218, 118, 2, 133, 228, 7, 235, 110, 91, 232, 215, 78, 99, 2, 64, 17, 72, 56, 70, 160, 255, 37, 168, 19, 29, 27], "*´\\yn<%\u{3cb12}\u{99}&\u{5}Ⱥ/\u{7}𭆼\u{4263d}\u{88dca}Q<\u{efab0}\u{f6ec2}": -1.1059496664576365e153, ".\"\\\u{3}{\r\r:�\u{81c10}\0Ⱥ\t>{\\:\u{e877b}\u{5e93f}": [65, 186, 167, 66, 51, 133, 164], ".e$Ⱥ6\u{63ca7}Ѩ`u\u{33bd2}'n\\\u{48590}\u{e646}Q\u{a08f7}.Uf": null, ".\u{9d849}\u{f94cd}=𖩡\u{bc869}¥+$5?\u{1016e0}`䎟\u{1043c3}\u{6c91d}\u{667af}🕴": false, "/\0\rѨ^\u{1a826}\u{202e}\u{7f}\u{fb3dc}\r_�\u{3}\r\u{7f}&ï\u{3}:8\u{9ed75}q𪥇\u{1b}\u{e8fa3}\u{4}𧽨\u{4efe1}\u{977d9}\u{3c6dd}\u{cfeed}X": [17, 67, 94, 98, 195, 0, 112, 199, 95, 233, 81, 197, 28, 69, 112, 207, 197, 72, 18, 50, 114, 108, 53, 31, 197, 3, 225, 75, 134, 216, 253, 203, 70, 71, 48, 33, 235, 32, 133, 245, 9, 224, 57, 91, 77, 233, 171, 9, 39, 186, 240, 49], "8:\u{c9e98}\"%1🕴<🕴\u{977c5}띦b4'": true, "9Ⱥ\u{84761}a\t\0": "î+%\u{202e}\"\u{c9bb9}*\u{2}\u{eb474}\u{4c05d}R{=\u{feff}\u{e7f1b}\u{1}\u{2}<Ⱥ-=", ":<'": bafy2bzacebdndpakl7grp6t2qzddcmrtpw4klywtvk77qimmd5fya7havzbyk, ";\u{8}�\u{1b}P'Ⱥ": null, "=$ȺN\u{e377a}\u{5a12b}`fW\u{7f}\u{2}:\".": -1.284865551557179e224, ">:O\u{3a925}ÿ\u{5}�{": -20, "A\u{7f}$'\u{b}¥\u{7}\"\0\\$=\u{b}\u{1b}�\u{feff}\u{b}\u{7e7a2}\u{b5184}\":\u{feff} D\0=\u{7f}耋Ⱥð`ࢁ": -41, "J\u{1008ed}\"\rLq\u{5}7": true, "[啡🕴mѨ®\"\u{100589}{\u{8}u\r'\u{b}\u{47a49}}\u{7}\u{1b}R\u{b4ad6}\u{b45f5}\\Ⱥ`'?": [234, 239, 156, 159], "].`'&\u{7f}^&.\u{202e}\0?%?¥\u{9929e}\u{7dd49}?a": 6.8070914168451965e-68, "_¦\u{ccda8}O\u{10bdee}fk$\u{feff}`픰>\r'�f➦\r|{\u{feff}laA=D\u{dc4db}\u{5f977}z": [142, 251, 14, 32, 228, 8, 15, 49, 217, 216, 50, 236, 66, 67, 25, 55, 193, 159, 240, 75, 84, 154, 198, 182, 176, 139, 245, 12, 189, 16, 217, 208, 9, 19, 164], "f\u{3f22c}\u{7f}\0.Y?'.*\rѨ)Q¥=�\u{6b46c}\u{42bbe}\u{ee66}": bafkr4idibino2zrwzvmr7jw2oqbramjdedkkhhexizf2kgnxongndjyvvm, "k\u{e0bf5}Ã\u{202e}<\u{202e}**\t\u{656ee}<&𘞄\0\u{b}*.b\u{1b}\u{b}\u{8}\u{c9220}&\u{36fa6}.?]G<ȺG": bafk2bzacec3uxd2irqv3kuoh6433iq7cdb2rkr4jes4ibxqlagobh7kcbz7b4, "pmOAB\u{b}\u{1094f0}\u{c3774}?\u{10918f}¥": true, "v\\\u{109f70}7`@\u{4}c'%!\u{7d32d}Ѩ\u{1f298}i\u{4d1e4}'�\"]�.\t𝦈": null, "v`\u{1b}\u{58c73}\0\u{101d7f}\0\u{d946b}\u{7f}\t\u{f2bf}\"\u{831aa}\u{60049}\u{51864}&?祕u\u{10403a}D]�\u{578ee}\u{ab4b8}:/ 𦁋R7Ѩ": -24, "{\"C썶\t\u{eec8b}<\u{ee555}\u{7574e}Ѩ\u{7f588}\rѨ*'\u{5eccf}%w{\r\r\u{b67d7}\0^kᗩ\u{1c3dd}G\u{8}`": 18, "{%🕴\0Ѩl%𤍭\u{feff}🕴\u{96}I\r=": "¥\u{93d84}y:w={", "¥~\u{566dc}9\u{ec076} \u{bd2c1};": false, "¥ȺD\u{1}\u{6b992}\u{434c4}\u{3}\t\u{1b}?\u{2}𥹩\u{9cb8c}\u{7f}𢲦N\u{e11b5}Q^{Ѩ\"w\u{505da}{": null, "Á-`^�\"\t\"`\"/<\u{1}=%�=\u{f8043}\t\u{3cb4c}\u{af74a}^\u{356ed}.:d?\u{65101}\t;\u{d0319}\u{b6b3a}$q\u{b}": [163, 128, 176, 15, 181, 109, 115, 80, 122, 244, 94, 32, 58, 251, 121, 38, 145, 131, 101, 44, 148, 129, 190, 123, 55, 19, 64, 213, 134, 70, 238, 108, 62, 157, 43, 176, 76, 240, 174, 14, 182, 98, 16, 190, 79, 42, 142, 212, 204, 192, 112, 130, 89, 121, 0], "�*??*e": [172, 83, 171, 22, 2], "�\u{9b09a}\u{15e59}\u{7}/\0E\u{6}\u{3cb2b}\u{4645e}\u{7f}\u{2}": true, "🕴": 41, "🕴\t\u{f0635}`Ⱥ\u{c865b}h\u{f39b}\u{fd001}\u{f197b}¥F/?\0\u{52376}%Ѩ/\t\u{b}\u{9a1af}\u{9b1fd}\u{feff}�\u{8f}/bt`k": [235, 24, 242, 136, 3, 90, 188, 21, 23, 10, 85, 3, 90, 163, 176, 209, 186, 96, 160, 240, 221, 232, 34, 228, 54, 35, 11, 200, 6, 93, 92, 140, 92, 36, 62, 178, 157, 50, 12, 66, 22, 208, 28, 217, 177, 9, 79, 248, 77, 198, 73, 78, 120, 28, 102, 200, 197, 67, 232, 133, 41, 38], "𥇖`": -3.1043921612514823e111, "\u{fdac9}\u{2}.𘠺🕴:'\r\t\u{3}嬊": bafk2bzacedwvgub3e2lthnyghripac4wx5ld6z3tavnb3iws5vdjukng5fwsk}, "$\u{890b7}&🕴=Ѩ\u{e0cc8}\u{7f}\u{8}\u{b}\\\u{ed02b}🕴°d\u{7}\u{202e}L\u{6d2ae}<\ta{*": {"\0&/\0&\u{8999c}.`%\u{feff}": baguqehraxkytankub52h3iajclairokepzb3skwifyzrg5qnfmbz4g4oodza, "\0🕴\r\u{2}`%O$%\u{de829}": bafkrmiht3e7seikbsbinibsxaqh4je5pzdcd4urxrwswdctilymrairlqy, "$\u{1e406}\u{1ea29}\u{7f}\u{c9cd8}𮖲X": [168, 224, 17, 123, 200, 96, 201, 98, 223, 37, 175, 231, 138, 28, 166, 190, 55, 123, 102, 20, 44, 208, 18, 184, 161, 233, 19, 136, 101, 164, 157, 248, 80, 214, 88, 79, 112, 146, 242], "=`C": -23, "\\8\u{d9453}($�[\u{539d4}\u{90f0d}\\u�\"$\u{bf277}\"\u{749e6}": 9, "wѨ&\u{f18dc}\u{8f209}\u{8}/\t\\": bafkr4ibmkkkbmtivel5pdx3jrmhp4wb5bd7rqzvoovp2j4b5hn2rtovype, "Ⱥ\u{b}\u{10433a}\\\u{1b}{`!\u{cd71b}¥<`ȺDȺ\u{d310e}\u{fee4b}𭨠0\u{b6c98}": true, "Ѩu鉏<}\u{8}Q\u{5ef3a}:Cm&\\\u{3}Q':@\u{f9a57}'\u{3f836}c<🕴P": false, "\u{202e}\u{6899a}<\u{b}/Ѩ&\u{2edba}\u{103edb}\u{9e328}`\u{4979f};\u{a6f16}!`\u{feff}w`": -32, "�Ѩ\u{8}�\"\u{fc9b3}\t": null}, "L_Ⱥ=,\u{4}\u{71158}'�\u{d01e8}/$\u{202e}\u{3b573}:`{w'": [-48, [93, 196, 141, 154, 134, 176, 139, 189, 180, 134, 124, 223, 175, 198, 167, 42, 110, 24, 222, 67, 196, 65, 202, 107, 120, 13, 17, 105, 196, 172, 101, 89, 218, 26, 140, 88, 96, 162, 89, 106, 160, 211, 109, 26, 157, 200, 132, 194, 38, 44, 145, 80, 249, 47, 202, 234, 243, 149, 231, 133, 231, 1, 121, 28, 157, 175, 187, 89, 100, 158, 213, 243, 162, 111, 87, 174], bafk2bzacecsuznauruwgtuwqc6nidbjw7qp5usnb5vr3w7lxuvebwffbqe3t4, -1.8324536891683285e-30, bafk2bzaceadjwnpm762btxzz5nfl24lfog3wm5ldrl7q7namae7ysv5647h76, -6.370155741536763e-309, null, null, 23, false, true, -41, 48, "`\t\u{cd15e}<{\u{ad7da}%W*腁=\t¯\u{7}-?,\\", 5, [238, 157, 140, 174, 18, 122, 121, 22, 84, 53, 144, 119, 53, 216, 130, 89, 6, 121, 229, 24, 153, 6, 11, 186, 92, 6, 52, 44, 74, 132, 139, 202, 28, 213, 100, 205, 127, 222, 130, 104, 210, 158, 235, 223, 136, 77, 58, 114, 135, 100, 151, 103, 91, 82, 150, 91, 149, 124, 36, 26, 78, 105, 207, 54, 253, 9, 201], [195, 193], -2.0736431262565832e-7, null, -7.471855593429185e234, [212, 22, 11, 89, 86, 227, 1, 226, 174, 110, 146, 235, 217, 246, 70, 133, 107, 165, 157, 151, 202, 116, 160, 243, 169, 65, 185, 193, 131, 184, 185, 157, 65, 108, 39]], "y#\u{49798}\u{202e}G<%r\u{1b}\u{c2763}\u{1b}\u{d7645}$M🕴\u{b}:": baguqegza3fmjvuxyw67cjennqkgi6ipcsxboanp4xjz4prjmp7h4r5muxaja, "¥\u{6}xS%\u{a21c0}Ѩ\0\u{b}\u{e52b5}\u{8cd0b}:\u{feff}\tS¨\u{dc97a}%$\u{8a51a}\u{8945b}\u{3c40a}🕴\u{feff}\u{feff}": "\u{b}\u{fbd6c}*", "🕴$�\u{1c50d}\t\u{2}": {"": 14, "\0�&\u{feff}\u{685bb}YVE9c\"|\"<[�K𤅰\u{cd669}}\"": bafy6bzaceak3lys2lo5m7woe2mfjyxxl3ore3aixosi5uajethhkjyze72xpm, "\u{1}\u{d0b74}\u{b6c77}y?[\u{202e}¥~{T\u{1b}\u{feff}\u{f8717}\u{a0f04}:\u{10f8b6}Ⱥ🕴\"Q\u{df178}\"\u{fe2ee}u": -1.5028963084100644e18, "\u{4}`\u{100473}\u{f434d}=�*/🦲Y\u{a0}W\u{a0e46}\u{5}": false, "\u{5}4\u{33c5d}\u{4}": 6, "\u{5}ZѨh\r&": true, "\t(/=:<\u{7}🕴.'Z": bafyb4ig3qyn3653hzem53fsfvx27lsgdkfmln4vgzjnu7bhb22rr6h5nvq, "\u{b}\u{5}\"\r𐴅\u{a3132}z$8\u{74e26}\0F\0\"*�J\u{7d9f5}\u{ad7c2}\u{f3faf}?\u{f35ed}": [158, 45, 189, 198, 136, 12, 91, 193, 206, 13, 142, 218, 17, 126, 95, 11, 139, 29, 191, 233, 236, 221, 187, 109, 193, 176, 213, 129, 96, 120, 253, 24, 3, 65, 228, 116, 138, 102, 127, 19, 54, 235, 37, 201, 238, 25, 41, 26, 248, 239, 191], "\u{b}Á\u{4}\0$\u{baa23}N\u{105c4d}\u{feff}g": 6.651792988712289e-76, "\r&\u{7f}𬛩\r*m\\𭘺\t": [7, 86, 209, 90, 4, 82, 142, 122, 123, 191, 41, 84, 37, 155, 147, 150], "\u{1b}=\u{ae8cb}🕴🕴YѨ": [15, 58, 171, 250, 163, 49, 118, 146, 209, 32, 161, 202, 1, 216, 170, 61, 6, 250, 37, 28, 65, 69, 84, 230, 70, 124, 99, 121, 212, 211, 35, 15, 202, 210, 87, 237, 13, 14, 12, 203], " n𦜹": false, "\"I¥": null, "'\u{202e}\u{1bfda}¥\r\u{e18c}{\u{4a199}Ⱥ\u{98ba2}{\u{202e}🕴f\\": false, "'🕴": [0, 125, 38, 17, 120, 195, 201, 150, 64, 36, 204, 245, 82, 31, 21, 85, 169, 69, 249, 10, 19, 33], ")�\r\"\r¥\u{202e}\u{cbc20}\u{5a381}\u{202e}𱙤{'": false, "*\rȺ\u{9af1d}g�.*🕴\u{1b}\u{feff}\u{6ecf1}`\rY{\u{88}\\*.\u{f8037}($\\¥.\r&ÑD`": null, "*'\u{fe9ce}\t𪡏h'Y\\{\0IWë\rU?$\u{7}IQi!%": bafkrwif2qr2egu3en3ttpfc277nx7wbogbs3exytgbmipe2ev4itu4abxi, "*{U\u{7f}3\u{feff}🕴%*``/ꁏw\u{88393}\u{1b}>": 16, "-&\u{588d4}J": -136947584516.8358, "/": bafybmigzjzmiin3bgn6qms6vavgzn6iyqjuo4onptcetzu657kebbwt5hu, "2\r\u{8c375}i\u{99}&🕴0J\t$Á\u{66ed4}/": "¾\t\t🕴$A\u{136fd}¥=l\u{8937a}\"\u{db049}`Ⱥ>@\u{b}\t\u{feff}%.\t\u{8ec09}\u{c5f5f}/$", "<\u{1b}/\\?z9\u{dbde9}\t?𦤐\u{1b}&H\u{1b}\u{125ec}\u{cf4c4}\t\u{b}\0.\t§\u{4a3f5}\\�": bafkr4id2n26zagqdmec7pq53l34j6st3x3xzqualxr3kl57litjvhiqjku, "=]\u{1b}\u{f411d}\t\"`\u{a4a2c}\u{6}": [238, 35, 78, 136, 226, 234, 214, 233, 77, 30, 183, 34, 221, 130, 60, 71, 179, 70, 33, 252, 49, 64, 114, 217, 11, 226, 152, 252, 143, 174, 237, 102, 78, 100, 138, 217, 6, 74, 250, 0, 234, 244, 152, 245, 69, 148, 151, 8, 51, 18, 105, 167, 200, 213, 240, 64, 138, 104], "=\u{8bf06}?x\u{1}𡊽@\u{6}.<%": "L\u{79877}\u{eccd7}*", ">=\u{feff}\u{8}à\u{1b}vL0\t\u{7f}I\u{feff}\u{7aba3}b$$Sv~i\t錦🕴&\u{709ee}.": null, ">`:𦎼\u{7f}º\u{be352}n$\u{eafbd}\u{cddd8}jw\u{1b}\u{feff}YT": true, "I韸\u{53960}\u{feff}?\u{7f}.\u{cf92c}Q\u{4}.\u{b}\u{8}🕴Ѩ%": -26, "M\u{2}𬗃\u{9082e}\u{e23ca}\u{4}\t\u{feff}\u{5bde3}*\u{e514b}x,\u{48624}\u{1b}\u{b}+\u{f8546}\u{8a2a6}\u{d4710}'🕴f;\u{46c31}\u{1004de}y{b:": true, "M&\u{e303}?&�\u{7f}j;\"\u{202e}픢\u{b}🕴": null, "W*/X\u{7f}^\u{7dd38}Þ7%.*3\t\u{ce607}:\u{d3d17}c\t\r\"\u{84fa3}\u{202e}=\u{92}": null, "Y*\u{c2e6a}?<~\u{feff}P'¥e¥\u{43a8e}\u{fa00a}`'": baguqehra4nft63u5d2aawrcgvhart2zja5ogq6c624azqqxgbgtoxekvlnpa, "Y\u{202e}'?": "\u{3}\u{cc1ea}\u{c7649}\u{a973e}\u{8}0\r^u\u{8eff2}l\r<]\u{5e264}&\u{f3e11}\t\r\06S", "\\\u{5}.\u{7}\u{b69af}*\u{19abc}]¥\u{202e}{🕴'\u{e46c7}`.xs\u{b23ec}\u{10beed}": -21, "\\*\r¢峭ìE\u{d96e1}\u{f8ff9}^\u{bc6ac}%:\u{50965}$6\u{8119f}¥\u{f67d0}y~\u{6c741}\u{3beb4}🕴": [126, 126, 7, 32, 17, 6, 234, 156, 163, 166, 193, 46, 81, 206, 32, 125, 166, 57, 195, 140, 39, 47, 171, 70, 117, 175], "\\\\/&Ѩ\\<": [92, 59, 122, 162, 91, 48, 33, 29, 29, 249, 254, 64, 21, 67, 100, 54, 206, 184, 116, 163, 175, 47, 42, 243, 205, 18, 136, 16, 249, 116, 141, 232, 122, 115, 123, 202, 72, 56, 116, 229, 177, 60, 49, 83, 199, 52, 23, 85, 114, 147, 155, 50, 31], "`¥?쉲G": "\t\u{4095b}\u{6c6a4}𮭀\u{2}\\\u{da8f7}8\u{90a53}.痗\\\u{202e}\u{56f97}{E]\u{492cd}&f\u{7f}¥D\\]Ⱥ?🕴", "`嬥\u{db443}.\u{ce5}\u{12eae}\u{108af8}\u{1b}\u{7f}\r\u{60df2}¥%&\u{5b0fe}-.&\u{3}\u{e2c1d}DR&:�\u{10c17b}Èc\u{8}U%": [49, 169, 171, 64, 201, 141, 228, 46, 7, 206, 13, 162, 170, 144, 168, 169, 198, 15, 58, 71, 112, 228, 149, 218, 144, 84, 70, 253, 39, 168, 223, 3, 232, 33, 162, 234, 136, 215], "`�`\u{7f}{\u{5c37e}\u{2}\u{202e}?": "/\"\u{3}𪜥N\u{57f77}h$\u{d1d92}𐊘�\u{7f}*¥<.\u{9e397}<\u{1}g\u{d0ad6}/\u{11ede}\u{7f}\u{ae007}\0$\u{3afa0}🕴", "c��W|`�*@.aѨN\u{feff}\u{f92a0}t$\u{f53cc}1\u{c8fbe}\r\u{19667}\t\r\r-\u{7c67c}": 2, "o\u{1}\u{1}\u{1}=\u{e8927}I\u{f584}&\u{a0}:9\0a𠿭/": true, "q$Ѩ\08{\"鉱[?ÀzY\u{9b8c9}%\u{3}{%\u{5}\0.\\\u{6c540}\u{7f}s\u{c05e8}`\u{47f61}=)🕴": -12, "s*¥\u{9a}\u{feff}": -1.6309188456998563e185, "v=<¥'<\\`\u{cc9eb}\u{798aa}\u{64542}:®3\u{202e}\tD&𰓏%�\u{7f}LF𒉠\t�\u{ee420}/'D": true, "v陈'\u{d832c}R??\u{7f}/\u{6}?\u{7d4d1}\u{dca1e}~🕴\u{aca77}:ç\t\rѨt?\u{10f5f3}\u{9db4d}": [35, 3, 217, 123, 77, 104, 154, 141, 118, 202, 130, 101, 62, 0, 182, 199, 130, 209, 149, 102, 212, 81, 90, 149, 217, 96, 127, 42, 251, 151, 46, 49, 33, 84, 28, 55, 109, 95, 208, 174, 193, 163, 28, 103, 91, 127, 159, 204, 177, 182, 31, 112, 61, 243, 10, 202, 83, 234, 176, 174, 61, 22], "v\u{43b04}6\u{f16c9}\u{2}\u{ad277}{Ã={/\u{202e}": [130, 234, 7, 201, 230, 244, 129, 23, 205, 123, 90, 239, 235, 197, 10, 88, 224, 76], "y/\u{1b}Ѩ\u{1b}": "?;*\u{156a5}🕴\u{1ad1f}:-\u{a1bea}r&\u{eb06}\u{a5275},:R\u{7f}&<\"®\"w\u{202e}:Ó.:\u{202e}\t.", "{B\u{7f}𲌜\u{9a6bf}.\0\u{4a3ca}🕴\u{7}\u{35765}/": -29, "{`=\t;\u{b}*EX\u{1b}鋪\u{61ce8}\u{b742a}/s🕴\u{5}\u{db6e7}'\u{10aa16}q?\u{8}\u{8d}\u{518fe}": "?\u{8d694}🕴", "{b\u{fe666}䀾\u{feff}]y\u{1b}[$\u{202e}\u{202e}🕴㞔\u{c132a}": true, "\u{7f}\"P=\u{555f9}\r\u{f034e}j\u{67c81}\t)Ø\t\u{a2116}\u{bcb49}<\u{108172}\u{489d6}3": [11, 195, 207, 131, 8, 71, 197, 12, 152, 172, 119, 96, 131, 191, 170, 2, 143, 112, 247, 189, 191, 156, 180, 76, 57, 86, 59, 6, 237, 106, 34, 175, 98, 78, 217, 192, 190, 63, 247, 202, 245, 190, 185, 58, 157, 147, 177, 129, 101, 197, 232, 22, 220, 131, 53, 79, 127, 137, 106, 0, 139, 8, 238, 179, 248, 40, 242, 178, 191, 193, 47, 56, 113], "\u{7f}_%\u{e1aa4}\u{c1f6f}/$s:\u{1010bd}*j\u{b}nêeȺ$\u{7f}\u{202e}\u{feff}\u{1b}\u{feff}%\u{9f08d}w\u{e8557}<$<\u{3e78b}": "Ⱥ\u{feff}$", "\u{7f}\u{9a}\0�\u{9c}'$=\u{4}&\u{94281}\t?\"Ѩ\u{654e1}": false, "\u{99}.\u{9d66b}z\u{db26c}\0\u{109c92}.W\u{ca505}\\¥\u{e0cf8}\0`4x\u{feff}×ꡔL\u{b16bc}\u{c1e8f}TYu\u{e7dae}\u{49a55}: ": 43, "\u{9e}%MWȺ`$\u{202e}:z?🕴\u{6}\u{a0}㶬{\u{5}<\u{6}*": bafk6bzaceaont65a5awr6tb55msdtvepr4xwa62mgsp4gyyfmbd62ey4ts4z6, "¥`?=%🕴`L": true, "¥\u{7aff2}Òb[?\u{8}\u{923a6}»\\»": false, "¦\u{6a307}": null, "Ѩ🕴T¥$*?\u{6}𪵅\u{98110}": -6.640335043931752e20, "\u{1bd53}Ѩd%@": null, "🕴3\u{feff}&\u{dd024}Ѩ=\u{c62d5}p\u{7f}�'AѨ|`g\u{635c5}h": 22, "𣅝\"\u{8b95b}\u{fbfeb}\u{cd3f3}A'\u{167f7}𘑦&🕴\u{94f3b}H⼣\u{95077}": "", "𮎾ýK\u{feff}qȺ\"\u{64e75}\u{70dd2}¥\u{41885}l": bafy6bzacedp25hwogiwuqmaa52iwzgx42gznm36zcpjwo2togshwzpsae6p26, "𲊳": [226, 239, 217, 38, 87, 223, 233, 194, 149, 219, 168, 60, 210, 19, 135, 178, 111, 92, 80, 108, 128, 121, 241, 63, 190, 148, 170, 24], "\u{3393b}\0¥\u{3b6e9}F\u{93d46}X=\u{d5dd8}:\u{feff}%'\t\u{95303}'*\u{aca4a}", "\0\\\"s\u{feff}M:b\u{8}H/\u{6cafc}\u{f1224}z\u{8}8P🕴\u{a2f8a}\u{8}\\": true, "\u{1}=\u{3134e}$\0v{K\"({Ѩ/�𪓆 \u{b3be1}�\"{$¥//🕴\0\u{604de}\u{a3afa}$\u{3}q": [43, 187, 50, 198], "\u{2}'Ⱥ\t\t\u{10b73d}\"\u{8}Ѩ:": [177, 214, 28, 238, 196, 162, 205, 149, 100, 179, 184, 109, 52, 32, 41, 6, 221, 134, 161, 95, 164, 2, 124, 18, 111, 203, 147, 153, 196, 122, 109, 242, 133, 28, 125, 73, 247, 14, 254, 57, 169, 190, 203], "\u{4}x1\u{b}\u{d46f0}k¥�'%\u{1007c4}": -41, "\u{6}/": 34, "\u{1b}2": -25, "\u{1b}\u{202e}\u{c9725}\u{80c81}?/\u{5}\u{e1f43}m\u{f878e}\u{feff}?M": bafyobzacecdottam3btcdm3lil44fdpksyl4ry22wyyufv5d3h6yuc2xic62a, "\u{1b}\u{4d55c}?|L¥ù\0\u{b}¥?[\u{f871d}k{.\u{9b}ć🕴$a\u{2}\u{db167}`\"": baguqeerag4wvi75skxsukegz4at6o2jjuwnsmujpjfkds3vdu4ybvdnqszfa, "\"[ .[": baguqefrayopq7znw46ljb72uinasfv3w2iuuvycues24ag6px6cwn7uhxgzq, "\"\u{eb96e}\u{3}7𩼱/\u{7f}?<\r�9\u{1b}'\u{b}<\u{56215}\u{612fc}*:'�EȺ/Ý\u{41397}-": -0.0, "$\u{3551b}$🕴\u{4051d}渴": -14, "%=�\t\u{cadf7}\"\0\"=z\u{f357}\u{a23f8}\":\u{f77ac}🕴\tJ\t$": [15, 242, 91, 76, 243, 187, 224, 9, 73, 10, 254, 67, 51, 52, 17, 243, 78, 108, 39, 226, 242, 59, 201, 164, 49, 64, 177, 101, 28, 139, 30, 14, 186, 53, 73, 148, 250, 157, 77, 84, 1, 36, 211, 17, 227, 29, 58, 144, 4, 192, 22, 128], "%\u{671d5}\u{5} =\r?'b.\u{1b}\u{7cbe2}𱛣\\\u{1}¹Sb\r/%\u{108071}ȺE$x\u{1b}": true, "&'\u{1398c}🕴\u{91} ": [35, 228, 163, 135, 36, 120, 223, 108, 37, 185, 72, 109, 236, 64, 172, 51, 97, 158, 76, 34, 242, 182, 40, 28, 179, 167, 49, 193, 42, 24, 222, 201, 26, 63, 190, 143, 222, 12, 8, 41, 47, 5, 168, 168, 222, 18, 54, 55, 98, 164, 82, 143, 124, 88, 254, 159, 177, 101, 205, 232, 17, 198, 34, 34, 105, 60, 25, 16, 173, 94, 188, 33, 68, 226, 245, 233, 154, 122, 243, 23, 198, 255, 31, 226, 251], "*]\u{8bb40}&.-Io\u{2ee60}%\u{b}\u{b4204}\u{b}\u{6fa9e}\u{1b}\t[\\\\uS=¥\u{7f}": -0.0, "*\u{1f84e}`𡣶\u{b}\"\u{df68f}A\u{33626}\u{202e}|\0\"\u{862b5}\u{7df5d}\u{1b}<\u{87a2b}\u{8dd82}~\tP\u{202e}Ѩ": null, "*\u{38df8}p\u{d2c21}\"\\\u{202e}\u{82032}㿶1&ѨU`H8�\"&": [38, 69, 84, 226, 186, 180, 254, 85, 31, 178, 18, 43, 46, 2, 151, 54, 154, 241, 244, 226, 93, 189, 139, 83, 133, 109], ".%\u{fd55f}?,/.`\r\u{dadcb}%\0\u{f3420}\"%#\\Ⱥ\u{95792}R¥}\\\r𡂂\u{69eab}\u{feff}\u{7f}{": [117, 59, 134, 197, 33, 33, 196, 45, 250, 203, 13, 97, 151, 48, 4, 203, 100, 245, 18, 88, 41, 62, 165, 72, 69, 218, 126, 161, 161, 184, 57], "/\u{5}\"I28qȺ|«㖃$c`\u{2}*?\u{ee2e9}\"\u{51724}": false, "/.\r¥": -1.2787723284947307e-308, "/J\u{10519c}%%/$.%\u{65527}\rW|Ⱥ{\\¥\u{8201f}\u{7f}\u{b}&%c=Ѩ\u{9bf3f}": 2.35302505714783e265, "/\u{52490}=|:%w\rl\u{90}": null, "5Q%\u{561e8}^r.\u{b8932}\u{92f2d}4\u{7f}+⩾\u{7f}/.¥\\\u{1}\rS1\0%": bafkreigrelqjps75gjoqf4ijk5muyqd3eo7nkw5cm5phlkiaaoy4yuiy6q, ":`\u{7112e}": baguqehraddx54dt5qiorol2h2cg5mnr7pjvahh3d43il2ernaikugjuvyymq, "?\u{75c14}\t龒Ⱥ\u{e5509}\u{534e4}<>\u{1b}\u{d3cef}\u{7f}\u{417fd}\u{7f}\u{6c856}%'\u{202e}>\u{43519}U[:\0\u{b}&\u{df77e}/&": -4.024585944763422e193, "K*\t\t\u{e140}Ø\r o\u{c3cad}2/{Ⱥ\u{7f}s\u{7f}¥\u{b71fe}&¥\u{e34cb}Ⱥ\u{135f0}\u{b}$\t2$$": -10, "OB\u{4}\u{7f}\rP5\u{80012}\u{16348}\\z\rÍ\u{1b}\u{3b61b}?": bafkreiaimlx7a6sb2ezufrqz5kqv3p2y4fadnzakimbrhq3ajzjg7ue75m, "P\u{93db9} &zG\u{7}?🕴\u{7f}'\u{10ec52}\u{feff}\t:\0\u{63df4}m\t\\": false, "U\u{2}\u{9ff8c}¥?\0\u{57a21}\u{9bb0e}ef\u{2}Ⱥp\u{f73a7}4\u{b}$\u{7f}\u{1b}9\u{7f}{\u{9e15c}\u{d804b}\u{705a1}\u{e750c}Ⱥ\u{7f}¾\u{7c515}": [182, 236, 56, 93, 36, 111, 103, 217, 53, 16, 250, 85, 194, 3, 183, 131, 156, 114, 43, 35, 38, 181, 143, 123, 141, 23, 109, 247, 10, 205, 121, 24, 25, 35, 178, 125, 3, 138, 141, 76, 31, 49, 144, 42, 23, 111, 9, 180, 199, 19, 28, 28, 36, 135, 72, 100, 199, 115], "Xa\u{b1ab2}$(\u{9e}\u{3812e}\u{fe6c}\u{6acee}\t8$=\u{7aa19}He\u{202e}|2*\u{6813f}Ⱥ:$%\u{70ca6}`\u{e4b58}o\u{a0}?": null, "Z\u{87bae}\0\u{f48d0}Æng": 49, "\\": [161, 24, 206, 12, 94, 242, 137, 40, 92, 187, 103, 166, 57, 108, 16, 46, 30, 140, 111, 96, 213, 196, 3], "\\\u{e7806}\u{7}%\r\u{7f}:<\u{95ec9}\u{10b262}\ry\u{d494a}g\u{5c182}'&.\u{2}\u{750dc}]Kq\t": -1.3364526424136176e63, "^\u{50332}\u{3}\u{7f}'$$Ѩ\u{7f}\"D?": -33, "`\"*\u{6989b}\u{a0552}Ⱥ&d\u{4}Ⱥ🕴Ѩ\u{f863f}Q\u{202e}\\=\u{202e}*}\u{6e906}\u{994d4}\t\u{10cc54}\u{3}_坢Ⱥ\u{ef59f}F\u{e82f1}": "'*𰀏Ѩ\\%\u{4}.*\u{202e}\u{96457}%|\u{100bbc}�\u{5046b}H\u{d9ba9}HL\u{5}*\r\u{8a1e4}\u{b}\":<\u{7f}\u{9f}Ѩ\u{7f}s{�\u{a46b4}\toh\u{7f}*", false, 46, 14, "�\u{102e58}'\u{f5c0e}\t\"E\\cM<'l", false, [135, 235, 189, 222, 190, 18, 166, 127, 220, 237, 86, 153, 31, 245, 85, 90, 198, 38, 141, 133, 190, 152, 45, 202, 134, 204, 48, 11, 139, 218, 166, 185, 151, 185, 14, 83, 198, 240, 223, 59, 82, 165, 180, 107, 145, 62, 145, 69, 120, 158, 111, 248, 129, 252, 131, 205, 198, 215, 133, 171, 133, 47, 12, 202, 128, 214, 166, 137, 1, 192, 251, 117, 7, 234, 236, 17, 204, 31, 74, 10, 168, 79, 30, 0, 231, 29, 135, 146, 72], "RvѨs$\u{75d03}Ѩ(<\t\t𤡧s`\0\u{1b}.\\\\$\u{1616b}\u{7f}\u{3cf54}", false, [235, 223, 146, 172, 43, 51, 24, 21, 97, 241, 49, 100, 109, 54, 82, 186, 134, 207, 194, 77, 228, 210, 37, 8, 203, 94, 183, 5, 197, 144, 138, 15, 165, 27, 102, 4, 88, 13, 236, 223, 176, 178, 233, 90, 97, 161, 185, 38, 223, 154, 255, 212, 108, 179, 214, 107, 224, 221, 198, 190, 85, 135, 36, 226, 102, 69, 206, 155, 37, 70, 205, 167, 176, 184, 47, 239, 155], -1.1621607219028973e-308, "\u{59b57}/x\"\u{7f}\u{7fb3b}\u{3}&2&\u{b}\u{2}*🕴l\u{9d87e}%\u{1b}\u{b}RA", -24, -18, null, [95, 22, 213, 216, 209, 22, 236, 29, 48, 196, 106, 1, 159, 4, 224, 154, 54, 81], true, bafyb4ieb5rs6wukn4nc3vkm5ej4qrmgt7ycs7lmkc65m3wcxmpgo2xms4u, bafkr4ieb5eefx4xbgebjbqtjvimlfqjwrqcrg7vlzoiqrnntc2avr3g5g4, bafyb4ibwxgkbwxtmkhdb5bwhyi7caxvo3ke6mk42emzae5iini52xuhlpy, -37, false, false, -28, "\u{8}1\u{79fd2}\0\u{ca356}\u{59494}O𫔓{\u{7f}Q\u{3269c}]\u{202e}\u{2}\u{97bce}\t\u{578dd}cP\0\r", null], [158, 31, 209, 14, 252, 175, 225, 4, 36, 138, 244, 197, 42, 56, 92], null, false, [52, 230, 174, 76, 132, 211, 203, 241, 115, 11, 214, 245, 111, 133, 43, 90, 154, 170, 151, 64, 144, 238, 229, 61, 189, 206, 192, 76, 209, 181, 128, 129, 149, 185, 71, 119, 213, 172, 36, 212, 47, 194, 112, 62, 251, 65, 101, 24, 90, 161, 131, 1, 164, 160, 99, 211, 197, 197, 201, 100, 159, 72, 192, 14, 231, 60, 84, 118, 134, 195, 67, 16, 246, 204, 36, 246, 158, 191, 131, 237, 231, 155, 25, 200, 124, 214], -1.6606481206991028e-178, bafybwif7fjc3n3ymfa23uyjq64sksrh45thlzmm76bb5bohapwgol2thvq, null, -1.346334315879978e109, "衴", [119, 113, 165, 145, 116, 9, 189, 140, 253, 252, 3, 148, 133, 255, 48, 161, 243], "\u{8}u/G\u{a6e8d}?\u{60a32}=ó\r\\?🕴\u{7}%a/\u{1b}¥'\u{7f}:\u{60bc8}\0i`\u{1037a2}A\u{c10fd}\u{b}>\u{202e}", "", {"\0\0¥": true, "\u{3}=Ѩ\u{5ce31}:/\u{97}\r\u{7f}/\\-\u{e2599}?\0\u{202e}`w0\u{7f}\u{b}\u{10ed54}5\u{84}%\"": "$\tE&\u{43d48}S\u{fb913}ÿzVt\u{e7bed}L&{²\u{6}\u{7f}", "\t\u{5}\u{a8a7f}\u{89589}sJ<'𫮹碗\"Ѩ1i/𦏲x🕴\u{a3b5e}\u{ee69}?": null, "\u{b}'\u{83bfd}\t/*㷰\t\u{97282}\u{5}I{": [223, 168, 69, 95, 72, 43, 203, 89, 110, 174, 115, 77, 84, 89, 26, 159, 57, 147, 74, 158, 40, 74, 218, 178, 183, 135, 65, 67, 200, 1, 1, 140, 39, 233, 54, 33, 28, 99, 87, 116, 176, 91, 93, 98, 3, 13, 238, 112, 18, 248, 83, 152, 221, 158, 134, 229, 80, 111, 168, 248, 209, 124, 19, 203, 55, 80, 89, 127, 31, 233, 114, 8, 236, 122, 44, 128], "\u{b}G;%&YP🕴t\u{4}E�.{\r\\/�K|\"\r": bafyrmifvkoneefkqfiseviqruma2fucp5bha2di7eg7jlyk3zrf46fw4na, "\u{b}d'S\u{4dc81}%\t)🕴\u{202e}.🂉\u{49dd2}𗹆\u{461bb}\\*c": true, "\r:*$㝩\u{98464}\u{6}𰂜.\u{1b}\u{b}=0\u{8d206}¥\u{95e7d}.\0\r:¥$\u{fd075}w{\u{202e}\u{7f}À{/\u{aee3d}": -54, "\u{1b}]Ѩr\u{46a43}Ⱥ\u{76dd1}\"@%6%�*L\u{10fe25}\u{cb0c9}::\u{82}{": baguqfiheaiqmnsckfvqrzdszfgxhnfdlbgv4tv2umb4ebrla4e4ovkqzr7qtl2q, "\u{1b}\u{202e}=\t%>\u{64bd7}=\t¥.?&\u{202e}\"=\u{feff}\0\u{7ed05}\u{7f}Ⱥ\u{3b248}": true, "\"ȺA\rZ": [182, 24, 119, 121, 203, 152, 112, 93, 47, 116, 134, 100], "$": bafyr4idvqlsptx6ct75oj6n343bqpaabhltktomqf74lrhnjwj5zwvrz5m, "$\u{2}\u{9f}¥{\0$\u{202e}c\u{1b}\u{736d1}\u{7e350}#K🕴´\u{44a44}.🕴Ⱥ\u{401cb}%\r//\u{44014}r\u{1b}é¥": bafyr4icg62pibjpimmwgopvait4hdkb3lkz4ry3duekmfx4expvn4rrwqm, "$)ѨÊ\u{69a2e}:»Ⱥ\u{99}\u{f3438}\u{b}\u{c0d9c}*": 17, "$/??{\t\0á<\u{6d90c}.\\\u{5ab70}W*D.`\u{81dac}`": "🕴j\u{202e}`\u{97293}Z<²\u{aabdb}¬\u{202e}.&=Ùh\u{5680a}\"𪻏", "*\"\0'`<ѨjȺ$\u{8d55d}Ⱥ\u{d1704}$\u{69be0}]\u{feff}%..": "Ⱥl'Ⱥ\r�<;\u{edd5d}<\0\u{a2820}\0\u{16271}\u{ccc45}\\:/\u{202e}\u{4}(%\u{7}", "*/$\u{3b185}/�:\u{c8c4f}CA&4\tc,>\r+": 0.0, ".{^\u{feff}\u{b31b8}\u{f0ff0}=\u{1b}\u{cf39b}&Ⱥ\u{cc0f0}ꟊj\u{7f}2*¥n.FѨ": bafkreifagebl3ayuazlaokrf45fopvx2mxh52amx5e7yh4w3pxhkken3vq, ".\u{202e}": true, "6\u{3}\u{febc8}P\u{6a6d5}~\u{3279b}p삣6\u{b}\u{b}K": baguqehrak3yvnpqo6ab5rdeccamokfdova7l5vpi4xq7skax4mmzw75slkeq, ":\\\u{1009ca}{'\u{7f}\u{9b51b}/ek%🕴=Ⱥ\u{7f}{\u{9ecd8}/Ⱥ`\u{aaa83}\u{feff}\u{1b}{\u{5ebd0}�\u{c68be}T": 9.620946678080735e-61, ":\u{feff}A*&\u{839f8}\u{86fd8}\r4{;�<`.<$\0\u{b1b8a}>": "𠑛%Ⱥ�\u{1}<\u{1b}\05$OѨ? =Io\u{3}Ⱥ\u{4a654}\u{95}\u{2}\\", ":\u{f12fc}\u{8f80d}\u{c4396}𣝛=\u{7}\u{92a55}🕴(&\u{91993}\u{e357e}\u{8ac6d}\u{7f}Ⱥ襥\"':�\u{5}": baguqegza2ch32i7qyewr7mke33b5bpzm7jep5ei5kpard5moafylxh46gpma, "<\"\u{fe42e}\r\u{103930}O3=\u{feff}𐽱Ѩ\u{c7c2a}<.譒\u{ec02e}/\u{520c0}FA㣂\u{b}\rù": -43, "<\u{b8112}}B\u{1}\u{e22e4}$[¥=\"\u{b}`$=": bafyobzacecdbrhr5ehtimriiz6mxvinsq5tuyh7dqu3pderxxdmgtepquklbe, "?¸H/%\u{feff}\\K\u{cc4ea}y:🕴\"@8:\0:𢭤¥<\u{c9d0a}\u{7f}\u{8}\u{7f}\u{5}": bafkrwidvq2ib5wbdwi6evstk4n4zuo2ptkqx62jlqzp65xevm54zh6caiy, "@\u{66e54}Ѩ$u": null, "C𫏦.\u{b}/'8\u{49182}ȺȺ&\u{c91e4}\t/": -46, "OS\u{b}\u{7}𩚡4?Ⱥ\u{feff}'\r¥\u{202e}\u{b}B🕴f\u{202e}.": null, "Q\u{d12fe}&`\t*\0g\u{1b}\u{8}2D/$)\r{?𠯚\u{fed96}x\\û": 5, "U^\"?d\u{f8edf}R\u{a3374}<🕴\\\u{202e}>%\u{feff}\u{a2d62}\u{ab6e8}\\\tP\u{81d20}W\u{998d7}\0\0\u{d2250}\u{74ab2}.:W*%": [212, 136, 153, 95, 249, 147, 220, 254, 102, 118, 32, 185, 203, 25, 119, 125, 56, 0, 225, 234, 220, 127, 234, 201, 81, 152, 213, 225, 133, 23, 85, 200, 158, 231, 88, 27, 12], "\\Ѩ&\u{ce722}\u{74bbb}i\u{9caf6}f}<\u{feff}Uo\u{f89a7}\u{2}$\0𰔭\u{63b24}𘥆&þ": null, "`\t\u{7abdf}Ð)&g&": "{Ѩ\u{e4a29}*nȺ\u{e491e}M\u{89c42}%a\u{8}\u{82}", "`r\r\u{8}=🕴\t\u{440b0}Ⱥ\u{4}�%*{\u{202e}\u{151ad}\u{55675}K": "\u{4}\u{5d2d2}:Úv\u{f704f}\0\u{7}\u{ad34a}¥A<\u{aebdd}\u{898f2}<\u{d3097}\u{8}W/`Ⱥ\t\rѨ\u{10361f}\u{6d1ed}", "e&#{`\u{34996}?'/\u{1b}\u{2}\u{19c0c}\u{6716f}\t$%\u{db2ea}[{": 22, "n?": 52, "o\u{106e1b}?\u{1b}{*/$<Ѩ\u{7f}\u{7f}.'\"`6\r🕴�\u{202e}\u{77895}�𩈷)�\u{f670f}\u{202e}`:\u{4fb5c}>`x\u{35613}(e\u{202e}µ\u{ae9bd}\u{83}Ѩ¥\u{cf5b4}¥~\u{1}\\\u{10bc4b}hl(:#z": "\u{b}<{\u{5}\u{3c7ce}\t\u{b0200}`Ⱥ\u{58ab1}2*\"\u{ff4a9}Dw🕴&", "葜\u{1b}`�:\u{10e218}<2\u{1b}`\u{10b2e2}C\u{7f}\u{202e}\"¥s\u{1b}\0*\u{b}%\u{9d41a}=\u{e573}": [true, [32, 206, 206, 97, 143, 10, 81, 82, 157, 249, 61, 22, 250, 48, 255, 194, 232, 184, 202, 22, 232, 158, 227, 32, 56, 8, 142, 212, 153, 240, 168, 65, 248, 231, 48, 108, 237, 135, 114, 246, 246, 204, 239, 24, 162, 41, 103, 211, 37, 81, 230, 208, 111, 248, 4, 9, 197, 153, 105, 20, 217, 206, 146], true, -5.25899467531148e-128, "M<0%\u{202e}\u{b}/\u{46e36}\u{108dc}\u{a2ef8}\u{7f}.]\u{b96d6}\u{b41c1}\u{4e68e}", "\u{5d121}\u{b}\u{cf745}\u{3}\u{5badc}\u{202e}\u{d984a}", bafybwic7uqdxsofaghtkiqawch55haewwlxbtyy7dzua22bjcif4y2jmri, false, null, 29, bafkr4idbn64n3h5bfrab6gnwlyggbaxq2lyvtcpvyzp5z4h3myyl7tkzui, "ѨSm¥L\u{7}?\u{7c63b}`x%qN\u{a501c}1.Ѩ\u{5}:\u{6120c}·{*7", false, [112, 107, 102, 23, 200, 228, 14, 201, 117, 98, 1, 76, 10, 156, 40, 126, 28, 147, 14], null, "%Ѩ\u{b}=\u{e06d3}\"\0\u{b}\u{105e48}\u{dd959}㮴a{N*¥\u{4}\u{6c856}", [1, 150, 20, 111, 201, 216, 245, 91, 220, 0, 194, 252, 33, 40, 135, 13, 44, 29, 164, 248, 22, 137, 89, 237, 222, 28, 4, 157, 221, 9, 100, 197, 235, 73, 41, 84, 55, 116, 103, 40, 48, 206, 112, 238, 68, 248, 66, 52, 206, 58, 27, 54, 24, 51, 14, 70, 8, 203, 39, 212, 210, 72, 151, 65, 50, 3, 131, 97, 121, 181, 227, 173, 63, 242, 250, 82, 244, 186, 53, 96, 222, 46, 102, 216, 26], -5.797387639150806e-228, true, [228, 221, 48, 49, 157, 129, 203, 175, 12, 252, 218, 247, 178, 191, 214, 73, 7, 40, 52, 53, 19, 147, 108, 202, 231, 162, 31, 28, 67, 99, 121, 1, 129, 25, 41, 204, 153, 97, 233, 32, 246, 185, 128], -2.3032544649066786e268, true, false, 29, -23, [119, 152, 15, 167, 240, 199, 14, 99, 170, 252, 95, 162, 82, 87, 28, 38, 176, 157, 185, 58, 156, 67, 186, 0, 230, 43, 167, 184, 246, 129, 63, 121, 129, 247, 200, 218], false, -0.0, true, true, 41, -1.9024538231552825e-308, null, 0, "%\u{5}\u{86d1a}ð\\\u{5}\u{1b}\u{3}n\u{feff}F\u{10d421} for arguments::Named { impl From for Ipld { fn from(receive: Receive) -> Self { - receive.into() + arguments::Named::::from(receive).into() } } impl TryFrom for Receive { - type Error = SerdeError; + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(map) = ipld { + arguments::Named::(map).try_into().map_err(|_| ()) + } else { + Err(()) + } } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index ff97b16f..c3e47ab5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -67,7 +67,8 @@ impl From for arguments::Named { impl From for Ipld { fn from(send: Send) -> Self { - arguments::Named::from(send).into() + let args = arguments::Named::from(send); + Ipld::Map(args.0) } } diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index e53472c5..0e46d2dc 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -19,7 +19,7 @@ use wasm_bindgen::prelude::*; use proptest::prelude::*; /// Known [`Nonce`] types -#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] +#[derive(Clone, Debug, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { /// 96-bit, 12-byte nonce Nonce12([u8; 12]), @@ -31,6 +31,45 @@ pub enum Nonce { Custom(Vec), } +impl PartialEq for Nonce { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Nonce::Nonce12(a), Nonce::Nonce12(b)) => a == b, + (Nonce::Nonce16(a), Nonce::Nonce16(b)) => a == b, + (Nonce::Custom(a), Nonce::Custom(b)) => a == b, + (Nonce::Custom(a), Nonce::Nonce12(b)) => { + if a.len() == 12 { + a.as_slice() == b + } else { + false + } + } + (Nonce::Custom(a), Nonce::Nonce16(b)) => { + if a.len() == 16 { + a.as_slice() == b + } else { + false + } + } + (Nonce::Nonce12(a), Nonce::Custom(b)) => { + if b.len() == 12 { + a == b.as_slice() + } else { + false + } + } + (Nonce::Nonce16(a), Nonce::Custom(b)) => { + if b.len() == 16 { + a == b.as_slice() + } else { + false + } + } + _ => false, + } + } +} + impl From<[u8; 12]> for Nonce { fn from(s: [u8; 12]) -> Self { Nonce::Nonce12(s) @@ -55,19 +94,15 @@ impl From for Vec { impl From> for Nonce { fn from(nonce: Vec) -> Self { - match nonce.len() { - 12 => Nonce::Nonce12( - nonce - .try_into() - .expect("12 bytes because we checked in the match"), - ), - 16 => Nonce::Nonce16( - nonce - .try_into() - .expect("16 bytes because we checked in the match"), - ), - _ => Nonce::Custom(nonce), + if let Ok(twelve) = <[u8; 12]>::try_from(nonce.clone()) { + return twelve.into(); } + + if let Ok(sixteen) = <[u8; 16]>::try_from(nonce.clone()) { + return sixteen.into(); + } + + Nonce::Custom(nonce) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index e75bb60b..c54438ec 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -404,7 +404,9 @@ mod tests { fn test_ipld_round_trip(payload in Payload::::arbitrary()) { let observed: Ipld = payload.clone().into(); let parsed = Payload::::try_from(observed); - prop_assert!(matches!(parsed, Ok(payload))); + + prop_assert!(parsed.is_ok()); + prop_assert_eq!(parsed.unwrap(), payload); } #[test_log::test] @@ -412,13 +414,14 @@ mod tests { let observed: Ipld = payload.clone().into(); if let Ipld::Map(named) = observed { + prop_assert!(named.len() >= 6); prop_assert!(named.len() <= 10); for key in named.keys() { prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); } } else { - panic!("Expected Ipld::Map, got {:?}", observed); + prop_assert!(false, "ipld map"); } } @@ -426,8 +429,6 @@ mod tests { fn test_ipld_field_types(payload in Payload::::arbitrary()) { let named: Named = payload.clone().into(); - prop_assert!(named.len() <= 10); - let iss = named.get("iss".into()); let aud = named.get("aud".into()); let cmd = named.get("cmd".into()); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0d71a412..6e92ae64 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -294,6 +294,14 @@ where args.insert("aud".into(), aud.to_string().into()); } + if let Some(cause) = payload.cause { + args.insert("cause".into(), cause.into()); + } + + if !payload.metadata.is_empty() { + args.insert("meta".into(), payload.metadata.into()); + } + if let Some(iat) = payload.issued_at { args.insert("iat".into(), iat.into()); } @@ -519,6 +527,7 @@ where let mut audience = None; let mut command = None; let mut args = None; + let mut cause = None; let mut metadata = None; let mut nonce = None; let mut expiration = None; @@ -532,45 +541,49 @@ where Ipld::String(s) => { DID::from_str(s.as_str()).map_err(ParseError::DidParseError)? } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }) } "iss" => match v { Ipld::String(s) => { issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "aud" => match v { Ipld::String(s) => { audience = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "cmd" => match v { Ipld::String(s) => command = Some(s), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "args" => match v.try_into() { Ok(a) => args = Some(a), - _ => Err(ParseError::ArgsNotAMap)?, + _ => return Err(ParseError::ArgsNotAMap), }, "meta" => match v { Ipld::Map(m) => metadata = Some(m), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "nonce" => match v { Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), + }, + "cause" => match v { + Ipld::Link(c) => cause = Some(c), + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "exp" => match v { Ipld::Integer(i) => expiration = Some(i.try_into()?), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "iat" => match v { Ipld::Integer(i) => issued_at = Some(i.try_into()?), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "prf" => match &v { Ipld::List(xs) => { @@ -583,9 +596,9 @@ where .collect::, ParseError>>()?, ) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, - _ => Err(ParseError::UnknownField(k.to_string()))?, + _ => return Err(ParseError::UnknownField(k.to_string())), } } @@ -600,7 +613,7 @@ where audience, ability, proofs: proofs.ok_or(ParseError::MissingProofsField)?, - cause: None, + cause, metadata: metadata.unwrap_or_default(), nonce: nonce.ok_or(ParseError::MissingNonce)?, issued_at, @@ -658,15 +671,6 @@ where /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -// impl From> for Ipld -// where -// Named: From, -// { -// fn from(payload: Payload) -> Self { -// arguments::Named::from(payload).into() -// } -// } - #[cfg(feature = "test_utils")] impl Arbitrary for Payload where @@ -683,11 +687,11 @@ where DID::arbitrary_with(did_args.clone()), Option::::arbitrary_with((0.5.into(), did_args)), Nonce::arbitrary(), - prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..25), + prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..12), Option::::arbitrary().prop_map(|opt_nt| opt_nt.map(|nt| nt.cid)), Option::::arbitrary(), Option::::arbitrary(), - prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..12).prop_map(|m| { m.into_iter() .map(|(k, v)| (k, v.0)) .collect::>() @@ -728,6 +732,7 @@ where mod tests { use super::*; use crate::ability::msg::Msg; + use crate::ipld; use assert_matches::assert_matches; use pretty_assertions as pretty; use proptest::prelude::*; @@ -737,91 +742,96 @@ mod tests { #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] - fn test_inv_ipld_round_trip(payload in Payload::::arbitrary()) { + fn test_ipld_round_trip(payload in Payload::::arbitrary()) { let observed: Named = payload.clone().into(); - let parsed = Payload::::try_from(observed); + let parsed = Payload::::try_from(observed.clone()); - dbg!(&parsed); - prop_assert!(matches!(parsed, Ok(payload))); + prop_assert!(parsed.is_ok()); + prop_assert_eq!(parsed.unwrap(), payload); + } + + #[test_log::test] + fn test_ipld_only_has_correct_fields(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); + + if let Ipld::Map(named) = observed { + prop_assert!(named.len() >= 6); + prop_assert!(named.len() <= 11); + + for key in named.keys() { + prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "cmd" | "args" | "prf" | "cause" | "meta" | "nonce" | "exp" | "iat")); + } + } else { + prop_assert!(false, "ipld map"); + } } - // #[test_log::test] - // fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { - // let observed: Ipld = payload.clone().into(); - - // if let Ipld::Map(named) = observed { - // prop_assert!(named.len() <= 10); - - // for key in named.keys() { - // prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); - // } - // } else { - // panic!("Expected Ipld::Map, got {:?}", observed); - // } - // } - - // #[test_log::test] - // fn test_ipld_field_types(payload in Payload::::arbitrary()) { - // let named: Named = payload.clone().into(); - - // dbg!(payload.issuer.to_string()); - - // prop_assert!(named.len() <= 10); - - // let iss = named.get("iss".into()); - // let aud = named.get("aud".into()); - // let cmd = named.get("cmd".into()); - // let pol = named.get("pol".into()); - // let nonce = named.get("nonce".into()); - // let exp = named.get("exp".into()); - - // // Required Fields - // prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); - // prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); - // prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); - // prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); - // prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); - // prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); - - // // Optional Fields - // match (payload.subject, named.get("sub")) { - // (Some(sub), Some(Ipld::String(s))) => { - // prop_assert_eq!(&sub.to_string(), s); - // } - // (None, Some(Ipld::Null)) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.via, named.get("via")) { - // (Some(via), Some(Ipld::String(s))) => { - // prop_assert_eq!(&via.to_string(), s); - // } - // (None, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.metadata.is_empty(), named.get("meta")) { - // (false, Some(Ipld::Map(btree))) => { - // prop_assert_eq!(&payload.metadata, btree); - // } - // (true, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.not_before, named.get("nbf")) { - // (Some(nbf), Some(Ipld::Integer(i))) => { - // prop_assert_eq!(&i128::from(nbf), i); - // } - // (None, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - // } - - // #[test_log::test] - // fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { - // // Just ensuring that a negative test shows up - // let parsed = Payload::::try_from(ipld.0); - // prop_assert!(parsed.is_err()) - // } + #[test_log::test] + fn test_ipld_field_types(payload in Payload::::arbitrary()) { + let named: Named = payload.clone().into(); + + let sub = named.get("sub".into()); + let iss = named.get("iss".into()); + let cmd = named.get("cmd".into()); + let args = named.get("args".into()); + let prf = named.get("prf".into()); + let nonce = named.get("nonce".into()); + + // Required Fields + prop_assert_eq!(sub.unwrap(), &Ipld::String(payload.subject.to_string())); + prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.ability.to_command())); + + prop_assert_eq!(args.unwrap(), &payload.ability.into()); + prop_assert!(matches!(args, Some(Ipld::Map(_)))); + + prop_assert!(matches!(prf.unwrap(), &Ipld::List(_))); + if let Some(Ipld::List(ipld_proofs)) = prf { + prop_assert_eq!(ipld_proofs.len(), payload.proofs.len()); + + for entry in ipld_proofs { + prop_assert!(matches!(entry, Ipld::Link(_))); + } + } else { + prop_assert!(false); + } + + prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + + // Optional Fields + prop_assert_eq!(payload.audience.map(|did| did.into()), named.get("aud").cloned()); + prop_assert_eq!(payload.cause.map(Ipld::Link), named.get("cause").cloned()); + + match (payload.metadata.is_empty(), named.get("meta")) { + (false, Some(Ipld::Map(btree))) => { + prop_assert_eq!(&payload.metadata, btree); + } + (true, None) => prop_assert!(true), + _ => prop_assert!(false) + } + + match (payload.expiration, named.get("exp")) { + (Some(exp), Some(Ipld::Integer(i))) => { + prop_assert_eq!(i128::from(exp), i.clone()); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } + + match (payload.issued_at, named.get("iat")) { + (Some(iat), Some(Ipld::Integer(i))) => { + prop_assert_eq!(i128::from(iat), i.clone()); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } + } + + #[test_log::test] + fn test_non_payload(named in arguments::Named::::arbitrary()) { + // Just ensuring that a negative test shows up + let parsed = Payload::::try_from(named); + prop_assert!(parsed.is_err()) + } } } From 07ed47888ace6bfa1a30ebf4a5db2341f1aa019b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 17 Mar 2024 21:57:13 -0700 Subject: [PATCH 224/234] Fix glob matcher --- src/crypto/varsig/header/preset.rs | 2 +- src/delegation/payload.rs | 2 +- src/delegation/policy/predicate.rs | 612 +++++++++++++++++++++++++++-- src/did/key/signer.rs | 109 +++-- src/did/preset.rs | 16 +- src/ipld/newtype.rs | 35 ++ 6 files changed, 706 insertions(+), 70 deletions(-) diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index 00bab1d8..80664d5e 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -100,7 +100,7 @@ impl Header for Preset { Preset::Es256(es256) => es256.codec(), Preset::Es256k(es256k) => es256k.codec(), Preset::Es512(es512) => es512.codec(), - // BLS? + // Preset::Bls } } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c54438ec..2f2715ef 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -398,7 +398,7 @@ mod tests { use testresult::TestResult; proptest! { - #![proptest_config(ProptestConfig::with_cases(100))] + // #![proptest_config(ProptestConfig::with_cases(200))] #[test_log::test] fn test_ipld_round_trip(payload in Payload::::arbitrary()) { diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index abfb96a5..d8e7cc5b 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,18 +1,14 @@ use super::selector::filter::Filter; -use super::selector::{Select, Selector, SelectorError}; +use super::selector::{Select, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; -// FIXME Normal form? -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq)] pub enum Predicate { // Comparison @@ -86,14 +82,18 @@ impl Predicate { .get(data)? .to_vec() .iter() - .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, + .try_fold(true, |acc, each_datum| { + Ok(acc && p.clone().run(&each_datum.0)?) + })?, Predicate::Some(xs, p) => { let pred = p.clone(); xs.get(data)? .to_vec() .iter() - .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? + .try_fold(false, |acc, each_datum| { + Ok(acc || pred.clone().run(&each_datum.0)?) + })? } }) } @@ -664,32 +664,69 @@ impl Predicate { } } -pub fn glob(input: &String, pattern: &String) -> bool { - let mut chars = input.chars(); - let mut like = pattern.chars(); - - loop { - match (chars.next(), like.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; +pub fn glob(input: &str, pattern: &str) -> bool { + if pattern.is_empty() { + return input == ""; + } + + // Parsing pattern + let (saw_escape, mut patterns, mut working) = pattern.chars().fold( + (false, vec![], "".to_string()), + |(saw_escape, mut acc, mut working), c| { + match c { + '*' => { + if saw_escape { + working.push('*'); + (false, acc, working) + } else { + acc.push(working); + working = "".to_string(); + (false, acc, working) + } } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; + '\\' => { + if saw_escape { + // Push prev escape + working.push('\\'); + } + (true, acc, working) + } + _ => { + if saw_escape { + working.push('\\'); + } + + working.push(c); + (false, acc, working) } } - (None, None) => { - return true; - } - } + }, + ); + + if saw_escape { + working.push('\\'); } + + patterns.push(working); + + // Test input against the pattern + patterns + .iter() + .enumerate() + .try_fold(input, |acc, (idx, pattern_frag)| { + if let Some((pre, post)) = acc.split_once(pattern_frag) { + if idx == 0 && !pattern.starts_with("*") && !pre.is_empty() { + Err(()) + } else if idx == patterns.len() - 1 && !pattern.ends_with("*") && !post.is_empty() { + Err(()) + } else { + Ok(post) + } + } else { + Err(()) + } + }) + .is_ok() } impl TryFrom for Predicate { @@ -873,7 +910,7 @@ impl From for Ipld { #[cfg(feature = "test_utils")] impl Arbitrary for Predicate { - type Parameters = (); // FIXME? + type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { @@ -913,3 +950,518 @@ impl Arbitrary for Predicate { prop_oneof![leaf, connective, quantified].boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod glob { + use super::*; + + #[test_log::test] + fn test_concrete() -> TestResult { + let got = glob(&"hello world", &"hello world"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_concrete_fail() -> TestResult { + let got = glob(&"hello world", &"NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_empty_pattern_fail() -> TestResult { + let got = glob(&"hello world", &""); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_escaped_star() -> TestResult { + let got = glob(&"*", &r#"\*"#); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_inner_escaped_star() -> TestResult { + let got = glob(&"hello, * world*", &r#"hello*\**\*"#); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_empty_string_fail() -> TestResult { + let got = glob(&"", &"NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_left_star() -> TestResult { + let got = glob(&"hello world", &"*world"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_left_star_failure() -> TestResult { + let got = glob(&"hello world", &"*NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_right_star() -> TestResult { + let got = glob(&"hello world", &"hello*"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_right_star_failure() -> TestResult { + let got = glob(&"hello world", &"NOPE*"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_only_star() -> TestResult { + let got = glob(&"hello world", &"*"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_two_stars() -> TestResult { + let got = glob(&"hello world", &"* *"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_two_stars_fail() -> TestResult { + let got = glob(&"hello world", &"*@*"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_multiple_inner_stars() -> TestResult { + let got = glob(&"hello world", &"h*l*o*w*r*d"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_multiple_inner_stars_fail() -> TestResult { + let got = glob(&"hello world", &"a*b*c*d*e*f"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_concrete_with_multiple_inner_stars() -> TestResult { + let got = glob(&"hello world", &"hello* *world"); + assert!(got); + Ok(()) + } + } + + mod run { + use super::*; + use libipld::ipld; + + fn simple() -> Ipld { + ipld!({ + "foo": 42, + "bar": "baz".to_string(), + "qux": true + }) + } + + fn email() -> Ipld { + ipld!({ + "from": "alice@example.com".to_string(), + "to": ["bob@example.com".to_string(), "fraud@example.com".to_string()], + "cc": ["carol@example.com".to_string()], + "subject": "Quarterly Reports".to_string(), + "body": "Here's Q2 the reports ...".to_string() + }) + } + + fn wasm() -> Ipld { + ipld!({ + "mod": "data:application/wasm;base64,SOMEBASE64GOESHERE", + "fun": "test", + "input": [0, 1, 2 ,3] + }) + } + + #[test_log::test] + fn test_eq() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_fail_same_type() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from").unwrap(), "NOPE".into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_bad_selector() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".NOPE").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email()).is_err()); + Ok(()) + } + + #[test_log::test] + fn test_eq_fail_different_type() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from").unwrap(), 42.into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_gt() -> TestResult { + let p = Predicate::GreaterThan(Select::from_str(".foo").unwrap(), (41.9).into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gt_fail() -> TestResult { + let p = Predicate::GreaterThan(Select::from_str(".foo").unwrap(), 42.into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gte() -> TestResult { + let p = Predicate::GreaterThanOrEqual(Select::from_str(".foo").unwrap(), 42.into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gte_fail() -> TestResult { + let p = Predicate::GreaterThanOrEqual(Select::from_str(".foo").unwrap(), (42.1).into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lt() -> TestResult { + let p = Predicate::LessThan(Select::from_str(".foo").unwrap(), (42.1).into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lt_fail() -> TestResult { + let p = Predicate::LessThan(Select::from_str(".foo").unwrap(), 42.into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lte() -> TestResult { + let p = Predicate::LessThanOrEqual(Select::from_str(".foo").unwrap(), 42.into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lte_fail() -> TestResult { + let p = Predicate::LessThanOrEqual(Select::from_str(".foo").unwrap(), (41.9).into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_like() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "alice@*".into()); + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_concrete() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "NOPE".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_left_star() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "*NOPE".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_right_star() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "NOPE*".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_both_stars() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "*NOPE*".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_not() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + ))); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_not_fail() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ))); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_both_succeed() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_left_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_right_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_both_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_both_succeed() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_left_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_right_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_both_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + // FIXME nested, too + #[test_log::test] + fn test_every() -> TestResult { + let p = Predicate::Every( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 100.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_every_failure() -> TestResult { + let p = Predicate::Every( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 1.into(), + )), + ); + + assert!(!p.run(&wasm())?); + Ok(()) + } + + // FIXME nested, too + #[test_log::test] + fn test_some_all_succeed() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 100.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_some_not_all() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 1.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_some_all_fail() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 0.into(), + )), + ); + + assert!(!p.run(&wasm())?); + Ok(()) + } + } +} diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs index 529f557c..b7f95e40 100644 --- a/src/did/key/signer.rs +++ b/src/did/key/signer.rs @@ -1,6 +1,4 @@ use super::Signature; -use crate::did::Did; -use did_url::DID; use enum_as_inner::EnumAsInner; #[cfg(feature = "eddsa")] @@ -27,50 +25,49 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use crate::crypto::bls12381; -/// Signature types that are verifiable by `did:key` [`Verifier`]s. -#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +/// Signer types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Clone, EnumAsInner)] pub enum Signer { - /// `EdDSA` signature. + /// `EdDSA` signer. #[cfg(feature = "eddsa")] EdDsa(ed25519_dalek::SigningKey), - // FIXME - // /// `ES256K` (`secp256k1`) signature. - // #[cfg(feature = "es256k")] - // Es256k(k256::ecdsa::Signer), - // /// `P-256` signature. - // #[cfg(feature = "es256")] - // P256(p256::ecdsa::Signer), + /// `ES256K` (`secp256k1`) signer. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::SigningKey), - // /// `P-384` signature. - // #[cfg(feature = "es384")] - // P384(p384::ecdsa::Signer), + /// `P-256` signer. + #[cfg(feature = "es256")] + P256(p256::ecdsa::SigningKey), - // /// `P-521` signature. - // #[cfg(feature = "es512")] - // P521(ext_p521::ecdsa::Signer), + /// `P-384` signer. + #[cfg(feature = "es384")] + P384(p384::ecdsa::SigningKey), - // /// `RS256` signature. - // #[cfg(feature = "rs256")] - // Rs256(rs256::Signer), + /// `P-521` signer. + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::SigningKey), - // /// `RS512` signature. - // #[cfg(feature = "rs512")] - // Rs512(rs512::Signer), + /// `RS256` signer. + #[cfg(feature = "rs256")] + Rs256(rs256::SigningKey), - // /// `BLS 12-381` signature for the "min pub key" variant. - // #[cfg(feature = "bls")] - // BlsMinPk(bls12381::min_pk::Signer), + /// `RS512` signer. + #[cfg(feature = "rs512")] + Rs512(rs512::SigningKey), - // /// `BLS 12-381` signature for the "min sig" variant. - // #[cfg(feature = "bls")] - // BlsMinSig(bls12381::min_sig::Signer), + /// `BLS 12-381` signer for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::SecretKey), - // /// An unknown signature type. + /// `BLS 12-381` signer for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::SecretKey), + // /// An unknown signer type. // /// // /// This is primarily for parsing, where reification is delayed // /// until the DID method is known. - // Unknown(Vec), + // FIXME rmeove Unknown(Vec), } impl signature::Signer for Signer { @@ -81,6 +78,54 @@ impl signature::Signer for Signer { let sig = signer.sign(msg); Ok(Signature::EdDsa(sig)) } + + #[cfg(feature = "es256k")] + Signer::Es256k(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Es256k(sig)) + } + + #[cfg(feature = "es256")] + Signer::P256(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P256(sig)) + } + + #[cfg(feature = "es384")] + Signer::P384(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P384(sig)) + } + + #[cfg(feature = "es512")] + Signer::P521(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P521(sig)) + } + + #[cfg(feature = "rs256")] + Signer::Rs256(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Rs256(sig)) + } + + #[cfg(feature = "rs512")] + Signer::Rs512(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Rs512(sig)) + } + + #[cfg(feature = "bls")] + Signer::BlsMinPk(signer) => { + let sig = signer.try_sign(msg)?; + Ok(Signature::BlsMinPk(sig)) + } + + #[cfg(feature = "bls")] + Signer::BlsMinSig(signer) => { + let sig = signer.try_sign(msg)?; + Ok(Signature::BlsMinSig(sig)) + } } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index 635540f7..a4616525 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -45,12 +45,20 @@ impl From for DID { } } -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +#[derive(Clone, EnumAsInner)] pub enum Signer { Key(key::Signer), // FIXME Dns(did_url::DID), } +impl std::fmt::Debug for Signer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Signer::Key(signer) => write!(f, "Signer::Key(HIDDEN)"), + } + } +} + impl Did for Verifier { type Signature = key::Signature; type Signer = self::Signer; @@ -102,10 +110,6 @@ impl Arbitrary for Verifier { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - prop_oneof![ - key::Verifier::arbitrary().prop_map(Verifier::Key), - // FIXME did_url::DID::arbitrary().prop_map(Verifier::Dns), - ] - .boxed() + key::Verifier::arbitrary().prop_map(Verifier::Key).boxed() } } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 30a28c89..31451de1 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -53,6 +53,30 @@ impl From for Newtype { } } +impl From for Newtype { + fn from(i: i128) -> Self { + Newtype(Ipld::Integer(i)) + } +} + +impl From for Newtype { + fn from(f: f64) -> Self { + Newtype(Ipld::Float(f)) + } +} + +impl From<&str> for Newtype { + fn from(s: &str) -> Self { + Newtype(Ipld::String(s.to_string())) + } +} + +impl From for Newtype { + fn from(s: String) -> Self { + Newtype(Ipld::String(s)) + } +} + impl TryFrom for String { type Error = (); @@ -64,6 +88,17 @@ impl TryFrom for String { } } +impl TryFrom for i128 { + type Error = (); + + fn try_from(nt: Newtype) -> Result { + match nt.0 { + Ipld::Integer(i) => Ok(i), + _ => Err(()), + } + } +} + impl From for Ipld { fn from(wrapped: Newtype) -> Self { wrapped.0 From b965982d1a98d53f387bbfa845327f6a66717de2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 18 Mar 2024 00:01:34 -0700 Subject: [PATCH 225/234] Working on edge cases in selector parser --- src/delegation/policy/predicate.rs | 105 +++++++++- src/delegation/policy/selector.rs | 248 ++++++++++++++--------- src/delegation/policy/selector/filter.rs | 231 ++++++++++++++++++++- 3 files changed, 475 insertions(+), 109 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index d8e7cc5b..a9f5b1f5 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1089,11 +1089,11 @@ mod tests { fn email() -> Ipld { ipld!({ - "from": "alice@example.com".to_string(), - "to": ["bob@example.com".to_string(), "fraud@example.com".to_string()], - "cc": ["carol@example.com".to_string()], - "subject": "Quarterly Reports".to_string(), - "body": "Here's Q2 the reports ...".to_string() + "from": "alice@example.com", + "to": ["bob@example.com", "fraud@example.com"], + "cc": ["carol@example.com"], + "subject": "Quarterly Reports", + "body": "Here's Q2 the reports ..." }) } @@ -1245,6 +1245,17 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_double_negative() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ))))); + + assert!(p.run(&email())?); + Ok(()) + } + #[test_log::test] fn test_not_fail() -> TestResult { let p = Predicate::Not(Box::new(Predicate::Equal( @@ -1463,5 +1474,89 @@ mod tests { assert!(!p.run(&wasm())?); Ok(()) } + + #[test_log::test] + fn test_deeply_alternate_some_and_every() -> TestResult { + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b.c[]").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".d").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".e[]").unwrap(), + Box::new(Predicate::Equal( + Select::from_str(".f.g").unwrap(), + 0.into(), + )), + )), + )), + )), + ); + + let deeply_nested_data = ipld!( + { + // Some + "a": [ + { + "b": { + // Every + "c": { + "c1": { + // Some + "d": [ + { + // Every + "e": { + "e1": { + "f": { + "g": 42 + }, + "nope": -10 + }, + "e2": { + "_": "not selected", + "f": { + "g": 99 + }, + } + } + } + ] + }, + "c2": { + // Some + "*": "avoid", + "_": [ + { + // Every + "e": { + "e1": { + "f": { + "g": 42 + }, + "nope": -10 + }, + "e2": { + "_": "not selected", + "f": { + "g": 99 + }, + } + } + } + ] + } + } + } + } + ], + "z": "doesn't read this" + } + ); + + assert!(p.run(&deeply_nested_data)?); + Ok(()) + } } } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 0489fa5a..c39476c2 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -8,12 +8,9 @@ pub use error::{ParseError, SelectorErrorReason}; pub use select::Select; pub use selectable::Selectable; -use crate::ipld; use filter::Filter; -use libipld_core::ipld::Ipld; use nom::{ self, - branch::alt, bytes::complete::tag, character::complete::char, combinator::map_res, @@ -41,110 +38,22 @@ impl Selector { pub fn is_related(&self, other: &Selector) -> bool { self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) } - - // pub fn get(&self, ctx: &Ipld) -> Result { - // let ipld: Ipld = self - // .0 - // .iter() - // .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - // seen_ops.push(op); - // - // match op { - // Filter::Try(inner) => { - // let op: Filter = *inner.clone(); - // let ipld: Ipld = Select::Get::(vec![op]) - // .resolve(ctx) - // .unwrap_or(Ipld::Null); - // - // Ok((ipld, seen_ops)) - // } - // Filter::ArrayIndex(i) => { - // let result = { - // match ipld { - // Ipld::List(xs) => { - // if i.abs() as usize > xs.len() { - // return Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::IndexOutOfBounds, - // )); - // }; - // - // xs.get((xs.len() as i32 + *i) as usize) - // .ok_or(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::IndexOutOfBounds, - // )) - // .cloned() - // } - // // FIXME behaviour on maps? type error - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotAList, - // )), - // } - // }; - // - // Ok((result?, seen_ops)) - // } - // Filter::Field(k) => { - // let result = match ipld { - // Ipld::Map(xs) => xs - // .get(k) - // .ok_or(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::KeyNotFound, - // )) - // .cloned(), - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotAMap, - // )), - // }; - // - // Ok((result?, seen_ops)) - // } - // Filter::Values => { - // let result = match ipld { - // Ipld::List(xs) => Ok(Ipld::List(xs)), - // Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotACollection, - // )), - // }; - // - // Ok((result?, seen_ops)) - // } - // } - // })? - // .0; - // - // Ok(ipld::Newtype(ipld)) - // } } pub fn parse(input: &str) -> IResult<&str, Selector> { - let without_this = many1(filter::parse); - let with_this = preceded(char('.'), many0(filter::parse)); + if input.starts_with("..") { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Count, + ))); + } - // NOTE: must try without_this this first, to disambiguate `.field` from `.` - let p = map_res(alt((without_this, with_this)), |found| { - Ok::(Selector(found)) - }); + let with_try_this = preceded(char('.'), preceded(many0(char('?')), many0(filter::parse))); + let p = map_res(with_try_this, |found| Ok::(Selector(found))); context("selector", p)(input) } -pub fn parse_this(input: &str) -> IResult<&str, Selector> { - let p = map_res(tag("."), |_| Ok::(Selector(vec![]))); - context("this", p)(input) -} - -pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { - let p = many1(filter::parse); - context("filters", p)(input) -} - impl fmt::Display for Selector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ops = self.0.iter(); @@ -266,7 +175,9 @@ impl Arbitrary for Selector { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use proptest::prelude::*; + use testresult::TestResult; mod serialization { use super::*; @@ -279,5 +190,144 @@ mod tests { prop_assert_eq!(Ok(sel), deserialized); } } + + #[test_log::test] + fn test_bare_dot() -> TestResult { + pretty::assert_eq!(Selector::from_str("."), Ok(Selector(vec![]))); + Ok(()) + } + + #[test_log::test] + fn test_dot_try() -> TestResult { + pretty::assert_eq!(Selector::from_str(".?"), Ok(Selector(vec![]))); + Ok(()) + } + + #[test_log::test] + fn test_dot_many_tries() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".?????????????????????"), + Ok(Selector(vec![])) + ); + Ok(()) + } + + #[test_log::test] + fn test_dot_many_tries_and_dot_field() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".?????????????????????.foo"), + Ok(Selector(vec![Filter::Field("foo".to_string())])) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_question_marks() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".foo??????????????"), + Ok(Selector(vec![Filter::Try(Box::new(Filter::Field( + "foo".to_string() + )))])) + ); + Ok(()) + } + + #[test_log::test] + fn test_fails_trailing_dot() -> TestResult { + let got = Selector::from_str(".foo."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_leading_double_dot() -> TestResult { + let got = Selector::from_str("..foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_inner_double_dot() -> TestResult { + let got = Selector::from_str(".foo..bar"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_multiple_leading_dots() -> TestResult { + let got = Selector::from_str(".."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fail_missing_leading_dot() -> TestResult { + let got = Selector::from_str("[22]"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_dot_field() -> TestResult { + let got = Selector::from_str(".foo"); + pretty::assert_eq!(got, Ok(Selector(vec![Filter::Field("foo".to_string())]))); + Ok(()) + } + + #[test_log::test] + fn test_multiple_dot_fields() -> TestResult { + let got = Selector::from_str(".foo.bar.baz"); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Field("baz".to_string()) + ])) + ); + Ok(()) + } + + #[test_log::test] + fn test_fairly_complex() -> TestResult { + let got = Selector::from_str(r#".foo.bar[].baz[0][]["42"]._quux?[8]"#); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Values, + Filter::Field("baz".to_string()), + Filter::ArrayIndex(0), + Filter::Values, + Filter::Field("42".to_string()), + Filter::Try(Box::new(Filter::Field("_quux".to_string()))), + Filter::ArrayIndex(8) + ])) + ); + + Ok(()) + } + + #[test_log::test] + fn test_very_complex() -> TestResult { + let got = Selector::from_str(r#".???.foo.bar[].baz[0][]["42"]._quux??[8]"#); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Values, + Filter::Field("baz".to_string()), + Filter::ArrayIndex(0), + Filter::Values, + Filter::Field("42".to_string()), + Filter::Try(Box::new(Filter::Field("_quux".to_string()))), + Filter::ArrayIndex(8) + ])) + ); + + Ok(()) + } } } diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 016fad93..6f704a7b 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -81,9 +81,11 @@ pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_try(input: &str) -> IResult<&str, Filter> { - let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { - Ok::(Filter::Try(Box::new(found))) - }); + let p = map_res( + terminated(parse_non_try, many1(tag("?"))), + // FIXME old code: terminated(parse_non_try, tag("?")), + |found: Filter| Ok::(Filter::Try(Box::new(found))), + ); context("try", p)(input) } @@ -241,10 +243,10 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - (_, found) => Ok(found), + ("", found) => Ok(found), // ("", found) => Ok(found), // FIXME - // (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } } @@ -281,7 +283,9 @@ impl Arbitrary for Filter { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use proptest::prelude::*; + use testresult::TestResult; mod serialization { use super::*; @@ -294,5 +298,222 @@ mod tests { prop_assert_eq!(Ok(filter), deserialized); } } + + #[test_log::test] + fn test_fails_on_empty() -> TestResult { + let got = Filter::from_str(""); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_bare_dot() -> TestResult { + // NOTE this passes as a Selector, but not a Filter + let got = Filter::from_str("."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_multiple_bare_dots() -> TestResult { + let got = Filter::from_str(".."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_leading_dots() -> TestResult { + let got = Filter::from_str("..foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_empty_whitespace() -> TestResult { + let got = Filter::from_str(" "); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_leading_whitespace() -> TestResult { + let got = Filter::from_str(" .foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_trailing_whitespace() -> TestResult { + let got = Filter::from_str(".foo "); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_values() -> TestResult { + let got = Filter::from_str("[]"); + pretty::assert_eq!(got, Ok(Filter::Values)); + Ok(()) + } + + #[test_log::test] + fn test_values_fails_inner_whitespace() -> TestResult { + let got = Filter::from_str("[ ]"); + pretty::assert_eq!(got.is_err(), true); + Ok(()) + } + + #[test_log::test] + fn test_array_index_zero() -> TestResult { + let got = Filter::from_str("[0]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(0))); + Ok(()) + } + + #[test_log::test] + fn test_array_index_small() -> TestResult { + let got = Filter::from_str("[2]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(2))); + Ok(()) + } + + #[test_log::test] + fn test_array_index_large() -> TestResult { + let got = Filter::from_str("[1234567890]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(1234567890))); + Ok(()) + } + + #[test_log::test] + fn test_array_from_end() -> TestResult { + let got = Filter::from_str("[-42]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(-42))); + Ok(()) + } + + #[test_log::test] + fn test_array_fails_spaces() -> TestResult { + let got = Filter::from_str("[ 42]"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_dot_field() -> TestResult { + let got = Filter::from_str(".F0o"); + pretty::assert_eq!(got, Ok(Filter::Field("F0o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_dot_field_starting_underscore() -> TestResult { + let got = Filter::from_str("._foo"); + pretty::assert_eq!(got, Ok(Filter::Field("_foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_dot_field_trailing_underscore() -> TestResult { + let got = Filter::from_str(".fO0_"); + pretty::assert_eq!(got, Ok(Filter::Field("fO0_".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_field_with_leading_number() -> TestResult { + let got = Filter::from_str(".1foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_field_with_inner_symbol() -> TestResult { + let got = Filter::from_str(".fo%o"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field() -> TestResult { + let got = Filter::from_str(r#"["F0o"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("F0o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_fails_without_quotes() -> TestResult { + let got = Filter::from_str(r#"[F0o]"#); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_fails_if_missing_right_brace() -> TestResult { + let got = Filter::from_str(r#"["F0o""#); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_starting_underscore() -> TestResult { + let got = Filter::from_str(r#"["_foo"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("_foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_trailing_underscore() -> TestResult { + let got = Filter::from_str(r#"["fO0_"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("fO0_".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_with_leading_number() -> TestResult { + let got = Filter::from_str(r#"["1foo"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("1foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_with_inner_symbol() -> TestResult { + let got = Filter::from_str(r#"[".fo%o"]"#); + pretty::assert_eq!(got, Ok(Filter::Field(".fo%o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_try() -> TestResult { + let got = Filter::from_str(".foo?"); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries() -> TestResult { + let got = Filter::from_str(".foo???????????????????"); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_fails_bare_try() -> TestResult { + let got = Filter::from_str("?"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_try() -> TestResult { + let got = Filter::from_str(".?"); + assert!(got.is_err()); + Ok(()) + } } } From c949a4f007e5b1573e3d3ba485ea97f356b84838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 21 Mar 2024 23:54:46 +0100 Subject: [PATCH 226/234] refactor: Adjustments to `Store`s & `Agent`s (#9) * refactor: Make delegation::Store::get return Option * refactor: Make delegation::Store::insert take &self instead of &mut self * refactor: Make invocation::Store take &self instead of &mut self * refactor: Make `delegation::Agent` not take `&mut self` in methods * refactor: Make `Agent` take `DID` by value * refactor: Take `DID` by value in `delegation::Agent::new` * refactor: Change generic order in `delegation::Agent` and add defaults --- src/delegation/agent.rs | 41 ++++++------- src/delegation/store/memory.rs | 100 +++++++++++++++++++++++-------- src/delegation/store/traits.rs | 36 +++++++----- src/invocation/agent.rs | 104 ++++++++++++++++----------------- src/invocation/store.rs | 65 ++++++++++++++++----- 5 files changed, 217 insertions(+), 129 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 89f86a8b..bfee4119 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,6 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::ability::arguments::Named; +use crate::did; use crate::{ crypto::{signature::Envelope, varsig, Nonce}, did::Did, @@ -14,40 +15,38 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; -/// A stateful agent capable of delegatint to others, and being delegated to. +/// A stateful agent capable of delegating to others, and being delegated to. /// /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent< - 'a, - DID: Did, S: Store, - V: varsig::Header, - Enc: Codec + TryFrom + Into, + DID: Did = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The [`Did`][Did] of the agent. - pub did: &'a DID, + pub did: DID, /// The attached [`deleagtion::Store`][super::store::Store]. - pub store: &'a mut S, + pub store: S, - signer: &'a ::Signer, + signer: ::Signer, _marker: PhantomData<(V, Enc)>, } impl< - 'a, - DID: Did + Clone, S: Store + Clone, + DID: Did + Clone, V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, - > Agent<'a, DID, S, V, Enc> + > Agent where Ipld: Encode, Payload: TryFrom>, Named: From>, { - pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { + pub fn new(did: DID, signer: ::Signer, store: S) -> Self { Self { did, store, @@ -73,7 +72,7 @@ where let nonce = Nonce::generate_12(&mut salt); if let Some(ref sub) = subject { - if sub == self.did { + if sub == &self.did { let payload: Payload = Payload { issuer: self.did.clone(), audience, @@ -88,19 +87,17 @@ where }; return Ok( - Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME") + Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME") ); } } - let to_delegate = &self + let proofs = &self .store .get_chain(&self.did, &subject, "/".into(), vec![], now) .map_err(DelegateError::StoreError)? - .ok_or(DelegateError::ProofsNotFound)? - .first() - .1 - .payload(); + .ok_or(DelegateError::ProofsNotFound)?; + let to_delegate = proofs.first().1.payload(); let mut policy = to_delegate.policy.clone(); policy.append(&mut new_policy.clone()); @@ -118,11 +115,11 @@ where not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")) + Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")) } pub fn receive( - &mut self, + &self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, ) -> Result<(), ReceiveError> { @@ -130,7 +127,7 @@ where return Ok(()); } - if delegation.audience() != self.did { + if delegation.audience() != &self.did { return Err(ReceiveError::WrongAudience(delegation.audience().clone())); } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 18d86103..c36a6e2d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -10,7 +10,11 @@ use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::collections::{BTreeMap, BTreeSet}; +use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::Infallible, +}; use web_time::SystemTime; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -69,36 +73,77 @@ use web_time::SystemTime; /// linkStyle 6 stroke:orange; /// linkStyle 1 stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct MemoryStore< DID: did::Did + Ord = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - ucans: BTreeMap>, + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +struct MemoryStoreInner< + DID: did::Did + Ord = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + ucans: BTreeMap>>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } -impl MemoryStore { +impl, C: Codec + TryFrom + Into> + MemoryStore +{ pub fn new() -> Self { Self::default() } pub fn len(&self) -> usize { - self.ucans.len() + self.read().ucans.len() } pub fn is_empty(&self) -> bool { - self.ucans.is_empty() // FIXME acocunt for revocations? + self.read().ucans.is_empty() // FIXME acocunt for revocations? + } + + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // We ignore lock poisoning for simplicity + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // We ignore lock poisoning for simplicity + poison.into_inner() + } + } } } -impl, C: Codec + TryFrom + Into> Default +impl, C: Codec + TryFrom + Into> Default for MemoryStore { fn default() -> Self { - MemoryStore { + Self { + inner: Default::default(), + } + } +} + +impl, C: Codec + TryFrom + Into> Default + for MemoryStoreInner +{ + fn default() -> Self { + MemoryStoreInner { ucans: BTreeMap::new(), index: BTreeMap::new(), revocations: BTreeSet::new(), @@ -117,34 +162,39 @@ where delegation::Payload: TryFrom>, Delegation: Encode, { - type DelegationStoreError = String; // FIXME misisng + type DelegationStoreError = Infallible; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans - .get(cid) - .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError> { + // cheap Arc clone + Ok(self.read().ucans.get(cid).cloned()) // FIXME } fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - self.index + let mut write_tx = self.write(); + + write_tx + .index .entry(delegation.subject().clone()) .or_default() .entry(delegation.audience().clone()) .or_default() .insert(cid); - self.ucans.insert(cid.clone(), delegation); + write_tx.ucans.insert(cid.clone(), Arc::new(delegation)); Ok(()) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { - self.revocations.insert(cid); + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + self.write().revocations.insert(cid); Ok(()) } @@ -155,12 +205,14 @@ where command: String, policy: Vec, // FIXME now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> { + ) -> Result>)>>, Self::DelegationStoreError> + { let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); + let read_tx = self.read(); - let all_powerlines = self.index.get(&None).unwrap_or(&blank_map); - let all_aud_for_subject = self.index.get(subject).unwrap_or(&blank_map); + let all_powerlines = read_tx.index.get(&None).unwrap_or(&blank_map); + let all_aud_for_subject = read_tx.index.get(subject).unwrap_or(&blank_map); let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); @@ -185,11 +237,11 @@ where } 'inner: for cid in parent_cid_candidates { - if self.revocations.contains(cid) { + if read_tx.revocations.contains(cid) { continue; } - if let Some(delegation) = self.ucans.get(cid) { + if let Some(delegation) = read_tx.ucans.get(cid) { if delegation.check_time(now).is_err() { continue; } @@ -217,7 +269,7 @@ where } } - hypothesis_chain.push((cid.clone(), delegation)); + hypothesis_chain.push((cid.clone(), Arc::clone(delegation))); let issuer = delegation.issuer().clone(); diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 3bd7b1fb..7413e700 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -5,16 +5,19 @@ use crate::{ }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; use web_time::SystemTime; pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError>; fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; @@ -22,7 +25,7 @@ pub trait Store, Enc: Codec + TryFrom + In // FIXME validate invocation // store invocation // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError>; fn get_chain( &self, @@ -31,7 +34,7 @@ pub trait Store, Enc: Codec + TryFrom + In command: String, policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::DelegationStoreError>; fn get_chain_cids( &self, @@ -60,32 +63,34 @@ pub trait Store, Enc: Codec + TryFrom + In fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { - cids.iter().try_fold(vec![], |mut acc, cid| { - acc.push(self.get(cid)?); - Ok(acc) - }) + ) -> Result>>>, Self::DelegationStoreError> { + cids.iter() + .map(|cid| self.get(cid)) + .collect::>() } } impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> - Store for &mut T + Store for &T { type DelegationStoreError = >::DelegationStoreError; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError> { (**self).get(cid) } fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { (**self).insert(cid, delegation) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { (**self).revoke(cid) } @@ -96,7 +101,8 @@ impl, DID: Did, V: varsig::Header, C: Codec + TryFrom, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> { + ) -> Result>)>>, Self::DelegationStoreError> + { (**self).get_chain(audience, subject, command, policy, now) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index a4e5f7fd..11838bde 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -27,13 +27,13 @@ use std::{ collections::{BTreeMap, BTreeSet}, fmt, marker::PhantomData, + sync::Arc, }; use thiserror::Error; use web_time::SystemTime; #[derive(Debug)] pub struct Agent< - 'a, S: Store, D: delegation::store::Store, T: ToCommand = ability::preset::Preset, @@ -42,7 +42,7 @@ pub struct Agent< C: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The agent's [`DID`]. - pub did: &'a DID, + pub did: DID, /// A [`delegation::Store`][delegation::store::Store]. pub delegation_store: D, @@ -50,11 +50,11 @@ pub struct Agent< /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: S, - signer: &'a ::Signer, + signer: ::Signer, marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, D, V, C> Agent<'a, S, D, T, DID, V, C> +impl Agent where Ipld: Encode, T: ToCommand + Clone + ParseAbility, @@ -70,8 +70,8 @@ where >::DelegationStoreError: fmt::Debug, { pub fn new( - did: &'a DID, - signer: &'a ::Signer, + did: DID, + signer: ::Signer, invocation_store: S, delegation_store: D, ) -> Self { @@ -85,7 +85,7 @@ where } pub fn invoke( - &mut self, + &self, audience: Option, subject: DID, ability: T, @@ -171,7 +171,7 @@ where // } pub fn receive( - &mut self, + &self, invocation: Invocation, ) -> Result>, ReceiveError> where @@ -183,7 +183,7 @@ where } pub fn generic_receive( - &mut self, + &self, invocation: Invocation, now: SystemTime, ) -> Result>, ReceiveError> @@ -204,20 +204,27 @@ where .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; - let proof_payloads: Vec<&delegation::Payload> = self + let proofs = &self .delegation_store .get_many(&invocation.proofs()) - .map_err(ReceiveError::DelegationStoreError)? + .map_err(ReceiveError::DelegationStoreError)?; + let proof_payloads: Vec<&delegation::Payload> = proofs .iter() - .map(|d| &d.payload) - .collect(); + .zip(invocation.proofs().iter()) + .map(|(d, cid)| { + Ok(&d + .as_ref() + .ok_or(ReceiveError::MissingDelegation(*cid))? + .payload) + }) + .collect::>>()?; let _ = &invocation .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - Ok(if invocation.normalized_audience() != self.did { + Ok(if invocation.normalized_audience() != &self.did { Recipient::Other(invocation.payload) } else { Recipient::You(invocation.payload) @@ -225,7 +232,7 @@ where } // pub fn revoke( - // &mut self, + // &self, // subject: DID, // cause: Option, // cid: Cid, @@ -290,6 +297,9 @@ pub enum ReceiveError< > where >::InvocationStoreError: fmt::Debug, { + #[error("missing delegation: {0}")] + MissingDelegation(Cid), + #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -397,11 +407,9 @@ mod tests { (verifier, signer) } - fn setup_agent<'a>( - did: &'a crate::did::preset::Verifier, - signer: &'a crate::did::preset::Signer, - ) -> Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore> - { + fn setup_agent( + ) -> Agent { + let (did, signer) = gen_did(); let inv_store = crate::invocation::store::MemoryStore::default(); let del_store = crate::delegation::store::MemoryStore::default(); @@ -427,8 +435,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( None, @@ -456,8 +463,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_and_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( Some(agent.did.clone()), @@ -486,8 +492,7 @@ mod tests { #[test_log::test] fn test_other_recipient() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let (not_server, _) = gen_did(); @@ -517,8 +522,7 @@ mod tests { #[test_log::test] fn test_expired() -> TestResult { let (past, now, _exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( None, @@ -553,8 +557,8 @@ mod tests { #[test_log::test] fn test_invalid_sig() -> TestResult { let (_past, now, _exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); + let server = &agent.did; let mut invocation = agent.invoke( None, @@ -624,7 +628,7 @@ mod tests { ); let inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); + let del_store = crate::delegation::store::MemoryStore::default(); // Scenario // ======== @@ -739,18 +743,13 @@ mod tests { #[test_log::test] fn test_chain_ok() -> TestResult { - let mut ctx = setup_test_chain()?; - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new( - &ctx.server, - &ctx.server_signer, - &mut ctx.inv_store, - &mut ctx.del_store, + let ctx = setup_test_chain()?; + + let mut agent = Agent::new( + ctx.server.clone(), + ctx.server_signer.clone(), + &ctx.inv_store, + &ctx.del_store, ); let observed = agent.receive(ctx.account_invocation.clone()); @@ -760,18 +759,13 @@ mod tests { #[test_log::test] fn test_chain_wrong_sub() -> TestResult { - let mut ctx = setup_test_chain()?; - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new( - &ctx.server, - &ctx.server_signer, - &mut ctx.inv_store, - &mut ctx.del_store, + let ctx = setup_test_chain()?; + + let mut agent = Agent::new( + ctx.server.clone(), + ctx.server_signer.clone(), + &ctx.inv_store, + &ctx.del_store, ); let not_account_invocation = crate::Invocation::try_sign( diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 26618909..4c9279fa 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -4,6 +4,7 @@ use super::Invocation; use crate::ability; use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{collections::BTreeMap, convert::Infallible}; pub trait Store, C: Codec + Into + TryFrom> { @@ -12,10 +13,10 @@ pub trait Store, C: Codec + Into + TryFro fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError>; + ) -> Result>>, Self::InvocationStoreError>; fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), Self::InvocationStoreError>; @@ -31,20 +32,22 @@ impl< DID: Did, V: varsig::Header, C: Codec + Into + TryFrom, - > Store for &mut S + > Store for &S { type InvocationStoreError = >::InvocationStoreError; fn get( &self, cid: Cid, - ) -> Result>, >::InvocationStoreError> - { + ) -> Result< + Option>>, + >::InvocationStoreError, + > { (**self).get(cid) } fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), >::InvocationStoreError> { @@ -52,14 +55,48 @@ impl< } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct MemoryStore< T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - store: BTreeMap>, + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStoreInner< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>>, +} + +impl, Enc: Codec + Into + TryFrom> + MemoryStore +{ + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } } impl, Enc: Codec + Into + TryFrom> Default @@ -67,7 +104,9 @@ impl, Enc: Codec + Into + TryFrom> { fn default() -> Self { Self { - store: BTreeMap::new(), + inner: Arc::new(RwLock::new(MemoryStoreInner { + store: BTreeMap::new(), + })), } } } @@ -80,16 +119,16 @@ impl, Enc: Codec + Into + TryFrom> fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError> { - Ok(self.store.get(&cid)) + ) -> Result>>, Self::InvocationStoreError> { + Ok(self.read().store.get(&cid).cloned()) } fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), Self::InvocationStoreError> { - self.store.insert(cid, invocation); + self.write().store.insert(cid, Arc::new(invocation)); Ok(()) } } From 3503a328e9f43e3eb2db8e619e4a52519c08be49 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 18 Mar 2024 16:43:50 -0700 Subject: [PATCH 227/234] Fairly complete predicate tests --- src/delegation/policy/predicate.rs | 233 +++++++++++++++++++++-- src/delegation/policy/selector.rs | 24 +-- src/delegation/policy/selector/error.rs | 3 + src/delegation/policy/selector/filter.rs | 101 +++++++++- 4 files changed, 317 insertions(+), 44 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index a9f5b1f5..423d9303 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -67,6 +67,7 @@ impl Harmonization { } impl Predicate { + // FIXME make &self? pub fn run(self, data: &Ipld) -> Result { Ok(match self { Predicate::Equal(lhs, rhs_data) => lhs.get(data)? == rhs_data, @@ -78,21 +79,22 @@ impl Predicate { Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Every(xs, p) => xs - .get(data)? - .to_vec() - .iter() - .try_fold(true, |acc, each_datum| { - Ok(acc && p.clone().run(&each_datum.0)?) - })?, + Predicate::Every(xs, p) => { + xs.get(data)? + .to_vec() + .iter() + .try_fold(true, |acc, each_datum| { + dbg!("every", &p, acc, each_datum); + Ok(acc && p.clone().run(&each_datum.0)?) + })? + } Predicate::Some(xs, p) => { - let pred = p.clone(); - xs.get(data)? .to_vec() .iter() .try_fold(false, |acc, each_datum| { - Ok(acc || pred.clone().run(&each_datum.0)?) + dbg!("some", &p, acc, each_datum); + Ok(acc || p.clone().run(&each_datum.0)?) })? } }) @@ -1116,6 +1118,58 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_eq_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".not_from?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_dot_field_ending_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from.not?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_dot_field_inner_try_null() -> TestResult { + // FIXME double check against jq + let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_root_try_not_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".?").unwrap(), Ipld::Null.into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_try_not_null() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".from?").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_nested_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from?.not?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + #[test_log::test] fn test_eq_fail_same_type() -> TestResult { let p = Predicate::Equal(Select::from_str(".from").unwrap(), "NOPE".into()); @@ -1475,8 +1529,155 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_alternate_every_and_some() -> TestResult { + // ["every", ".a", ["some", ".b[]", ["==", ".", 0]]] + let p = Predicate::Every( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, but ok because "some" + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 0, 1] + } + ] + } + ); + + assert!(p.run(&nested_data)?); + Ok(()) + } + + #[test_log::test] + fn test_alternate_fail_every_and_some() -> TestResult { + // ["every", ".a", ["some", ".b[]", ["==", ".", 0]]] + let p = Predicate::Every( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, but ok because "some" + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 42, 1] // No 0, so fail "every" + } + ] + } + ); + + assert!(!p.run(&nested_data)?); + Ok(()) + } + + // FIXME + #[test_log::test] + fn test_alternate_some_and_every() -> TestResult { + // ["some", ".a", ["every", ".b[]", ["==", ".", 0]]] + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, so fail this every, but... + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [0, 0, 0] // This every succeeds, so the outer "some" succeeds + } + ] + } + ); + + assert!(p.run(&nested_data)?); + Ok(()) + } + + // FIXME + #[test_log::test] + fn test_alternate_fail_some_and_every() -> TestResult { + // ["some", ".a", ["every", ".b[]", ["==", ".", 0]]] + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 42, 1] // Also nope, so fail + } + ] + } + ); + + assert!(!p.run(&nested_data)?); + Ok(()) + } + #[test_log::test] fn test_deeply_alternate_some_and_every() -> TestResult { + // ["some", ".a", + // ["every", ".b.c[]", + // ["some", ".d", + // ["every", ".e[]", + // ["==", ".f.g", 0] + // ] + // ] + // ] + // ] let p = Predicate::Some( Select::from_str(".a").unwrap(), Box::new(Predicate::Every( @@ -1500,8 +1701,8 @@ mod tests { "a": [ { "b": { - // Every "c": { + // Every "c1": { // Some "d": [ @@ -1510,14 +1711,14 @@ mod tests { "e": { "e1": { "f": { - "g": 42 + "g": 0 }, "nope": -10 }, "e2": { "_": "not selected", "f": { - "g": 99 + "g": 0 }, } } @@ -1527,20 +1728,20 @@ mod tests { "c2": { // Some "*": "avoid", - "_": [ + "d": [ { // Every "e": { "e1": { "f": { - "g": 42 + "g": 0 }, "nope": -10 }, "e2": { "_": "not selected", "f": { - "g": 99 + "g": 0 }, } } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index c39476c2..28af8f2e 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -40,20 +40,6 @@ impl Selector { } } -pub fn parse(input: &str) -> IResult<&str, Selector> { - if input.starts_with("..") { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Count, - ))); - } - - let with_try_this = preceded(char('.'), preceded(many0(char('?')), many0(filter::parse))); - let p = map_res(with_try_this, |found| Ok::(Selector(found))); - - context("selector", p)(input) -} - impl fmt::Display for Selector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ops = self.0.iter(); @@ -86,8 +72,8 @@ impl FromStr for Selector { ))); } - if s.len() == 0 { - return Err(nom::Err::Error(ParseError::MissingStartingDot( + if s.starts_with("..") { + return Err(nom::Err::Error(ParseError::StartsWithDoubleDot( s.to_string(), ))); } @@ -95,14 +81,16 @@ impl FromStr for Selector { let working; let mut acc = vec![]; - if let Ok((more, found)) = filter::parse_dot_field(s) { + if let Ok((more, found)) = + nom::branch::alt((filter::parse_try_dot_field, filter::parse_dot_field))(s) + { working = more; acc.push(found); } else { working = &s[1..]; } - match many0(filter::parse)(working) { + match preceded(many0(char('?')), many0(filter::parse))(working) { Ok(("", ops)) => { let mut mut_ops = ops.clone(); acc.append(&mut mut_ops); diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index f1363f07..37f663d6 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -11,6 +11,9 @@ pub enum ParseError { #[error("missing starting dot: {0}")] MissingStartingDot(String), + + #[error("starts with double dot: {0}")] + StartsWithDoubleDot(String), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 6f704a7b..96343a5a 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -75,21 +75,29 @@ pub fn parse(input: &str) -> IResult<&str, Filter> { context("selector_op", p)(input) } -pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { - let p = alt((parse_values, parse_field, parse_array_index)); - context("non_try", p)(input) -} - pub fn parse_try(input: &str) -> IResult<&str, Filter> { let p = map_res( terminated(parse_non_try, many1(tag("?"))), - // FIXME old code: terminated(parse_non_try, tag("?")), |found: Filter| Ok::(Filter::Try(Box::new(found))), ); context("try", p)(input) } +pub fn parse_try_dot_field(input: &str) -> IResult<&str, Filter> { + let p = map_res( + terminated(parse_dot_field, many1(tag("?"))), + |found: Filter| Ok::(Filter::Try(Box::new(found))), + ); + + context("try", p)(input) +} + +pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { + let p = alt((parse_values, parse_field, parse_array_index)); + context("non_try", p)(input) +} + pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); @@ -244,8 +252,6 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { ("", found) => Ok(found), - // ("", found) => Ok(found), - // FIXME (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } @@ -493,15 +499,90 @@ mod tests { } #[test_log::test] - fn test_multiple_tries() -> TestResult { - let got = Filter::from_str(".foo???????????????????"); + fn test_parse_try() -> TestResult { + let got = parse(".foo?"); pretty::assert_eq!( got, + Ok(("", Filter::Try(Box::new(Filter::Field("foo".to_string()))))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + Filter::from_str(".foo???????????????????"), Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) ); Ok(()) } + #[test_log::test] + fn test_parse_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + parse(".foo???????????????????"), + Ok(("", Filter::Try(Box::new(Filter::Field("foo".to_string()))))) + ); + Ok(()) + } + + #[test_log::test] + fn test_parse_multiple_tries_after_dot_field_trailing() -> TestResult { + pretty::assert_eq!( + parse(".foo???????????????????abc"), + Ok(( + "abc", + Filter::Try(Box::new(Filter::Field("foo".to_string()))) + )) + ); + Ok(()) + } + + #[test_log::test] + fn test_parse_many0_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + nom::multi::many0(parse)(".foo???????????????????abc"), + Ok(( + "abc", + vec![Filter::Try(Box::new(Filter::Field("foo".to_string())))] + )) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_delim_field() -> TestResult { + pretty::assert_eq!( + Filter::from_str(r#"["foo"]???????"#), + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_delim_field_inner_questionmarks() -> TestResult { + let got = Filter::from_str(r#"["f?o"]???????"#); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("f?o".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_values() -> TestResult { + let got = Filter::from_str("[]???????"); + pretty::assert_eq!(got, Ok(Filter::Try(Box::new(Filter::Values)))); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_index() -> TestResult { + let got = Filter::from_str("[42]???????"); + pretty::assert_eq!(got, Ok(Filter::Try(Box::new(Filter::ArrayIndex(42))))); + Ok(()) + } + #[test_log::test] fn test_fails_bare_try() -> TestResult { let got = Filter::from_str("?"); From deee2bcf9f2acbfedc81c4a62e5e5d554f801442 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 15:55:40 -0700 Subject: [PATCH 228/234] WIP rebasing --- src/ability/ucan.rs | 1 + src/ability/ucan/assert.rs | 30 ++++++++++++++++++++++++++++++ src/delegation/payload.rs | 2 +- src/delegation/store/memory.rs | 2 +- src/invocation/agent.rs | 9 ++++++++- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/ability/ucan/assert.rs diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 0cf8e210..220034b6 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,4 +1,5 @@ //! Abilities for and about UCANs themselves +pub mod assert; pub mod batch; pub mod revoke; diff --git a/src/ability/ucan/assert.rs b/src/ability/ucan/assert.rs new file mode 100644 index 00000000..62946c23 --- /dev/null +++ b/src/ability/ucan/assert.rs @@ -0,0 +1,30 @@ +use crate::ability::command::Command; +use crate::task::Task; +use libipld_core::{cid::Cid, ipld::Ipld}; + +// Things that you can assert include content and receipts + +#[derive(Debug, PartialEq)] +pub struct Ran { + ran: Cid, + out: Box>, + fx: Vec, // FIXME may be more than "just" a task +} + +impl Command for Ran { + const COMMAND: &'static str = "/ucan/assert/ran"; + // const COMMAND: &'static str = "/ucan/ran";???? +} + +/////////////// +/////////////// +/////////////// + +#[derive(Debug, PartialEq)] +pub struct Claim { + claim: T, +} // Where Ipld: From + +impl Command for Claim { + const COMMAND: &'static str = "/ucan/assert/claim"; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2f2715ef..c54438ec 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -398,7 +398,7 @@ mod tests { use testresult::TestResult; proptest! { - // #![proptest_config(ProptestConfig::with_cases(200))] + #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] fn test_ipld_round_trip(payload in Payload::::arbitrary()) { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index c36a6e2d..a7bc74e6 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -254,7 +254,7 @@ where format!("{}/", delegation.payload.command) }; - if !corrected_delegation_command.starts_with(&corrected_target_command) { + if !corrected_target_command.starts_with(&corrected_delegation_command) { continue; } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 11838bde..62e49e87 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,6 +88,7 @@ where &self, audience: Option, subject: DID, + command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -98,7 +99,13 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain( + &self.did, + &Some(subject.clone()), + command.into(), + vec![], + now, + ) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From 93cce69d3e475269421e631ee46a0c24c0e79c23 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 15:58:52 -0700 Subject: [PATCH 229/234] Revert so that can push --- src/invocation/agent.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 62e49e87..808ddb60 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,7 +88,7 @@ where &self, audience: Option, subject: DID, - command: String, + // command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -99,13 +99,7 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain( - &self.did, - &Some(subject.clone()), - command.into(), - vec![], - now, - ) // FIXME + .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From 643b4662b8f5879963eafe2c9f3ce6410f980535 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 16:05:04 -0700 Subject: [PATCH 230/234] Correct the `FIXME` for command --- src/invocation/agent.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 808ddb60..5641a169 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,7 +88,6 @@ where &self, audience: Option, subject: DID, - // command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -99,7 +98,13 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain( + &self.did, + &Some(subject.clone()), + ability.to_command(), + vec![], + now, + ) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From faba286d43af0555af01d2c7a57bbf1115077bcc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 22 Mar 2024 14:04:22 -0700 Subject: [PATCH 231/234] Test delegation store --- src/delegation/agent.rs | 33 +- src/delegation/policy/predicate.rs | 32 +- src/delegation/store/memory.rs | 528 +++++++++++++++++++++++++++-- src/delegation/store/traits.rs | 42 ++- src/invocation/agent.rs | 83 ++--- 5 files changed, 601 insertions(+), 117 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index bfee4119..8165b0a9 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -20,11 +20,15 @@ use web_time::SystemTime; /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent< - S: Store, - DID: Did = did::preset::Verifier, - V: varsig::Header + Clone = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, -> { + S: Store, + DID: Did + Clone = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, +> where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ /// The [`Did`][Did] of the agent. pub did: DID, @@ -32,17 +36,18 @@ pub struct Agent< pub store: S, signer: ::Signer, - _marker: PhantomData<(V, Enc)>, + _marker: PhantomData<(V, C)>, } impl< - S: Store + Clone, + S: Store + Clone, DID: Did + Clone, - V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, - > Agent + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Agent where - Ipld: Encode, + Ipld: Encode, + Delegation: Encode, Payload: TryFrom>, Named: From>, { @@ -67,7 +72,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -121,7 +126,7 @@ where pub fn receive( &self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); @@ -135,7 +140,7 @@ where .validate_signature() .map_err(|_| ReceiveError::InvalidSignature(cid))?; - self.store.insert(cid, delegation).map_err(Into::into) + self.store.insert_keyed(cid, delegation).map_err(Into::into) } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 423d9303..12cb7eae 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -79,24 +79,20 @@ impl Predicate { Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Every(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(true, |acc, each_datum| { - dbg!("every", &p, acc, each_datum); - Ok(acc && p.clone().run(&each_datum.0)?) - })? - } - Predicate::Some(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(false, |acc, each_datum| { - dbg!("some", &p, acc, each_datum); - Ok(acc || p.clone().run(&each_datum.0)?) - })? - } + Predicate::Every(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(true, |acc, each_datum| { + Ok(acc && p.clone().run(&each_datum.0)?) + })?, + Predicate::Some(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(false, |acc, each_datum| { + Ok(acc || p.clone().run(&each_datum.0)?) + })?, }) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index a7bc74e6..d9dcb376 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -105,7 +105,7 @@ impl, C: Codec + TryFrom + Into bool { - self.read().ucans.is_empty() // FIXME acocunt for revocations? + self.read().ucans.is_empty() // FIXME account for revocations? } fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { @@ -173,7 +173,7 @@ where // FIXME } - fn insert( + fn insert_keyed( &self, cid: Cid, delegation: Delegation, @@ -203,7 +203,7 @@ where aud: &DID, subject: &Option, command: String, - policy: Vec, // FIXME + policy: Vec, now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError> { @@ -216,7 +216,8 @@ where let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); - let mut parent_candidate_stack = vec![]; + let mut parent_candidate_stack = + vec![sub_candidates.iter().chain(powerline_candidates.iter())]; let mut hypothesis_chain = vec![]; let corrected_target_command = if command.ends_with('/') { @@ -225,18 +226,23 @@ where format!("{}/", command) }; - parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); - let mut next = None; + // TODO Vec> + // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); + + // Pseudocode: + // If empty, pop: + // if pop fials, you're out of stuff + // If 'outer: loop { if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { if parent_cid_candidates.clone().collect::>().is_empty() { parent_candidate_stack.pop(); - hypothesis_chain.pop(); - break 'outer; + continue; } 'inner: for cid in parent_cid_candidates { + // CHECKS if read_tx.revocations.contains(cid) { continue; } @@ -258,17 +264,19 @@ where continue; } - for target_pred in policy.iter() { - for delegate_pred in delegation.payload.policy.iter() { - let comparison = - target_pred.harmonize(delegate_pred, vec![], vec![]); + // FIXME + // for target_pred in policy.iter() { + // for delegate_pred in delegation.payload.policy.iter() { + // let comparison = + // target_pred.harmonize(delegate_pred, vec![], vec![]); - if comparison.is_conflict() || comparison.is_lhs_weaker() { - continue 'inner; - } - } - } + // if comparison.is_conflict() || comparison.is_lhs_weaker() { + // continue 'inner; + // } + // } + // } + // PASSED CHECKS, so processing hypothesis_chain.push((cid.clone(), Arc::clone(delegation))); let issuer = delegation.issuer().clone(); @@ -281,34 +289,486 @@ where let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); - let new_powerline_candidates = - all_powerlines.get(&issuer).unwrap_or(&blank_set); - - if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { - next = Some( - new_aud_candidates - .iter() - .chain(new_powerline_candidates.iter()), + if !new_aud_candidates.is_empty() || !all_powerlines.get(&issuer).is_none() + { + parent_candidate_stack.push( + new_aud_candidates.iter().chain( + all_powerlines.get(&issuer).unwrap_or(&blank_set).iter(), + ), ); break 'inner; } } } - - if let Some(ref n) = next { - parent_candidate_stack.push(n.clone()); - next = None; - } else { - // Didn't find a match - break 'outer; - } } else { parent_candidate_stack.pop(); - hypothesis_chain.pop(); + break 'outer; } } Ok(NonEmpty::from_vec(hypothesis_chain)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::varsig::encoding; + use crate::crypto::varsig::header; + use crate::{crypto::signature::Envelope, delegation::store::Store}; + + use libipld_core::cid::Cid; + use nonempty::nonempty; + use pretty_assertions as pretty; + use rand::thread_rng; + use std::time::SystemTime; + use testresult::TestResult; + + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + + #[test_log::test] + fn test_get_fail() -> TestResult { + let store = crate::delegation::store::MemoryStore::default(); + store.get(&Cid::default())?; + pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); + Ok(()) + } + + #[test_log::test] + fn test_insert_get_roundtrip() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + + Ok(()) + } + + #[test_log::test] + fn test_insert_is_idempotent() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + pretty::assert_eq!(store.len(), 1); + + Ok(()) + } + + mod get_chain { + use super::*; + + #[test_log::test] + fn test_simple_fail() -> TestResult { + let (server, _server_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_with_one() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_one_with_others_in_store() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let noise = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/example".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(noise.clone())?; + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let more_noise = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(carol.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(more_noise.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_two() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = + store.get_chain(&carol, &Some(alice), "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_looking_for_narrower_command() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_broken_chain() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dan, dan_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(alice_to_bob.clone())?; + + let carol_to_dan = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(carol.clone()) + .audience(dan.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(carol_to_dan.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_long_chain() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + // + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _dave_signer) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &Some(alice), "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } + } +} diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 7413e700..d19b5d13 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,25 +1,39 @@ use crate::{ + ability::arguments::Named, + crypto::signature::Envelope, crypto::varsig, + delegation::payload::Payload, delegation::{policy::Predicate, Delegation}, did::Did, }; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{fmt::Debug, sync::Arc}; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store + Clone, C: Codec + TryFrom + Into> +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ type DelegationStoreError: Debug; fn get( &self, cid: &Cid, - ) -> Result>>, Self::DelegationStoreError>; + ) -> Result>>, Self::DelegationStoreError>; + + fn insert(&self, delegation: Delegation) -> Result<(), Self::DelegationStoreError> { + self.insert_keyed(delegation.cid().expect("FIXME"), delegation) + } - fn insert( + fn insert_keyed( &self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -34,7 +48,7 @@ pub trait Store, Enc: Codec + TryFrom + In command: String, policy: Vec, now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::DelegationStoreError>; fn get_chain_cids( &self, @@ -63,15 +77,23 @@ pub trait Store, Enc: Codec + TryFrom + In fn get_many( &self, cids: &[Cid], - ) -> Result>>>, Self::DelegationStoreError> { + ) -> Result>>>, Self::DelegationStoreError> { cids.iter() .map(|cid| self.get(cid)) .collect::>() } } -impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> - Store for &T +impl< + T: Store, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Store for &T +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, { type DelegationStoreError = >::DelegationStoreError; @@ -82,12 +104,12 @@ impl, DID: Did, V: varsig::Header, C: Codec + TryFrom, ) -> Result<(), Self::DelegationStoreError> { - (**self).insert(cid, delegation) + (**self).insert_keyed(cid, delegation) } fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5641a169..11c3de80 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,6 +6,7 @@ use super::{ use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::ability::parse::ParseAbility; +use crate::delegation::Delegation; use crate::invocation::payload::PayloadBuilder; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -23,12 +24,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt, - marker::PhantomData, - sync::Arc, -}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -37,10 +33,14 @@ pub struct Agent< S: Store, D: delegation::store::Store, T: ToCommand = ability::preset::Preset, - DID: Did = did::preset::Verifier, + DID: Did + Clone = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, -> { +> where + Delegation: Encode, + delegation::Payload: TryFrom>, + Named: From>, +{ /// The agent's [`DID`]. pub did: DID, @@ -68,6 +68,9 @@ where C: Codec + Into + TryFrom, >::InvocationStoreError: fmt::Debug, >::DelegationStoreError: fmt::Debug, + delegation::Payload: TryFrom>, + Named: From>, + Delegation: Encode, { pub fn new( did: DID, @@ -96,20 +99,21 @@ where now: SystemTime, varsig_header: V, ) -> Result, InvokeError> { - let proofs = self - .delegation_store - .get_chain( - &self.did, - &Some(subject.clone()), - ability.to_command(), - vec![], - now, - ) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]); - - let mut seed = vec![]; + let proofs = if subject == self.did { + vec![] + } else { + self.delegation_store + .get_chain( + &self.did, + &Some(subject.clone()), + ability.to_command(), + vec![], + now, + ) + .map_err(InvokeError::DelegationStoreError)? + .map(|chain| chain.map(|(cid, _)| cid).into()) + .unwrap_or(vec![]) // FIXME + }; let payload = Payload { issuer: self.did.clone(), @@ -118,7 +122,7 @@ where ability, proofs, metadata, - nonce: Nonce::generate_12(&mut seed), + nonce: Nonce::generate_12(&mut vec![]), cause, expiration, issued_at, @@ -441,7 +445,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -469,7 +473,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_and_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( Some(agent.did.clone()), @@ -498,7 +502,7 @@ mod tests { #[test_log::test] fn test_other_recipient() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let (not_server, _) = gen_did(); @@ -528,7 +532,7 @@ mod tests { #[test_log::test] fn test_expired() -> TestResult { let (past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -563,7 +567,7 @@ mod tests { #[test_log::test] fn test_invalid_sig() -> TestResult { let (_past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let server = &agent.did; let mut invocation = agent.invoke( @@ -686,13 +690,13 @@ mod tests { .build()?, )?; - drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); - drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); - drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); + del_store.insert(account_device_ucan.clone())?; + del_store.insert(account_pbox.clone())?; + del_store.insert(dnslink_ucan.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? - .ok_or("FIXME")? + .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); @@ -714,17 +718,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(vec![ - account_device_ucan.cid()?, - account_pbox.cid()?, - dnslink_ucan.cid()?, - ]) - // .proofs(proofs_for_powerline.clone()) + .proofs(proofs_for_powerline.clone()) .build()?, )?; let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink?.ok_or("FIXME")?.len(); + let dnslink_len = chain_for_dnslink? + .ok_or("failed while finding DNSLink delegtaions")? + .len(); Ok(Ctx { varsig_header, @@ -751,7 +752,7 @@ mod tests { fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, @@ -767,7 +768,7 @@ mod tests { fn test_chain_wrong_sub() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, From 246578b7b7f62921e930296d96b1e142e9225892 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 23 Mar 2024 15:45:16 -0700 Subject: [PATCH 232/234] WIP testing --- src/crypto/signature/envelope.rs | 4 +- src/delegation/agent.rs | 3 +- src/delegation/store/memory.rs | 125 +++++++++++++++++++++++++--- src/delegation/store/traits.rs | 4 +- src/invocation/agent.rs | 29 ++++--- src/invocation/store.rs | 135 +------------------------------ src/invocation/store/memory.rs | 83 +++++++++++++++++++ src/invocation/store/traits.rs | 51 ++++++++++++ 8 files changed, 279 insertions(+), 155 deletions(-) create mode 100644 src/invocation/store/memory.rs create mode 100644 src/invocation/store/traits.rs diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 0cea6c02..170b719d 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -186,11 +186,11 @@ pub trait Envelope: Sized { fn cid(&self) -> Result where - Self: Encode, + Ipld: Encode, { let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let mut ipld_buffer = vec![]; - self.encode(codec, &mut ipld_buffer)?; + self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 8165b0a9..16482c14 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -25,7 +25,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -47,7 +47,6 @@ impl< > Agent where Ipld: Encode, - Delegation: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index d9dcb376..9dcf04b3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -160,7 +160,7 @@ impl< where Named: From>, delegation::Payload: TryFrom>, - Delegation: Encode, + Ipld: Encode, { type DelegationStoreError = Infallible; @@ -235,19 +235,28 @@ where // If 'outer: loop { + dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { + dbg!("SOME INNER"); + for cid in parent_cid_candidates.clone() { + dbg!("INNER", cid.to_string()); + } if parent_cid_candidates.clone().collect::>().is_empty() { + dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { + dbg!("BBBBBBBBBBBBBBBBBBBBBB"); + dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { + dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -283,6 +292,7 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { + dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -336,7 +346,11 @@ mod tests { #[test_log::test] fn test_get_fail() -> TestResult { - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); store.get(&Cid::default())?; pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); Ok(()) @@ -415,7 +429,11 @@ mod tests { fn test_simple_fail() -> TestResult { let (server, _server_signer) = gen_did(); - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, None); @@ -635,9 +653,9 @@ mod tests { #[test_log::test] fn test_broken_chain() -> TestResult { let (alice, alice_signer) = gen_did(); - let (bob, bob_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dan, dan_signer) = gen_did(); + let (dan, _dan_signer) = gen_did(); let store = crate::delegation::store::MemoryStore::default(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( @@ -693,11 +711,10 @@ mod tests { // 1. bob -*-> carol // 2. carol -a-> dave // 3. alice -d-> bob - // let (alice, alice_signer) = gen_did(); let (bob, bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dave, _dave_signer) = gen_did(); + let (dave, _) = gen_did(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( crate::crypto::varsig::header::EdDsaHeader { @@ -723,9 +740,9 @@ mod tests { // 2. carol -a-> dave let carol_to_dave = crate::Delegation::try_sign( &carol_signer, - varsig_header.clone(), // FIXME can also put this on a builder + varsig_header.clone(), crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox + .subject(None) .issuer(carol.clone()) .audience(dave.clone()) .command("/".into()) @@ -770,5 +787,95 @@ mod tests { Ok(()) } + + #[test_log::test] + fn test_long_powerline() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + dbg!("THERE!!!!!!!!!!!!!!!!!"); + + for cid in &got { + dbg!(cid.to_string()); + } + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index d19b5d13..c917dc20 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -15,7 +15,7 @@ use web_time::SystemTime; pub trait Store + Clone, C: Codec + TryFrom + Into> where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -91,7 +91,7 @@ impl< C: Codec + TryFrom + Into, > Store for &T where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 11c3de80..ee7e44d1 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, delegation::Payload: TryFrom>, Named: From>, { @@ -70,7 +70,6 @@ where >::DelegationStoreError: fmt::Debug, delegation::Payload: TryFrom>, Named: From>, - Delegation: Encode, { pub fn new( did: DID, @@ -652,7 +651,7 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] // 1. account -*-> server - let account_pbox = crate::Delegation::try_sign( + let account_to_server = crate::Delegation::try_sign( &account_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -665,7 +664,7 @@ mod tests { )?; // 2. server -a-> device - let account_device_ucan = crate::Delegation::try_sign( + let server_to_device = crate::Delegation::try_sign( &server_signer, varsig_header.clone(), // FIXME can also put this on a builder crate::delegation::PayloadBuilder::default() @@ -678,7 +677,7 @@ mod tests { )?; // 3. dnslink -d-> account - let dnslink_ucan = crate::Delegation::try_sign( + let dnslink_to_account = crate::Delegation::try_sign( &dnslink_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -690,9 +689,9 @@ mod tests { .build()?, )?; - del_store.insert(account_device_ucan.clone())?; - del_store.insert(account_pbox.clone())?; - del_store.insert(dnslink_ucan.clone())?; + del_store.insert(account_to_server.clone())?; + del_store.insert(server_to_device.clone())?; + del_store.insert(dnslink_to_account.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? @@ -722,6 +721,18 @@ mod tests { .build()?, )?; + dbg!("==================="); + dbg!(proofs_for_powerline.len()); + dbg!(">>>>>>>>>>>>>>>>>."); + dbg!(account_to_server.cid()?.to_string()); + dbg!(server_to_device.cid()?.to_string()); + dbg!(dnslink_to_account.cid()?.to_string()); + + dbg!("<<<<<<<<<<<<<<<<<<"); + for prf_cid in &proofs_for_powerline { + dbg!(prf_cid.to_string()); + } + let powerline_len = proofs_for_powerline.len(); let dnslink_len = chain_for_dnslink? .ok_or("failed while finding DNSLink delegtaions")? @@ -760,7 +771,7 @@ mod tests { ); let observed = agent.receive(ctx.account_invocation.clone()); - assert!(observed.is_ok()); + assert_matches!(observed, Ok(Recipient::You(_))); Ok(()) } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 4c9279fa..224e0c77 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,134 +1,7 @@ //! Storage for [`Invocation`]s. -use super::Invocation; -use crate::ability; -use crate::{crypto::varsig, did::Did}; -use libipld_core::{cid::Cid, codec::Codec}; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::{collections::BTreeMap, convert::Infallible}; +mod memory; +mod traits; -pub trait Store, C: Codec + Into + TryFrom> { - type InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError>; - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError>; - - fn has(&self, cid: Cid) -> Result { - Ok(self.get(cid).is_ok()) - } -} - -impl< - S: Store, - T, - DID: Did, - V: varsig::Header, - C: Codec + Into + TryFrom, - > Store for &S -{ - type InvocationStoreError = >::InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result< - Option>>, - >::InvocationStoreError, - > { - (**self).get(cid) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), >::InvocationStoreError> { - (**self).put(cid, invocation) - } -} - -#[derive(Debug, Clone)] -pub struct MemoryStore< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - inner: Arc>>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStoreInner< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - store: BTreeMap>>, -} - -impl, Enc: Codec + Into + TryFrom> - MemoryStore -{ - fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { - match self.inner.read() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } - - fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { - match self.inner.write() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } -} - -impl, Enc: Codec + Into + TryFrom> Default - for MemoryStore -{ - fn default() -> Self { - Self { - inner: Arc::new(RwLock::new(MemoryStoreInner { - store: BTreeMap::new(), - })), - } - } -} - -impl, Enc: Codec + Into + TryFrom> - Store for MemoryStore -{ - type InvocationStoreError = Infallible; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError> { - Ok(self.read().store.get(&cid).cloned()) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError> { - self.write().store.insert(cid, Arc::new(invocation)); - Ok(()) - } -} +pub use memory::{MemoryStore, MemoryStoreInner}; +pub use traits::Store; diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs new file mode 100644 index 00000000..2ae9a360 --- /dev/null +++ b/src/invocation/store/memory.rs @@ -0,0 +1,83 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use super::Store; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{collections::BTreeMap, convert::Infallible}; + +#[derive(Debug, Clone)] +pub struct MemoryStore< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStoreInner< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>>, +} + +impl, Enc: Codec + Into + TryFrom> + MemoryStore +{ + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + inner: Arc::new(RwLock::new(MemoryStoreInner { + store: BTreeMap::new(), + })), + } + } +} + +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore +{ + type InvocationStoreError = Infallible; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError> { + Ok(self.read().store.get(&cid).cloned()) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError> { + self.write().store.insert(cid, Arc::new(invocation)); + Ok(()) + } +} diff --git a/src/invocation/store/traits.rs b/src/invocation/store/traits.rs new file mode 100644 index 00000000..6d3fc723 --- /dev/null +++ b/src/invocation/store/traits.rs @@ -0,0 +1,51 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::Arc; + +pub trait Store, C: Codec + Into + TryFrom> { + type InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError>; + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError>; + + fn has(&self, cid: Cid) -> Result { + Ok(self.get(cid).is_ok()) + } +} + +impl< + S: Store, + T, + DID: Did, + V: varsig::Header, + C: Codec + Into + TryFrom, + > Store for &S +{ + type InvocationStoreError = >::InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result< + Option>>, + >::InvocationStoreError, + > { + (**self).get(cid) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), >::InvocationStoreError> { + (**self).put(cid, invocation) + } +} From 279f17c67cd61bc2219fc67e8b63adb9f4e354cb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 24 Mar 2024 21:01:25 -0700 Subject: [PATCH 233/234] Save WIP writng more tests --- src/delegation/store/memory.rs | 33 ++++++------------------ src/invocation/agent.rs | 46 ++++++++-------------------------- 2 files changed, 18 insertions(+), 61 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9dcf04b3..a0cd4ec1 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -198,6 +198,7 @@ where Ok(()) } + // FIXME take a PayloadBuilder fn get_chain( &self, aud: &DID, @@ -226,37 +227,20 @@ where format!("{}/", command) }; - // TODO Vec> - // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); - - // Pseudocode: - // If empty, pop: - // if pop fials, you're out of stuff - // If - 'outer: loop { - dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { - dbg!("SOME INNER"); - for cid in parent_cid_candidates.clone() { - dbg!("INNER", cid.to_string()); - } if parent_cid_candidates.clone().collect::>().is_empty() { - dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { - dbg!("BBBBBBBBBBBBBBBBBBBBBB"); - dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { - dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -292,7 +276,6 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { - dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -852,7 +835,13 @@ mod tests { store.insert(alice_to_bob.clone())?; let got: Vec = store - .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .get_chain( + &dave, + &Some(alice.clone()), + "/".into(), + vec![], + SystemTime::now(), + ) .map_err(|e| e.to_string())? .ok_or("failed during proof lookup")? .iter() @@ -860,12 +849,6 @@ mod tests { .cloned() .collect(); - dbg!("THERE!!!!!!!!!!!!!!!!!"); - - for cid in &got { - dbg!(cid.to_string()); - } - pretty::assert_eq!( got, vec![ diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ee7e44d1..158f49c2 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -613,7 +613,6 @@ mod tests { struct Ctx { varsig_header: crate::crypto::varsig::header::Preset, - powerline_len: usize, dnslink_len: usize, inv_store: crate::invocation::store::MemoryStore, del_store: crate::delegation::store::MemoryStore, @@ -693,21 +692,19 @@ mod tests { del_store.insert(server_to_device.clone())?; del_store.insert(dnslink_to_account.clone())?; - let proofs_for_powerline: Vec = del_store - .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + let chain_for_dnslink: Vec = del_store + .get_chain( + &device, + &Some(dnslink.clone()), + "/".into(), + vec![], + SystemTime::now(), + )? .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); - let chain_for_dnslink = del_store.get_chain( - &device, - &Some(dnslink.clone()), - "/".into(), - vec![], - SystemTime::now(), - ); - // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, @@ -717,30 +714,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(proofs_for_powerline.clone()) + .proofs(chain_for_dnslink.clone()) .build()?, )?; - dbg!("==================="); - dbg!(proofs_for_powerline.len()); - dbg!(">>>>>>>>>>>>>>>>>."); - dbg!(account_to_server.cid()?.to_string()); - dbg!(server_to_device.cid()?.to_string()); - dbg!(dnslink_to_account.cid()?.to_string()); - - dbg!("<<<<<<<<<<<<<<<<<<"); - for prf_cid in &proofs_for_powerline { - dbg!(prf_cid.to_string()); - } - - let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink? - .ok_or("failed while finding DNSLink delegtaions")? - .len(); + let dnslink_len = chain_for_dnslink.len(); Ok(Ctx { varsig_header, - powerline_len, dnslink_len, inv_store, del_store, @@ -752,13 +733,6 @@ mod tests { }) } - #[test_log::test] - fn test_chain_len() -> TestResult { - let ctx = setup_test_chain()?; - assert_eq!((ctx.powerline_len, ctx.dnslink_len), (3, 3)); - Ok(()) - } - #[test_log::test] fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; From 6affc01c5dd5ce5323c84f40a3d1dd7cc3c4f131 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 15:41:35 -0700 Subject: [PATCH 234/234] Not pretty, but got that last selector passing --- .../delegation/policy/selector/select.txt | 7 + src/delegation/policy/predicate.rs | 1 - src/delegation/policy/selector.rs | 12 ++ src/delegation/policy/selector/select.rs | 152 ++++++++++++++---- 4 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector/select.txt diff --git a/proptest-regressions/delegation/policy/selector/select.txt b/proptest-regressions/delegation/policy/selector/select.txt new file mode 100644 index 00000000..716e8c0c --- /dev/null +++ b/proptest-regressions/delegation/policy/selector/select.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 6496f8ae07f0fa0d57c9bc4d581551bc9940c50fe830880006156471a72e806b # shrinks to data = Newtype(null), more = [ArrayIndex(0)] diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 12cb7eae..30c9c0dd 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1132,7 +1132,6 @@ mod tests { #[test_log::test] fn test_eq_dot_field_inner_try_null() -> TestResult { - // FIXME double check against jq let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into()); assert!(p.run(&email())?); diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 28af8f2e..acba9a2c 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -200,6 +200,18 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_inner_try_is_null() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".nope?.not"), + Ok(Selector(vec![ + Filter::Try(Box::new(Filter::Field("nope".into()))), + Filter::Field("not".into()) + ])) + ); + Ok(()) + } + #[test_log::test] fn test_dot_many_tries_and_dot_field() -> TestResult { pretty::assert_eq!( diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 8be9fb08..7506cc63 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -46,9 +46,9 @@ impl Select { impl Select { pub fn get(self, ctx: &Ipld) -> Result { - self.filters - .iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + let got = self.filters.iter().try_fold( + (ctx.clone(), vec![], false), + |(ipld, mut seen_ops, is_try), op| { seen_ops.push(op); match op { @@ -57,70 +57,95 @@ impl Select { let ipld: Ipld = Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); - Ok((ipld, seen_ops)) + Ok((ipld, seen_ops.clone(), true)) } Filter::ArrayIndex(i) => { let result = { match ipld { Ipld::List(xs) => { if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + return Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )); }; xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )) .cloned() } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAList, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + ), )), } }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Field(k) => { let result = match ipld { Ipld::Map(xs) => xs .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + ), )) .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, + _ => Err(( + is_try, + SelectorError::from_refs(&seen_ops, SelectorErrorReason::NotAMap), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Values => { let result = match ipld { Ipld::List(xs) => Ok(Ipld::List(xs)), Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + ), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) + }, + ); + + let (ipld, path) = match got { + Ok((ipld, seen_ops, _)) => Ok((ipld, seen_ops)), + Err((is_try, ref e @ SelectorError { ref selector, .. })) => { + if is_try { + Ok((Ipld::Null, selector.0.iter().map(|x| x).collect::>())) + } else { + Err(e.clone()) + } + } + }?; + + T::try_select(ipld).map_err(|e| SelectorError::from_refs(&path, e)) } } @@ -166,3 +191,70 @@ impl Arbitrary for Select { .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ipld; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod get { + use super::*; + + fn nested_data() -> Ipld { + Ipld::Map( + vec![ + ("name".to_string(), Ipld::String("Alice".to_string())), + ("age".to_string(), Ipld::Integer(42)), + ( + "friends".to_string(), + Ipld::List(vec![ + Ipld::String("Bob".to_string()), + Ipld::String("Charlie".to_string()), + ]), + ), + ] + .into_iter() + .collect(), + ) + } + + proptest! { + #[test_log::test] + fn test_identity(data: ipld::Newtype) { + let selector = Select::::from_str(".")?; + prop_assert_eq!(selector.get(&data.0)?, data); + } + + #[test_log::test] + fn test_try_missing_is_null(data: ipld::Newtype) { + let selector = Select::::from_str(".foo?")?; + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + + #[test_log::test] + fn test_try_missing_plus_trailing_is_null(data: ipld::Newtype, more: Vec) { + let mut filters = vec![Filter::Try(Box::new(Filter::Field("foo".into())))]; + filters.append(&mut more.clone()); + + let selector: Select = Select::new(filters); + + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + } + } +}

>::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, diff --git a/src/invocation/store.rs b/src/invocation/store.rs index d9ef104d..e7e3ad50 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -5,7 +5,7 @@ use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store, Enc: Codec + Into + TryFrom> { +pub trait Store, Enc: Codec + Into + TryFrom> { type InvocationStoreError; fn get( @@ -25,11 +25,11 @@ pub trait Store, Enc: Codec + Into + Tr } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, Enc: Codec + Into + TryFrom> { +pub struct MemoryStore, Enc: Codec + Into + TryFrom> { store: BTreeMap>, } -impl, Enc: Codec + Into + TryFrom> +impl, Enc: Codec + Into + TryFrom> Store for MemoryStore { type InvocationStoreError = Infallible; diff --git a/src/receipt.rs b/src/receipt.rs index 087bd013..1c1feb79 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -27,7 +27,7 @@ pub struct Receipt< T: Responds, DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + Into + TryFrom = varsig::encoding::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, > { pub varsig_header: V, pub signature: DID::Signature, @@ -36,7 +36,7 @@ pub struct Receipt< _marker: std::marker::PhantomData, } -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> did::Verifiable for Receipt { fn verifier(&self) -> &DID { @@ -48,7 +48,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > From> for Ipld where Payload: TryFrom, @@ -62,7 +62,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Envelope for Receipt where Payload: TryFrom, @@ -106,7 +106,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Serialize for Receipt where Payload: TryFrom, @@ -124,7 +124,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Deserialize<'de> for Receipt where Payload: TryFrom, diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs index 11e84731..6be098fa 100644 --- a/src/receipt/store/memory.rs +++ b/src/receipt/store/memory.rs @@ -14,14 +14,14 @@ pub struct MemoryStore< T: Responds, DID: Did, V: varsig::Header, - Enc: Codec + Into + TryFrom, + Enc: Codec + Into + TryFrom, > where T::Success: fmt::Debug + Clone + PartialEq, { store: BTreeMap>, } -impl, Enc: Codec + Into + TryFrom> +impl, Enc: Codec + Into + TryFrom> Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs index 40a141d4..8d53a1f0 100644 --- a/src/receipt/store/traits.rs +++ b/src/receipt/store/traits.rs @@ -7,7 +7,7 @@ use crate::{ use libipld_core::{codec::Codec, ipld::Ipld}; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store, C: Codec + Into + TryFrom> { +pub trait Store, C: Codec + Into + TryFrom> { /// The error type representing all the ways a store operation can fail. type Error; From 842bf28e1642c01d0007ba6218e8eca9e2a6ed89 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 13:26:59 -0800 Subject: [PATCH 186/234] Builders --- Cargo.toml | 1 + src/delegation/payload.rs | 28 ++++++++-------------------- src/delegation/store/memory.rs | 8 ++++++-- src/invocation/payload.rs | 25 ++++++++++++++++++++++--- src/receipt.rs | 2 +- src/receipt/payload.rs | 8 +++++++- src/time/timestamp.rs | 5 +++++ 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 397845d4..3aa30508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-fea ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } # Code Convenience +derive_builder = "0.20" enum-as-inner = "0.6" getrandom = { version = "0.2", features = ["js", "rdrand"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7a544e26..50d9107b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,11 +1,12 @@ use super::policy::Predicate; use crate::{ capsule::Capsule, - crypto::Nonce, + crypto::{varsig, Nonce}, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use derive_builder::Builder; +use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -20,7 +21,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -49,15 +50,18 @@ pub struct Payload { pub command: String, /// Any [`Predicate`] policies that constrain the `args` on an [`Invocation`][crate::invocation::Invocation]. + #[builder(default)] pub policy: Vec, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid ; + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// The latest wall-clock time that the UCAN is valid until, @@ -70,27 +74,11 @@ pub struct Payload { /// given as a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + #[builder(default)] pub not_before: Option, } impl Payload { - pub fn powerbox(issuer: DID, audience: DID, command: String, expiration: Timestamp) -> Self { - let mut seed = vec![]; - let nonce = Nonce::generate_12(seed.as_mut()); - - Payload { - issuer, - subject: None, - audience, - command, - policy: vec![], - metadata: BTreeMap::new(), - nonce, - expiration, - not_before: None, - } - } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index b603fb5c..9d96a60d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -2,7 +2,7 @@ use super::Store; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, - did::Did, + did::{self, Did}, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -69,7 +69,11 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, C: Codec + TryFrom + Into> { +pub struct MemoryStore< + DID: Did + Ord, // = did::preset::Verifier, + V: varsig::Header, + C: Codec + TryFrom + Into, +> { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f64a64c9..f93ed9af 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,7 +2,7 @@ use super::promise::Resolvable; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, - crypto::Nonce, + crypto::{varsig, Nonce}, delegation::{ self, policy::{selector::SelectorError, Predicate}, @@ -10,7 +10,8 @@ use crate::{ did::{Did, Verifiable}, time::{Expired, Timestamp}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use derive_builder::Builder; +use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; use serde::{ de::{self, MapAccess, Visitor}, ser::SerializeStruct, @@ -29,7 +30,7 @@ use crate::ipld; #[cfg(feature = "test_utils")] use crate::ipld::cid; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Builder)] pub struct Payload { /// The subject of the [`Invocation`]. /// @@ -58,6 +59,7 @@ pub struct Payload { /// /// Note that if this is the same as the [`subject`], /// this field may be omitted. + #[builder(default)] pub audience: Option, /// The [Ability] being invoked. @@ -75,6 +77,7 @@ pub struct Payload { /// /// [`Invocation`]: super::Invocation /// [`Delegation`]: crate::delegation::Delegation + #[builder(default)] pub proofs: Vec, /// An optional [`Cid`] of the [`Receipt`] that requested this be invoked. @@ -82,14 +85,17 @@ pub struct Payload { /// This is helpful for provenance of calls. /// /// [`Receipt`]: crate::receipt::Receipt + #[builder(default)] pub cause: Option, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] @@ -101,6 +107,7 @@ pub struct Payload { /// /// One way of thinking about this is as a `timeout`. It also guards against /// certain types of denial-of-service attacks. + #[builder(default = "Some(Timestamp::five_minutes_from_now())")] pub expiration: Option, } @@ -446,6 +453,18 @@ impl Verifiable for Payload { } } +// impl, DID: Did> TryFrom for Payload { +// type Error = (); +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(btree) = ipld { +// let payload = btree.get(A::COMMAND).map_err(|_| ())?; +// } else { +// Err(()) +// } +// } +// } + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise diff --git a/src/receipt.rs b/src/receipt.rs index 1c1feb79..c96a7cf9 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -10,7 +10,7 @@ mod responds; pub mod store; -pub use payload::Payload; +pub use payload::*; pub use responds::Responds; pub use store::Store; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ace1e4d5..4bc40a96 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -10,6 +10,7 @@ use crate::{ did::{Did, Verifiable}, time::Timestamp, }; +use derive_builder::Builder; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::{self, MapAccess, Visitor}, @@ -36,7 +37,7 @@ impl Verifiable for Payload { /// The payload (non-signature) portion of a response from an [`Invocation`]. /// /// [`Invocation`]: crate::invocation::Invocation -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Builder)] pub struct Payload { /// The issuer of the [`Receipt`]. This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. @@ -59,6 +60,7 @@ pub struct Payload { /// requested to be queued next. /// /// [`Invocation`]: crate::invocation::Invocation + #[builder(default)] pub next: Vec, /// An optional proof chain authorizing a different [`Did`] to @@ -66,21 +68,25 @@ pub struct Payload { /// [`Invocation`] that was run. /// /// [`Invocation`]: crate::invocation::Invocation + #[builder(default)] pub proofs: Vec, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which the /// receipt claims to have been issued at. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + #[builder(default)] pub issued_at: Option, } diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 8b4cfea7..a378cf83 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -57,6 +57,11 @@ impl Timestamp { .expect("the current time to be somtime in the 3rd millenium CE") } + pub fn five_minutes_from_now() -> Timestamp { + Self::new(SystemTime::now() + Duration::from_secs(5 * 60)) + .expect("the current time to be somtime in the 3rd millenium CE") + } + pub fn five_years_from_now() -> Timestamp { Self::new(SystemTime::now() + Duration::from_secs(5 * 365 * 24 * 60 * 60)) .expect("the current time to be somtime in the 3rd millenium CE") From 2cb8ef87247a9f7138d70c4bcfd64553d8b7abc0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:11:01 -0800 Subject: [PATCH 187/234] lol oops --- src/crypto/signature/envelope.rs | 2 +- src/invocation/payload.rs | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 13d2b7a4..e8bff36a 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -100,7 +100,7 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, + Ipld: Encode + From, // FIXME force it to be named args not IPLD { Self::try_sign_generic(signer, varsig_header, payload) } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f93ed9af..596462e1 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -453,17 +453,20 @@ impl Verifiable for Payload { } } -// impl, DID: Did> TryFrom for Payload { -// type Error = (); -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(btree) = ipld { -// let payload = btree.get(A::COMMAND).map_err(|_| ())?; -// } else { -// Err(()) -// } -// } -// } +use crate::ability::command::Command; + +impl + Command, DID: Did> TryFrom for Payload { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(btree) = ipld { + let payload_ipld = btree.get(A::COMMAND).ok_or(|_| ())?; + payload_ipld.clone().try_into().map_err(|_| ()) + } else { + Err(()) + } + } +} /// A variant that accepts [`Promise`]s. /// From 51c3b0276ad926528f10e7fb996fd4fb1f6cb4cc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:12:37 -0800 Subject: [PATCH 188/234] oops again! --- src/invocation/payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 596462e1..a558b00b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -460,7 +460,7 @@ impl + Command, DID: Did> TryFrom for Payload { fn try_from(ipld: Ipld) -> Result { if let Ipld::Map(btree) = ipld { - let payload_ipld = btree.get(A::COMMAND).ok_or(|_| ())?; + let payload_ipld = btree.get(A::COMMAND).ok_or(())?; payload_ipld.clone().try_into().map_err(|_| ()) } else { Err(()) From ec1b2b6354b1b6549cb2631a1e9aac1ec8286d64 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:31:32 -0800 Subject: [PATCH 189/234] PartialOrd --- src/did/key/verifier.rs | 30 +++++++++++++++++++++++------- src/did/preset.rs | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 20b021eb..0a1be8d8 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -1,12 +1,12 @@ use super::Signature; +use blst::BLST_ERROR; +use did_url::DID; use enum_as_inner::EnumAsInner; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; +use serde::{Deserialize, Serialize}; +use signature as sig; use std::{fmt::Display, str::FromStr}; -use serde::{Serialize, Deserialize}; use thiserror::Error; -use blst::BLST_ERROR; -use signature as sig; -use did_url::DID; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -75,6 +75,18 @@ pub enum Verifier { BlsMinSig(blst::min_sig::PublicKey), } +impl PartialOrd for Verifier { + fn partial_cmp(&self, other: &Self) -> Option { + self.to_string().partial_cmp(&other.to_string()) + } +} + +impl Ord for Verifier { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.to_string().cmp(&other.to_string()) + } +} + impl signature::Verifier for Verifier { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { match (self, signature) { @@ -256,13 +268,17 @@ impl FromStr for Verifier { word => return Err(FromStrError::UnexpectedPrefix([word].into())), }, (word, _) => { - return Err(FromStrError::UnexpectedPrefix(word.iter().map(|u| u.clone().into()).collect())); + return Err(FromStrError::UnexpectedPrefix( + word.iter().map(|u| u.clone().into()).collect(), + )); } } - }, + } (s, _) => { - return Err(FromStrError::UnexpectedPrefix(s.to_string().chars().map(|u| u as usize).collect())); + return Err(FromStrError::UnexpectedPrefix( + s.to_string().chars().map(|u| u as usize).collect(), + )); } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index aff0d261..cdc52528 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; /// The set of [`Did`] types that ship with this library ("presets"). -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, EnumAsInner, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Verifier { /// `did:key` DIDs. From 33a0ac3dcdf97d200143d457580ad8305eeba727 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:32:51 -0800 Subject: [PATCH 190/234] Defaults --- src/delegation/store/memory.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9d96a60d..e54a8ee3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -70,9 +70,9 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - DID: Did + Ord, // = did::preset::Verifier, - V: varsig::Header, - C: Codec + TryFrom + Into, + DID: did::Did + Ord = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, From cfe55fde5fb59c82a540aa1ad00a84e6ca71ba90 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:43:38 -0800 Subject: [PATCH 191/234] this is such test code lol --- src/crypto/varsig/encoding/preset.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index a4189ba3..b12070d0 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -1,4 +1,6 @@ use libipld_core::codec::Codec; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] @@ -11,6 +13,23 @@ pub enum Preset { Eip191 = 0xe191, } +impl Encode for Ipld { + fn encode( + &self, + c: Preset, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + match c { + Preset::Identity => todo!(), + Preset::DagPb => todo!(), + Preset::DagCbor => self.encode(libipld_cbor::DagCborCodec, w), + Preset::DagJson => todo!(), + Preset::Jwt => todo!(), + Preset::Eip191 => todo!(), + } + } +} + impl TryFrom for Preset { type Error = libipld_core::error::UnsupportedCodec; From 8ecd176abdd90ba32aff859cce3615cdfc1a44e3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:54:17 -0800 Subject: [PATCH 192/234] Give delegation an encode instance --- src/crypto/varsig/encoding/preset.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index b12070d0..e69a5a87 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -1,3 +1,5 @@ +use crate::crypto::signature::Envelope; +use crate::delegation::Delegation; use libipld_core::codec::Codec; use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; @@ -30,6 +32,16 @@ impl Encode for Ipld { } } +impl Encode for Delegation { + fn encode( + &self, + c: Preset, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + self.clone().to_ipld_envelope().encode(c, w) + } +} + impl TryFrom for Preset { type Error = libipld_core::error::UnsupportedCodec; From 326338cf818ff2d727323048fd0530b84b38d849 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:57:48 -0800 Subject: [PATCH 193/234] Helper functions on memstore --- src/delegation/store/memory.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e54a8ee3..d2eb1ce3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -79,6 +79,16 @@ pub struct MemoryStore< revocations: BTreeSet, } +impl MemoryStore { + pub fn new() -> Self { + Self::default() + } + + fn is_empty(&self) -> bool { + self.ucans.is_empty() // FIXME acocunt for revocations? + } +} + impl, C: Codec + TryFrom + Into> Default for MemoryStore { From d5cfbac4deedf1b4101a85392cce5a07ec7f1384 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:58:20 -0800 Subject: [PATCH 194/234] make method public --- src/delegation/store/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index d2eb1ce3..7a3abb65 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -84,7 +84,7 @@ impl MemoryStore { Self::default() } - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.ucans.is_empty() // FIXME acocunt for revocations? } } From 82130915623bb5ed448c9ef74465b477e58d2734 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:05:35 -0800 Subject: [PATCH 195/234] printable error --- src/delegation/store/memory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 7a3abb65..9ef9b8f9 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -105,10 +105,10 @@ impl, C: Codec + TryFrom + Into> impl, Enc: Codec + TryFrom + Into> Store for MemoryStore { - type DelegationStoreError = (); // FIXME misisng + type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or(()) + self.ucans.get(cid).ok_or("nope".into()) } fn insert( From 6e746ad2be1314cd182f9cb8efc487346cd2be25 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:18:20 -0800 Subject: [PATCH 196/234] Hopefully this eliminates the stack overflow? --- src/crypto/signature/envelope.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index e8bff36a..e7971871 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -134,9 +134,8 @@ pub trait Envelope: Sized { ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = signer - .try_sign(&buffer) - .map_err(SignError::SignatureError)?; + let signature = + signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; Ok(Self::construct(varsig_header, signature, payload)) } From 39939f468a77d675d53feb2d872374478a45733e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:20:21 -0800 Subject: [PATCH 197/234] remote debugging... gross --- src/crypto/signature/envelope.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index e7971871..9d78eda1 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -134,8 +134,8 @@ pub trait Envelope: Sized { ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = - signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + let signature = todo!(); + // signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; Ok(Self::construct(varsig_header, signature, payload)) } From d0d9055bc4b4159538cd04811912348ee369e368 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:23:52 -0800 Subject: [PATCH 198/234] good ol' printline debugging --- src/crypto/signature/envelope.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 9d78eda1..3eb20595 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -102,6 +102,7 @@ pub trait Envelope: Sized { where Ipld: Encode + From, // FIXME force it to be named args not IPLD { + dbg!("try_sign"); Self::try_sign_generic(signer, varsig_header, payload) } @@ -127,16 +128,21 @@ pub trait Envelope: Sized { where Ipld: Encode + From, { + dbg!("try_sign_generic"); let ipld: Ipld = BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); + dbg!("buffer"); let mut buffer = vec![]; + dbg!("encode"); ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = todo!(); - // signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + dbg!("sign"); + let signature = + signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + dbg!("construct"); Ok(Self::construct(varsig_header, signature, payload)) } From f50445dc77f8f5ab7defbdad759aee9d5147373e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:52:01 -0800 Subject: [PATCH 199/234] Serialize to IPLD --- src/crypto/signature/envelope.rs | 2 +- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 30 +++++++++++++- src/delegation/policy/predicate.rs | 53 ++++++++++++++++++++++++ src/delegation/policy/selector/select.rs | 13 ++++++ src/did/traits.rs | 4 +- src/ipld/collection.rs | 14 +++++++ 7 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 3eb20595..cb19990c 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -130,7 +130,7 @@ pub trait Envelope: Sized { { dbg!("try_sign_generic"); let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); dbg!("buffer"); let mut buffer = vec![]; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index abe27a34..139e8e41 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -36,7 +36,7 @@ pub struct Agent< impl< 'a, - DID: Did + ToString + Clone, + DID: Did + Clone, S: Store + Clone, V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 50d9107b..f830ec23 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -114,9 +114,35 @@ impl Deserialize<'de>> TryFrom for Payload { } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { - payload.into() + let mut btree: BTreeMap = BTreeMap::::from_iter([ + ("iss".to_string(), Ipld::from(payload.issuer.to_string())), + ("aud".to_string(), payload.audience.to_string().into()), + ("cmd".to_string(), payload.command.into()), + ( + "pol".to_string(), + Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()), + ), + ("nonce".to_string(), payload.nonce.into()), + ("exp".to_string(), payload.expiration.into()), + ]); + + if let Some(subject) = payload.subject { + btree.insert("sub".to_string(), Ipld::from(subject.to_string())); + } else { + btree.insert("sub".to_string(), Ipld::Null); + } + + if let Some(not_before) = payload.not_before { + btree.insert("nbf".to_string(), Ipld::from(not_before)); + } + + if !payload.metadata.is_empty() { + btree.insert("metadata".to_string(), Ipld::Map(payload.metadata)); + } + + Ipld::from(btree) } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 6f9533e6..01617003 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -95,6 +95,59 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } +impl From for Ipld { + fn from(p: Predicate) -> Self { + match p { + Predicate::True => Ipld::Bool(true), + Predicate::False => Ipld::Bool(false), + Predicate::Equal(lhs, rhs) => { + Ipld::List(vec![Ipld::String("==".to_string()), lhs.into(), rhs.into()]) + } + Predicate::GreaterThan(lhs, rhs) => { + Ipld::List(vec![Ipld::String(">".to_string()), lhs.into(), rhs.into()]) + } + Predicate::GreaterThanOrEqual(lhs, rhs) => { + Ipld::List(vec![Ipld::String(">=".to_string()), lhs.into(), rhs.into()]) + } + Predicate::LessThan(lhs, rhs) => { + Ipld::List(vec![Ipld::String("<".to_string()), lhs.into(), rhs.into()]) + } + Predicate::LessThanOrEqual(lhs, rhs) => { + Ipld::List(vec![Ipld::String("<=".to_string()), lhs.into(), rhs.into()]) + } + Predicate::Like(lhs, rhs) => Ipld::List(vec![ + Ipld::String("like".to_string()), + lhs.into(), + rhs.into(), + ]), + Predicate::Not(inner) => { + let unboxed = *inner; + Ipld::List(vec![Ipld::String("not".to_string()), unboxed.into()]) + } + Predicate::And(lhs, rhs) => Ipld::List(vec![ + Ipld::String("and".to_string()), + (*lhs).into(), + (*rhs).into(), + ]), + Predicate::Or(lhs, rhs) => Ipld::List(vec![ + Ipld::String("or".to_string()), + (*lhs).into(), + (*rhs).into(), + ]), + Predicate::Every(xs, p) => Ipld::List(vec![ + Ipld::String("every".to_string()), + xs.into(), + (*p).into(), + ]), + Predicate::Some(xs, p) => Ipld::List(vec![ + Ipld::String("some".to_string()), + xs.into(), + (*p).into(), + ]), + } + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Predicate { type Parameters = (); // FIXME? diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index e6064911..736bff0a 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -1,3 +1,4 @@ +use super::Selector; // FIXME cycle? use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -11,6 +12,18 @@ pub enum Select { Pure(T), } +impl From> for Ipld +where + Ipld: From, +{ + fn from(s: Select) -> Self { + match s { + Select::Get(ops) => Selector(ops).to_string().into(), + Select::Pure(inner) => inner.into(), + } + } +} + impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { diff --git a/src/did/traits.rs b/src/did/traits.rs index 448186be..03ed7659 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,7 +1,9 @@ use did_url::DID; use std::fmt; -pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { +pub trait Did: + PartialEq + ToString + TryFrom + Into + signature::Verifier +{ type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/ipld/collection.rs b/src/ipld/collection.rs index 1478c805..fb7bc4b0 100644 --- a/src/ipld/collection.rs +++ b/src/ipld/collection.rs @@ -1,4 +1,5 @@ use crate::ipld; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,6 +12,19 @@ pub enum Collection { Map(BTreeMap), } +impl From for Ipld { + fn from(collection: Collection) -> Self { + match collection { + Collection::Array(xs) => Ipld::List(xs.into_iter().map(Into::into).collect()), + Collection::Map(xs) => Ipld::Map( + xs.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + } + } +} + impl Collection { pub fn to_vec(self) -> Vec { match self { From 6a089868c3938cb50aa01fd8fda3b84488ffc12b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:54:49 -0800 Subject: [PATCH 200/234] Better defaults for inv payload builder --- src/invocation/payload.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a558b00b..90465671 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -100,6 +100,7 @@ pub struct Payload { /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] /// was created. + #[builder(default)] pub issued_at: Option, /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] From ef778149795d331d73d575ac41707f8061f01824 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 16:09:27 -0800 Subject: [PATCH 201/234] same for invocation --- src/invocation.rs | 13 +++++++++---- src/invocation/agent.rs | 13 +++++++++---- src/invocation/payload.rs | 11 +++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/invocation.rs b/src/invocation.rs index 840b537a..09eaad20 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -21,6 +21,7 @@ pub mod store; pub use agent::Agent; pub use payload::*; +use crate::ability::command::ToCommand; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -126,12 +127,13 @@ impl, C: Codec + TryFrom + Into> did } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > From> for Ipld where + Ipld: From, Payload: TryFrom, { fn from(invocation: Invocation) -> Self { @@ -140,12 +142,13 @@ where } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Envelope for Invocation where + Ipld: From, Payload: TryFrom, { type DID = DID; @@ -184,12 +187,13 @@ where } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Serialize for Invocation where + Ipld: From, Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result @@ -202,12 +206,13 @@ where impl< 'de, - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where + Ipld: From, Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 64a4e750..058f3d8e 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,6 +4,7 @@ use super::{ store::Store, Invocation, }; +use crate::ability::command::ToCommand; use crate::{ ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -31,7 +32,7 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable, + T: Resolvable + ToCommand, DID: Did, S: Store, P: promise::Store, @@ -57,10 +58,10 @@ pub struct Agent< impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> where - T::Promised: Clone, - Ipld: Encode, + Ipld: Encode + From, delegation::Payload: Clone, - T: Resolvable + Clone, + T: Resolvable + ToCommand + Clone, + T::Promised: Clone + ToCommand, DID: Did + Clone, S: Store, P: promise::Store, @@ -104,6 +105,7 @@ where >, > where + Ipld: From, Payload: TryFrom, { let proofs = self @@ -151,6 +153,7 @@ where >, > where + Ipld: From, Payload: TryFrom, { let proofs = self @@ -185,6 +188,7 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where + Ipld: From + From, Payload: TryFrom, arguments::Named: From, Invocation: Clone + Encode, @@ -249,6 +253,7 @@ where // FIXME return type ) -> Result, ()> where + Ipld: From, T: From, Payload: TryFrom, { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 90465671..2b42391f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -236,8 +236,8 @@ impl Capsule for Payload { impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.into().to_string().into()), - ("sub".into(), payload.subject.into().to_string().into()), + ("iss".into(), payload.issuer.to_string().into()), + ("sub".into(), payload.subject.to_string().into()), ("cmd".into(), payload.ability.to_command().into()), ("args".into(), payload.ability.into()), ( @@ -474,9 +474,12 @@ impl + Command, DID: Did> TryFrom for Payload { /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld { +impl From> for Ipld +where + Ipld: From, +{ fn from(payload: Payload) -> Self { - payload.into() + arguments::Named::from(payload).into() } } From 2f3810898d287b0338303c3bf715d2042ecc4f6f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 16:19:47 -0800 Subject: [PATCH 202/234] Add some dbg --- src/delegation/store/memory.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9ef9b8f9..6f6a3e5e 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -186,6 +186,8 @@ impl, Enc: Codec + TryFrom + } }); + dbg!(found.clone()); + if found.is_continue() { status = Status::NoPath; } From 6e9131c2492c4105c27749f4937f5973b075a55b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 22:36:17 -0800 Subject: [PATCH 203/234] debugging the store --- Cargo.toml | 3 + src/crypto/signature/envelope.rs | 6 - src/delegation/store/memory.rs | 341 +++++++++++++++++++++++++++---- 3 files changed, 299 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3aa30508..a1e3775a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,9 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] libipld = "0.16" +rand = "0.8" +testresult = "0.3" +test-log = "0.2" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index cb19990c..d080bfc0 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -102,7 +102,6 @@ pub trait Envelope: Sized { where Ipld: Encode + From, // FIXME force it to be named args not IPLD { - dbg!("try_sign"); Self::try_sign_generic(signer, varsig_header, payload) } @@ -128,21 +127,16 @@ pub trait Envelope: Sized { where Ipld: Encode + From, { - dbg!("try_sign_generic"); let ipld: Ipld = BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); - dbg!("buffer"); let mut buffer = vec![]; - dbg!("encode"); ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - dbg!("sign"); let signature = signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; - dbg!("construct"); Ok(Self::construct(varsig_header, signature, payload)) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 6f6a3e5e..f6801070 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -124,6 +124,22 @@ impl, Enc: Codec + TryFrom + .insert(cid); self.ucans.insert(cid.clone(), delegation); + + // dbg!(&self.ucans.keys().map(|k| k.to_string()).collect::>()); + // dbg!(&self + // .index + // .keys() + // .map(|k| k.clone().map(|y| y.to_string())) + // .collect::>()); + dbg!(self.ucans.len()); + dbg!(self.index.len()); + for (sub, inner) in self.index.clone() { + dbg!(sub.clone().map(|x| x.to_string())); + for (aud, cids) in inner { + dbg!(aud.to_string()); + dbg!(cids.len()); + } + } Ok(()) } @@ -139,65 +155,300 @@ impl, Enc: Codec + TryFrom + policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - match self - .index - .get(subject) // FIXME probably need to rework this after last minbute chanegs - .and_then(|aud_map| aud_map.get(aud)) - { - None => Ok(None), - Some(delegation_subtree) => { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } + dbg!("started get_chain"); + dbg!(subject.clone().map(|x| x.to_string()).clone()); + dbg!(aud.to_string().clone()); - let mut status = Status::Looking; - let mut target_aud = aud; - let mut chain = vec![]; + let blank_set = BTreeSet::new(); + let blank_map = BTreeMap::new(); - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(cid) { - return ControlFlow::Continue(()); - } + let all_powerlines = self.index.get(&None).unwrap_or(&blank_map); + let all_aud_for_subject = self.index.get(subject).unwrap_or(&blank_map); + let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); + let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); - if d.check_time(now).is_err() { - return ControlFlow::Continue(()); - } + let mut parent_candidate_stack = vec![]; + let mut hypothesis_chain = vec![]; - target_aud = &d.audience(); + dbg!(">>>>>>>>>>>>>>>."); + parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); - chain.push((*cid, d)); + let mut done = false; + while !done && !parent_candidate_stack.is_empty() { + dbg!("inner loop"); + + let mut next = None; + if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { + dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + dbg!(parent_cid_candidates.clone().fold(0, |acc, _| acc + 1)); + + for cid in parent_cid_candidates { + dbg!("##########################"); + dbg!(hypothesis_chain.len()); + if self.revocations.contains(cid) { + dbg!("REVOKE"); + continue; + } + dbg!("@@@@@@@@@@@@@@@@@@@@@@@@2"); - if let Some(ref subject) = subject { - if d.issuer() == subject { - status = Status::Complete; - } - } else { - status = Status::Complete; - } + if let Some(delegation) = self.ucans.get(cid) { + if delegation.check_time(now).is_err() { + dbg!("TIME"); + continue; + } - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) + let issuer = delegation.issuer().clone(); + dbg!(issuer.to_string().clone()); + if &Some(issuer.clone()) == delegation.subject() + && (&subject == &delegation.subject() || subject == &None) + { + dbg!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + hypothesis_chain.push((cid.clone(), delegation)); + done = true; + break; } - }); - dbg!(found.clone()); + let new_aud_candidates = + all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); + let new_powerline_candidates = + all_powerlines.get(&issuer).unwrap_or(&blank_set); + + dbg!("%%%%%%%%%%%%%%%%%%%%%%%%"); + hypothesis_chain.push((cid.clone(), delegation)); - if found.is_continue() { - status = Status::NoPath; + if !new_aud_candidates.is_empty() && !new_powerline_candidates.is_empty() { + next = Some( + new_aud_candidates + .iter() + .chain(new_powerline_candidates.iter()), + ); + // parent_candidate_stack.push( + // new_aud_candidates + // .iter() + // .chain(new_powerline_candidates.iter()), + // ); + break; + } } } - match status { - Status::Complete => Ok(NonEmpty::from_vec(chain)), - _ => Ok(None), - } + // Didn't find a match, so drop this candidate + parent_candidate_stack.pop(); + hypothesis_chain.pop(); + } else { + dbg!("done"); + done = true; // FIXME pass up error? + } + + if let Some(n) = next { + parent_candidate_stack.push(n); } } + + dbg!(&hypothesis_chain + .iter() + .map(|(cid, _)| cid.to_string()) + .collect::>()); + + Ok(NonEmpty::from_vec(hypothesis_chain)) + } +} + +#[cfg(test)] +mod tests { + use crate::ability::command::Command; + use crate::crypto::signature::Envelope; + use crate::delegation::store::Store; + use libipld_core::ipld::Ipld; + use rand::thread_rng; + use testresult::TestResult; + + #[test_log::test] + fn test_powerbox_ucan_resource() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let account_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let account = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + account_sk.verifying_key(), + )); + let account_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(account_sk)); + + let dnslink_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let dnslink = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + dnslink_sk.verifying_key(), + )); + let dnslink_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(dnslink_sk)); + + let device_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let device = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + device_sk.verifying_key(), + )); + let device_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(device_sk)); + + // FIXME perhaps add this back upstream as a named const + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + // 1. account -*-> server + // 2. server -a-> device + // 3. dnslink -d-> account + // 4. [dnslink -d-> account -*-> server -a-> device] + + // Both of these UCANs just create ephemeral DIDs & delegate all of those + // DID's rights to the server + // let (account_did, _account_ucan) = server_create_resource(&server_ed_did_key)?; + // let (dnslink_did, _dnslink_ucan) = server_create_resource(&server_ed_did_key)?; + + // 1. account -*-> server + let account_pbox = crate::Delegation::try_sign( + &account_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(account.clone()) + .audience(server.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), + ) + .expect("signature to work"); + + // 2. server -a-> device + let account_device_ucan = crate::Delegation::try_sign( + &server_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(server.clone()) + .audience(device.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), // I don't love this is now failable + ) + .expect("signature to work"); + + // 3. dnslink -d-> account + let dnslink_ucan = crate::Delegation::try_sign( + &dnslink_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(dnslink.clone())) + .issuer(dnslink.clone()) + .audience(account.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), + ) + .expect("signature to work"); + + #[derive(Debug, Clone, PartialEq)] + pub struct AccountInfo {} + + impl Command for AccountInfo { + const COMMAND: &'static str = "/account/info"; + } + + impl From for AccountInfo { + fn from(_: Ipld) -> Self { + AccountInfo {} + } + } + + impl From for Ipld { + fn from(_: AccountInfo) -> Self { + Ipld::Null + } + } + + // #[derive(Debug, Clone, PartialEq)] + // pub struct DnsLinkUpdate { + // pub cid: Cid, + // } + + // impl From for DnsLinkUpdate { + // fn from(_: Ipld) -> Self { + // todo!() + // } + // } + + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountInfo {}) + .proofs(vec![]) // FIXME + .build() + .expect("FIXME"), + ) + .expect("FIXME"); + + // FIXME reenable + // let dnslink_invocation = crate::Invocation::try_sign( + // &device, + // varsig_header, + // crate::invocation::PayloadBuilder::default() + // .subject(dnslink) + // .issuer(device) + // .audience(Some(server)) + // .ability(DnsLinkUpdate { cid: todo!() }) + // .build() + // .expect("FIXME"), + // ) + // .expect("FIXME"); + + use crate::crypto::varsig; + + let mut store: crate::delegation::store::MemoryStore< + crate::did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + > = Default::default(); + + let agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + + let _ = store.insert( + account_device_ucan.cid().expect("FIXME"), + account_device_ucan.clone(), + ); + + let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); + + let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); + + use std::time::SystemTime; + + dbg!(server.to_string().clone()); + dbg!(device.to_string().clone()); + dbg!(account.to_string().clone()); + // let chainer = store.get_chain(&device, &None, vec![], SystemTime::now()); + let chainer = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); + + // tracing::debug!(?account_pbox, "Capabilities"); + + // dbg!(store.clone()); + dbg!(chainer.clone()); + + assert!(chainer?.is_some()); + + Ok(()) } } From 5c3cea5508819f80c6000f02afa011f8e2241526 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 8 Mar 2024 11:33:49 -0800 Subject: [PATCH 204/234] Dios mio memstore test works now --- src/crypto/signature/envelope.rs | 1 - src/delegation/store/memory.rs | 147 +++++++++++++++++-------------- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index d080bfc0..07b18300 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -174,7 +174,6 @@ pub trait Envelope: Sized { fn cid(&self) -> Result where - // Ipld: Encode + From, Self: Encode, { let codec = varsig::header::Header::codec(self.varsig_header()).clone(); diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index f6801070..1a390dab 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,4 +1,5 @@ use super::Store; +use crate::crypto::signature::Envelope; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, @@ -101,9 +102,20 @@ impl, C: Codec + TryFrom + Into> } } +use crate::delegation; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; + // FIXME check that UCAN is valid -impl, Enc: Codec + TryFrom + Into> - Store for MemoryStore +impl< + DID: Did + Ord + Clone, + V: varsig::Header + Clone, + Enc: Codec + TryFrom + Into, + > Store for MemoryStore +where + Ipld: From>, + delegation::Payload: TryFrom, + Delegation: Encode, { type DelegationStoreError = String; // FIXME misisng @@ -116,6 +128,8 @@ impl, Enc: Codec + TryFrom + cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { + dbg!("^^^^^^^^^^^^^^^^^^^^^ inert"); + dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) .or_default() @@ -125,12 +139,6 @@ impl, Enc: Codec + TryFrom + self.ucans.insert(cid.clone(), delegation); - // dbg!(&self.ucans.keys().map(|k| k.to_string()).collect::>()); - // dbg!(&self - // .index - // .keys() - // .map(|k| k.clone().map(|y| y.to_string())) - // .collect::>()); dbg!(self.ucans.len()); dbg!(self.index.len()); for (sub, inner) in self.index.clone() { @@ -152,10 +160,11 @@ impl, Enc: Codec + TryFrom + &self, aud: &DID, subject: &Option, + // command: String, // FIXME policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - dbg!("started get_chain"); + dbg!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> get chain"); dbg!(subject.clone().map(|x| x.to_string()).clone()); dbg!(aud.to_string().clone()); @@ -170,86 +179,102 @@ impl, Enc: Codec + TryFrom + let mut parent_candidate_stack = vec![]; let mut hypothesis_chain = vec![]; - dbg!(">>>>>>>>>>>>>>>."); parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); + let mut next = None; - let mut done = false; - while !done && !parent_candidate_stack.is_empty() { - dbg!("inner loop"); - - let mut next = None; + 'outer: loop { + dbg!("starting loop"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - dbg!(parent_cid_candidates.clone().fold(0, |acc, _| acc + 1)); - for cid in parent_cid_candidates { - dbg!("##########################"); - dbg!(hypothesis_chain.len()); + if parent_cid_candidates.clone().collect::>().is_empty() { + dbg!("EMPTY"); + parent_candidate_stack.pop(); + hypothesis_chain.pop(); + break 'outer; + } + + 'inner: for cid in parent_cid_candidates { + next = None; + + dbg!(cid.to_string()); if self.revocations.contains(cid) { - dbg!("REVOKE"); continue; } - dbg!("@@@@@@@@@@@@@@@@@@@@@@@@2"); if let Some(delegation) = self.ucans.get(cid) { + dbg!(delegation.cid().expect("FIXME").to_string()); if delegation.check_time(now).is_err() { - dbg!("TIME"); continue; } + hypothesis_chain.push((cid.clone(), delegation)); + dbg!(hypothesis_chain.len()); + + // if hypothesis_chain.last().map(|x| x.1.issuer()) + // == Some(delegation.issuer()) + // { + // break 'outer; + // } + let issuer = delegation.issuer().clone(); - dbg!(issuer.to_string().clone()); + + // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() - && (&subject == &delegation.subject() || subject == &None) + // && (&subject == &delegation.subject() || subject == &None) { - dbg!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - hypothesis_chain.push((cid.clone(), delegation)); - done = true; - break; + break 'outer; } + dbg!(issuer.to_string().clone()); let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); + let new_powerline_candidates = all_powerlines.get(&issuer).unwrap_or(&blank_set); - dbg!("%%%%%%%%%%%%%%%%%%%%%%%%"); - hypothesis_chain.push((cid.clone(), delegation)); - - if !new_aud_candidates.is_empty() && !new_powerline_candidates.is_empty() { + // keep looking until exhausted? + // if subject == &None { + // dbg!("SUBJECT NONE"); + // next = Some( + // new_aud_candidates + // .iter() + // .chain(new_powerline_candidates.iter()), + // ); + // break 'inner; + // } + + if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { + dbg!("MORE CANIDATES"); next = Some( new_aud_candidates .iter() .chain(new_powerline_candidates.iter()), ); - // parent_candidate_stack.push( - // new_aud_candidates - // .iter() - // .chain(new_powerline_candidates.iter()), - // ); - break; + break 'inner; + } else { + // break 'outer; } } } - // Didn't find a match, so drop this candidate + if let Some(ref n) = next { + dbg!("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNn"); + parent_candidate_stack.push(n.clone()); + } else { + dbg!("NO MORE CANDIDATES"); + // Didn't find a match, so drop this candidate + // parent_candidate_stack.pop(); + // hypothesis_chain.pop(); + break 'outer; + } + } else { + dbg!("ELSE"); parent_candidate_stack.pop(); hypothesis_chain.pop(); - } else { - dbg!("done"); - done = true; // FIXME pass up error? - } - - if let Some(n) = next { - parent_candidate_stack.push(n); } } - dbg!(&hypothesis_chain - .iter() - .map(|(cid, _)| cid.to_string()) - .collect::>()); - Ok(NonEmpty::from_vec(hypothesis_chain)) } } @@ -306,11 +331,6 @@ mod tests { // 3. dnslink -d-> account // 4. [dnslink -d-> account -*-> server -a-> device] - // Both of these UCANs just create ephemeral DIDs & delegate all of those - // DID's rights to the server - // let (account_did, _account_ucan) = server_create_resource(&server_ed_did_key)?; - // let (dnslink_did, _dnslink_ucan) = server_create_resource(&server_ed_did_key)?; - // 1. account -*-> server let account_pbox = crate::Delegation::try_sign( &account_signer, @@ -436,18 +456,17 @@ mod tests { use std::time::SystemTime; - dbg!(server.to_string().clone()); dbg!(device.to_string().clone()); + dbg!(server.to_string().clone()); dbg!(account.to_string().clone()); - // let chainer = store.get_chain(&device, &None, vec![], SystemTime::now()); - let chainer = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); - - // tracing::debug!(?account_pbox, "Capabilities"); + dbg!(dnslink.to_string().clone()); + let chain_for_powerline = store.get_chain(&device, &None, vec![], SystemTime::now()); + let chain_for_dnslink = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); - // dbg!(store.clone()); - dbg!(chainer.clone()); + let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); + let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); - assert!(chainer?.is_some()); + assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME Ok(()) } From 9afd69d70300e2c14209c222def56dbe150a4c78 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 9 Mar 2024 14:41:27 -0800 Subject: [PATCH 205/234] WIP --- src/delegation/agent.rs | 2 +- src/delegation/policy/predicate.rs | 255 +++++++++++++++++++++-- src/delegation/policy/selector.rs | 31 ++- src/delegation/policy/selector/filter.rs | 14 ++ src/delegation/policy/selector/select.rs | 55 ++++- src/delegation/store/memory.rs | 96 ++++----- src/delegation/store/traits.rs | 4 +- src/invocation/agent.rs | 15 +- src/ipld/number.rs | 8 +- 9 files changed, 397 insertions(+), 83 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 139e8e41..57e0c30e 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -93,7 +93,7 @@ where let to_delegate = &self .store - .get_chain(&self.did, &subject, vec![], now) + .get_chain(&self.did, &subject, "/".into(), vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 01617003..352300da 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,7 +1,10 @@ +use super::selector::filter::Filter; use super::selector::{Select, SelectorError}; use crate::ipld; +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -16,15 +19,15 @@ pub enum Predicate { False, // Comparison - Equal(Select, Select), + Equal(Select, ipld::Newtype), - GreaterThan(Select, Select), - GreaterThanOrEqual(Select, Select), + GreaterThan(Select, ipld::Number), + GreaterThanOrEqual(Select, ipld::Number), - LessThan(Select, Select), - LessThanOrEqual(Select, Select), + LessThan(Select, ipld::Number), + LessThanOrEqual(Select, ipld::Number), - Like(Select, Select), + Like(Select, String), // Connectives Not(Box), @@ -36,17 +39,25 @@ pub enum Predicate { Some(Select, Box), // ∃x ∈ xs } +#[derive(Debug, Clone, PartialEq, EnumAsInner)] +pub enum Harmonization { + IncompatiblePredicate, // Failed check + IncomparablePath, // LHS is ok + LhsNarrowerOrEqual, // LHS succeeded + RhsNarrower, // Succeeded, but RHS is narrower +} + impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { Predicate::True => true, Predicate::False => false, - Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, - Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, - Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, - Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, - Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, - Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, + Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, + Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, + Predicate::LessThan(lhs, rhs_data) => lhs.resolve(data)? < rhs_data, + Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? <= rhs_data, + Predicate::Like(lhs, rhs_data) => glob(&lhs.resolve(data)?, &rhs_data), Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, @@ -65,6 +76,226 @@ impl Predicate { } }) } + + // FIXME check paths are subsets, becase that changes some of these + pub fn harmonize( + &self, + other: &Self, + lhs_ctx: Vec, + rhs_ctx: Vec, + ) -> Harmonization { + match self { + Predicate::True => match other { + Predicate::True => Harmonization::LhsNarrowerOrEqual, + _ => todo!(), + }, + // FIXME this should generally fail always? But how does it compare? + Predicate::False => match other { + // FIXME correct? + Predicate::False => Harmonization::LhsNarrowerOrEqual, + _ => todo!(), + }, + Predicate::Equal(lhs_selector, lhs_ipld) => match other { + Predicate::Equal(rhs_selector, rhs_ipld) => { + // FIXME include ctx in path? + if lhs_selector.is_related(rhs_selector) { + if lhs_ipld == rhs_ipld { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + + /************ + * Numerics * + ************/ + Predicate::GreaterThan(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num > *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num >= *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::LessThan(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num < *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::LessThanOrEqual(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num <= *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + /********** + * String * + **********/ + Predicate::Like(rhs_selector, rhs_str) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_str) = String::try_from(*lhs_ipld) { + if glob(&lhs_str, rhs_str) { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + + /*************** + * Connectives * + ***************/ + Predicate::Not(rhs_inner) => { + let rhs_raw_pred: Predicate = **rhs_inner; + match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, + Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // FIXME double check + Harmonization::IncompatiblePredicate => Harmonization::LhsNarrowerOrEqual, + } + } + Predicate::And(and_left, and_right) => { + let rhs_raw_pred1: Predicate = **and_left; + let rhs_raw_pred2: Predicate = **and_right; + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { + Harmonization::RhsNarrower + } + (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { + Harmonization::IncompatiblePredicate + } + (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::IncompatiblePredicate + } + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::IncompatiblePredicate, _) => { + Harmonization::IncompatiblePredicate + } + (_, Harmonization::IncompatiblePredicate) => { + Harmonization::IncompatiblePredicate + } + } + } + Predicate::Or(or_left, or_right) => { + let rhs_raw_pred1: Predicate = *or_left.clone(); + let rhs_raw_pred2: Predicate = *or_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { + Harmonization::RhsNarrower + } + (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::IncompatiblePredicate, _) => { + Harmonization::IncompatiblePredicate + } + (_, Harmonization::IncompatiblePredicate) => { + Harmonization::IncompatiblePredicate + } + } + } + /****************** + * Quantification * + ******************/ + Predicate::Every(rhs_selector, rhs_inner) => { + let rhs_raw_pred: Predicate = *rhs_inner.clone(); + todo!() + // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, + // Harmonization::RhsNarrower => Harmonization::RhsNarrower, + // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // Harmonization::IncompatiblePredicate => { + // Harmonization::IncompatiblePredicate + // } + // } + } + Predicate::Some(rhs_selector, rhs_inner) => { + let rhs_raw_pred: Predicate = *rhs_inner.clone(); + todo!() + // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, + // Harmonization::RhsNarrower => Harmonization::RhsNarrower, + // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // Harmonization::IncompatiblePredicate => { + // Harmonization::IncompatiblePredicate + // } + // } + } + _ => todo!(), // FIXME + }, + _ => todo!(), + } + } } pub fn glob(input: &String, pattern: &String) -> bool { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 4ee35a1b..965b89a1 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -21,12 +21,23 @@ use nom::{ IResult, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::cmp::Ordering; use std::{fmt, str::FromStr}; use thiserror::Error; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct Selector(pub Vec); +impl Selector { + pub fn new() -> Self { + Selector(vec![]) + } + + pub fn is_related(&self, other: &Selector) -> bool { + self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) + } +} + pub fn parse(input: &str) -> IResult<&str, Selector> { let without_this = many1(filter::parse); let with_this = preceded(char('.'), many0(filter::parse)); @@ -106,3 +117,21 @@ impl SelectorError { } } } + +impl PartialOrd for Selector { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(Ordering::Equal); + } + + if self.0.starts_with(&other.0) { + return Some(Ordering::Greater); + } + + if other.0.starts_with(&self.0) { + return Some(Ordering::Less); + } + + None + } +} diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index b7273bdc..a1e054a3 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -25,6 +25,20 @@ pub enum Filter { Try(Box), // ? } +impl Filter { + pub fn is_in(&self, other: &Self) -> bool { + match (self, other) { + (Filter::ArrayIndex(a), Filter::ArrayIndex(b)) => a == b, + (Filter::Field(a), Filter::Field(b)) => a == b, + (Filter::Values, Filter::Values) => true, + (Filter::ArrayIndex(_a), Filter::Values) => true, + (Filter::Field(_k), Filter::Values) => true, + (Filter::Try(a), Filter::Try(b)) => a.is_in(b), // FIXME Try is basically == null? + _ => false, + } + } +} + impl fmt::Display for Filter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 736bff0a..e9c32ae2 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -2,6 +2,7 @@ use super::Selector; // FIXME cycle? use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -12,19 +13,21 @@ pub enum Select { Pure(T), } -impl From> for Ipld -where - Ipld: From, -{ - fn from(s: Select) -> Self { - match s { - Select::Get(ops) => Selector(ops).to_string().into(), - Select::Pure(inner) => inner.into(), +impl Select { + pub fn is_related(&self, other: &Select) -> bool + where + Ipld: From + From, + { + match (self, other) { + (Select::Pure(lhs_val), Select::Pure(rhs_val)) => { + Ipld::from(lhs_val.clone()) == Ipld::from(rhs_val.clone()) + } + (Select::Get(lhs_path), Select::Get(rhs_path)) => { + Selector(lhs_path.clone()).is_related(&Selector(rhs_path.clone())) + } + _ => false, } } -} - -impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { Select::Pure(inner) => Ok(inner), @@ -109,6 +112,36 @@ impl Select { } } +impl From> for Ipld +where + Ipld: From, +{ + fn from(s: Select) -> Self { + match s { + Select::Get(ops) => Selector(ops).to_string().into(), + Select::Pure(inner) => inner.into(), + } + } +} + +impl PartialOrd for Select { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Select::Pure(inner), Select::Pure(other_inner)) => { + if inner == other_inner { + Some(Ordering::Equal) + } else { + None + } + } + (Select::Get(ops), Select::Get(other_ops)) => { + Selector(ops.clone()).partial_cmp(&Selector(other_ops.clone())) + } + _ => None, + } + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Select { type Parameters = T::Parameters; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 1a390dab..6d547e88 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -120,7 +120,7 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or("nope".into()) + self.ucans.get(cid).ok_or("nope".into()) // FIXME } fn insert( @@ -128,7 +128,6 @@ where cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - dbg!("^^^^^^^^^^^^^^^^^^^^^ inert"); dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) @@ -160,14 +159,10 @@ where &self, aud: &DID, subject: &Option, - // command: String, // FIXME + command: String, policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - dbg!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> get chain"); - dbg!(subject.clone().map(|x| x.to_string()).clone()); - dbg!(aud.to_string().clone()); - let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); @@ -179,97 +174,93 @@ where let mut parent_candidate_stack = vec![]; let mut hypothesis_chain = vec![]; + let corrected_target_command = if command.ends_with('/') { + command + } else { + format!("{}/", command) + }; + parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); let mut next = None; 'outer: loop { - dbg!("starting loop"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { - dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - if parent_cid_candidates.clone().collect::>().is_empty() { - dbg!("EMPTY"); parent_candidate_stack.pop(); hypothesis_chain.pop(); break 'outer; } 'inner: for cid in parent_cid_candidates { - next = None; - - dbg!(cid.to_string()); if self.revocations.contains(cid) { continue; } if let Some(delegation) = self.ucans.get(cid) { - dbg!(delegation.cid().expect("FIXME").to_string()); if delegation.check_time(now).is_err() { continue; } - hypothesis_chain.push((cid.clone(), delegation)); - dbg!(hypothesis_chain.len()); + // FIXME extract + let corrected_delegation_command = + if delegation.payload.command.ends_with('/') { + delegation.payload.command.clone() + } else { + format!("{}/", delegation.payload.command) + }; + + if !corrected_delegation_command.starts_with(&corrected_target_command) { + continue; + } - // if hypothesis_chain.last().map(|x| x.1.issuer()) - // == Some(delegation.issuer()) - // { - // break 'outer; - // } + for target_pred in policy.iter() { + for delegate_pred in delegation.payload.policy.iter() { + let comparison = + target_pred.harmonize(delegate_pred, vec![], vec![]); + + if comparison.is_incompatible_predicate() + || comparison.is_rhs_narrower() + { + continue 'inner; + } + } + } + + hypothesis_chain.push((cid.clone(), delegation)); let issuer = delegation.issuer().clone(); // Hit a root delegation, AKA base case - if &Some(issuer.clone()) == delegation.subject() - // && (&subject == &delegation.subject() || subject == &None) - { + if &Some(issuer.clone()) == delegation.subject() { break 'outer; } - dbg!(issuer.to_string().clone()); let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); let new_powerline_candidates = all_powerlines.get(&issuer).unwrap_or(&blank_set); - // keep looking until exhausted? - // if subject == &None { - // dbg!("SUBJECT NONE"); - // next = Some( - // new_aud_candidates - // .iter() - // .chain(new_powerline_candidates.iter()), - // ); - // break 'inner; - // } - if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { - dbg!("MORE CANIDATES"); next = Some( new_aud_candidates .iter() .chain(new_powerline_candidates.iter()), ); + break 'inner; - } else { - // break 'outer; } } } if let Some(ref n) = next { - dbg!("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNn"); parent_candidate_stack.push(n.clone()); + next = None; } else { - dbg!("NO MORE CANDIDATES"); - // Didn't find a match, so drop this candidate - // parent_candidate_stack.pop(); - // hypothesis_chain.pop(); + // Didn't find a match break 'outer; } } else { - dbg!("ELSE"); parent_candidate_stack.pop(); hypothesis_chain.pop(); } @@ -460,8 +451,17 @@ mod tests { dbg!(server.to_string().clone()); dbg!(account.to_string().clone()); dbg!(dnslink.to_string().clone()); - let chain_for_powerline = store.get_chain(&device, &None, vec![], SystemTime::now()); - let chain_for_dnslink = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); + + let chain_for_powerline = + store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); + + let chain_for_dnslink = store.get_chain( + &device, + &Some(dnslink), + "/".into(), + vec![], + SystemTime::now(), + ); let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 58d08bbc..16fe3814 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -28,6 +28,7 @@ pub trait Store, Enc: Codec + TryFrom + In &self, audience: &DID, subject: &Option, + command: String, policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; @@ -36,10 +37,11 @@ pub trait Store, Enc: Codec + TryFrom + In &self, issuer: DID, audience: &DID, + command: String, policy: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, &Some(issuer), policy, now) + self.get_chain(audience, &Some(issuer), command, policy, now) .map(|chain| chain.is_some()) } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 058f3d8e..5f5d927d 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -110,7 +110,7 @@ where { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), vec![], now) + .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -158,7 +158,7 @@ where { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), vec![], now) + .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -261,11 +261,12 @@ where let proofs = if &subject == self.did { vec![] } else { - self.delegation_store - .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) - .map_err(|_| ())? - .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) - .unwrap_or(vec![]) + todo!("update to latest trait interface"); // FIXME + // self.delegation_store + // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) + // .map_err(|_| ())? + // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + // .unwrap_or(vec![]) }; let payload = Payload { diff --git a/src/ipld/number.rs b/src/ipld/number.rs index 15e3bd0b..aced2a53 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -41,10 +41,14 @@ impl From for Ipld { } impl TryFrom for Number { - type Error = SerdeError; + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::Integer(i) => Ok(Number::Integer(i)), + Ipld::Float(f) => Ok(Number::Float(f)), + _ => Err(()), + } } } From 30e687cf642749f788e996648cdfba48b0362b98 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 9 Mar 2024 14:42:44 -0800 Subject: [PATCH 206/234] Remove bools from pred lang --- src/delegation/policy/predicate.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 352300da..65d1db08 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -14,10 +14,6 @@ use proptest::prelude::*; // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Predicate { - // Booleans - True, - False, - // Comparison Equal(Select, ipld::Newtype), @@ -50,8 +46,6 @@ pub enum Harmonization { impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { - Predicate::True => true, - Predicate::False => false, Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, @@ -85,16 +79,6 @@ impl Predicate { rhs_ctx: Vec, ) -> Harmonization { match self { - Predicate::True => match other { - Predicate::True => Harmonization::LhsNarrowerOrEqual, - _ => todo!(), - }, - // FIXME this should generally fail always? But how does it compare? - Predicate::False => match other { - // FIXME correct? - Predicate::False => Harmonization::LhsNarrowerOrEqual, - _ => todo!(), - }, Predicate::Equal(lhs_selector, lhs_ipld) => match other { Predicate::Equal(rhs_selector, rhs_ipld) => { // FIXME include ctx in path? @@ -177,7 +161,7 @@ impl Predicate { **********/ Predicate::Like(rhs_selector, rhs_str) => { if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_str) = String::try_from(*lhs_ipld) { + if let Ok(lhs_str) = String::try_from(lhs_ipld.clone()) { if glob(&lhs_str, rhs_str) { Harmonization::LhsNarrowerOrEqual } else { @@ -195,7 +179,7 @@ impl Predicate { * Connectives * ***************/ Predicate::Not(rhs_inner) => { - let rhs_raw_pred: Predicate = **rhs_inner; + let rhs_raw_pred: Predicate = *rhs_inner.clone(); match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, @@ -205,8 +189,8 @@ impl Predicate { } } Predicate::And(and_left, and_right) => { - let rhs_raw_pred1: Predicate = **and_left; - let rhs_raw_pred2: Predicate = **and_right; + let rhs_raw_pred1: Predicate = *and_left.clone(); + let rhs_raw_pred2: Predicate = *and_right.clone(); match ( self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), @@ -329,8 +313,6 @@ pub fn glob(input: &String, pattern: &String) -> bool { impl From for Ipld { fn from(p: Predicate) -> Self { match p { - Predicate::True => Ipld::Bool(true), - Predicate::False => Ipld::Bool(false), Predicate::Equal(lhs, rhs) => { Ipld::List(vec![Ipld::String("==".to_string()), lhs.into(), rhs.into()]) } From 02d4c2f79a6beed1dc8969210e9fda1148de5aac Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 11 Mar 2024 20:48:18 -0700 Subject: [PATCH 207/234] Working on predicate unification / symbolic matching --- src/delegation/policy/predicate.rs | 684 ++++++++++++++++++++++------- src/delegation/store/memory.rs | 4 +- 2 files changed, 534 insertions(+), 154 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 65d1db08..c73dbbf6 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -3,6 +3,7 @@ use super::selector::{Select, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; +use multihash::Hasher; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -37,10 +38,36 @@ pub enum Predicate { #[derive(Debug, Clone, PartialEq, EnumAsInner)] pub enum Harmonization { - IncompatiblePredicate, // Failed check - IncomparablePath, // LHS is ok - LhsNarrowerOrEqual, // LHS succeeded - RhsNarrower, // Succeeded, but RHS is narrower + Equal, // e.g. x > 10 vs x > 10 + Conflict, // e.g. x == 1 vs x == 2 + LhsWeaker, // e.g. x > 10 vs x > 100 (AKA compatible but rhs narrower than lhs) + LhsStronger, // e.g. x > 10 vs x > 1 (AKA compatible lhs narrower than rhs) + StrongerTogether, // e.g. x > 10 vs x < 100 (AKA both narrow each other) + IncomparablePath, // e.g. .foo and .bar +} + +impl Harmonization { + pub fn complement(self) -> Self { + match self { + Harmonization::Equal => Harmonization::Conflict, + Harmonization::Conflict => Harmonization::Equal, // FIXME Correct? + Harmonization::LhsWeaker => Harmonization::LhsStronger, + Harmonization::LhsStronger => Harmonization::LhsWeaker, + Harmonization::StrongerTogether => Harmonization::StrongerTogether, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + } + } + + pub fn flip(self) -> Self { + match self { + Harmonization::Equal => Harmonization::Equal, + Harmonization::Conflict => Harmonization::Conflict, + Harmonization::LhsWeaker => Harmonization::LhsStronger, + Harmonization::LhsStronger => Harmonization::LhsWeaker, + Harmonization::StrongerTogether => Harmonization::StrongerTogether, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + } + } } impl Predicate { @@ -78,205 +105,560 @@ impl Predicate { lhs_ctx: Vec, rhs_ctx: Vec, ) -> Harmonization { - match self { - Predicate::Equal(lhs_selector, lhs_ipld) => match other { - Predicate::Equal(rhs_selector, rhs_ipld) => { - // FIXME include ctx in path? - if lhs_selector.is_related(rhs_selector) { - if lhs_ipld == rhs_ipld { - Harmonization::LhsNarrowerOrEqual + match (self, other) { + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::Equal(rhs_selector, rhs_ipld), + ) => { + // FIXME include ctx in path? + if lhs_selector.is_related(rhs_selector) { + if lhs_ipld == rhs_ipld { + Harmonization::Equal + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + // FIXME lhs + rhs selector must be exact + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num > *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - - /************ - * Numerics * - ************/ - Predicate::GreaterThan(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num > *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num >= *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::GreaterThanOrEqual(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num >= *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num < *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::LessThan(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num < *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num <= *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::LessThanOrEqual(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num <= *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + /*********** + * Strings * + ***********/ + (Predicate::Like(lhs_selector, lhs_str), Predicate::Like(rhs_selector, rhs_str)) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_str == rhs_str { + Harmonization::Equal } else { - Harmonization::IncompatiblePredicate + // FIXME actually not accurate; need to walk both in case of inner patterns + match (glob(lhs_str, rhs_str), glob(rhs_str, lhs_str)) { + (true, true) => Harmonization::StrongerTogether, + _ => Harmonization::Conflict, + } } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - /********** - * String * - **********/ - Predicate::Like(rhs_selector, rhs_str) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_str) = String::try_from(lhs_ipld.clone()) { - if glob(&lhs_str, rhs_str) { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + (Predicate::Equal(lhs_selector, lhs_ipld), Predicate::Like(rhs_selector, rhs_str)) => { + if lhs_selector.is_related(rhs_selector) { + if let Ipld::String(lhs_str) = &lhs_ipld.0 { + if glob(&lhs_str, rhs_str) { + // FIXME? + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + // NOTE Predicate::Like forces this to unify as a string, so anything else fails + // ...so this is not *not* a type checker + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } + } + (lhs @ Predicate::Like(_, _), rhs @ Predicate::Equal(_, _)) => { + rhs.harmonize(lhs, rhs_ctx, lhs_ctx).complement() + } - /*************** - * Connectives * - ***************/ - Predicate::Not(rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, - Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, - Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // FIXME double check - Harmonization::IncompatiblePredicate => Harmonization::LhsNarrowerOrEqual, + /**************** + * Greater Than * + ***************/ + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num > rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker + } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::And(and_left, and_right) => { - let rhs_raw_pred1: Predicate = *and_left.clone(); - let rhs_raw_pred2: Predicate = *and_right.clone(); - - match ( - self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), - self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), - ) { - (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::LhsWeaker + } else { + Harmonization::LhsStronger } - (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { - Harmonization::RhsNarrower + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + + /************************* + * Greater Than Or Equal * + *************************/ + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num > rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::IncomparablePath, right) => right, - (left, Harmonization::IncomparablePath) => left, - (Harmonization::IncompatiblePredicate, _) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (_, Harmonization::IncompatiblePredicate) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num <= rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::Or(or_left, or_right) => { - let rhs_raw_pred1: Predicate = *or_left.clone(); - let rhs_raw_pred2: Predicate = *or_right.clone(); + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } - match ( - self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), - self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), - ) { - (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + /********************** + * Less Than Or Equal * + **********************/ + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { - Harmonization::RhsNarrower + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::LhsWeaker + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { - Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num >= rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + + /************* + * Less Than * + *************/ + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::LhsStronger + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::IncomparablePath, right) => right, - (left, Harmonization::IncomparablePath) => left, - (Harmonization::IncompatiblePredicate, _) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (_, Harmonization::IncompatiblePredicate) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - /****************** - * Quantification * - ******************/ - Predicate::Every(rhs_selector, rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - todo!() - // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, - // Harmonization::RhsNarrower => Harmonization::RhsNarrower, - // Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // Harmonization::IncompatiblePredicate => { - // Harmonization::IncompatiblePredicate - // } - // } + } + + /*************** + * Connectives * + ***************/ + (_self, Predicate::Not(rhs_inner)) => { + self.harmonize(rhs_inner, lhs_ctx, rhs_ctx).complement() + } + (Predicate::Not(lhs_inner), rhs) => { + lhs_inner.harmonize(rhs, lhs_ctx, rhs_ctx).complement() + } + (_self, Predicate::And(and_left, and_right)) => { + let rhs_raw_pred1: Predicate = *and_left.clone(); + let rhs_raw_pred2: Predicate = *and_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::Conflict, _) => Harmonization::Conflict, + (_, Harmonization::Conflict) => Harmonization::Conflict, + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::Equal, rhs) => rhs, + (lhs, Harmonization::Equal) => lhs, + (Harmonization::LhsWeaker, Harmonization::LhsWeaker) => { + Harmonization::LhsWeaker + } + (Harmonization::LhsStronger, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (Harmonization::LhsStronger, Harmonization::LhsWeaker) => { + Harmonization::StrongerTogether + } + (Harmonization::LhsWeaker, Harmonization::LhsStronger) => { + Harmonization::StrongerTogether + } + (Harmonization::StrongerTogether, _) => Harmonization::StrongerTogether, + (_, Harmonization::StrongerTogether) => Harmonization::StrongerTogether, } - Predicate::Some(rhs_selector, rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - todo!() - // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, - // Harmonization::RhsNarrower => Harmonization::RhsNarrower, - // Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // Harmonization::IncompatiblePredicate => { - // Harmonization::IncompatiblePredicate - // } - // } + } + (lhs @ Predicate::And(_, _), rhs) => lhs.harmonize(rhs, lhs_ctx, rhs_ctx).flip(), + (_self, Predicate::Or(or_left, or_right)) => { + let rhs_raw_pred1: Predicate = *or_left.clone(); + let rhs_raw_pred2: Predicate = *or_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::Conflict, Harmonization::Conflict) => Harmonization::Conflict, + (lhs, Harmonization::Conflict) => lhs, + (Harmonization::Conflict, rhs) => rhs, + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::Equal, rhs) => rhs, + (lhs, Harmonization::Equal) => lhs, + (Harmonization::LhsWeaker, Harmonization::LhsWeaker) => { + Harmonization::LhsWeaker + } + (Harmonization::LhsStronger, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (_, Harmonization::LhsWeaker) => Harmonization::LhsWeaker, + (Harmonization::LhsWeaker, _) => Harmonization::LhsWeaker, + (Harmonization::LhsStronger, Harmonization::StrongerTogether) => { + Harmonization::LhsStronger + } + (Harmonization::StrongerTogether, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (Harmonization::StrongerTogether, Harmonization::StrongerTogether) => { + Harmonization::StrongerTogether + } } - _ => todo!(), // FIXME - }, + } + (lhs @ Predicate::Or(_, _), rhs) => lhs.harmonize(rhs, lhs_ctx, rhs_ctx).flip(), + // /****************** + // * Quantification * + // ******************/ + // Predicate::Every(rhs_selector, rhs_inner) => { + // let rhs_raw_pred: Predicate = *rhs_inner.clone(); + // // TODO FIXME exact path + // todo!() + // // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // // Harmonization::LhsPassed => Harmonization::LhsPassed, + // // Harmonization::LhsWeaker => Harmonization::LhsWeaker, + // // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // // Harmonization::Conflict => { + // // Harmonization::Conflict + // // } + // // } + // } + // Predicate::Some(rhs_selector, rhs_inner) => { + // let rhs_raw_pred: Predicate = *rhs_inner.clone(); + // // TODO FIXME As long as the lhs path doens't terminate earlier, then pass + // todo!() + // // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // // Harmonization::LhsPassed => Harmonization::LhsPassed, + // // Harmonization::LhsWeaker => Harmonization::LhsWeaker, + // // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // // Harmonization::Conflict => { + // // Harmonization::Conflict + // // } + // // } + // } + // }, _ => todo!(), } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 6d547e88..4b9373bb 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -218,9 +218,7 @@ where let comparison = target_pred.harmonize(delegate_pred, vec![], vec![]); - if comparison.is_incompatible_predicate() - || comparison.is_rhs_narrower() - { + if comparison.is_conflict() || comparison.is_lhs_weaker() { continue 'inner; } } From bfae42777bb904cb7c0d00e6765daa7119369e5f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 12 Mar 2024 18:20:36 -0700 Subject: [PATCH 208/234] Add "via" functionality --- src/ability/arguments.rs | 23 ------------ src/delegation/agent.rs | 3 ++ src/delegation/payload.rs | 4 ++ src/did/traits.rs | 2 +- src/invocation/agent.rs | 2 +- src/invocation/payload.rs | 78 +++++++++++++++++++++++---------------- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 7d504812..d53a5583 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -3,26 +3,3 @@ mod named; pub use named::*; - -use crate::ipld; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -// FIXME just remove? -// FIXME move under invoc::promise? -// pub type Promised = Resolves>; -// -// impl Promised { -// pub fn try_resolve_option(self) -> Option> { -// match self.try_resolve() { -// Err(_) => None, -// Ok(named_promises) => named_promises -// .iter() -// .try_fold(BTreeMap::new(), |mut map, (k, v)| { -// map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); -// Some(map) -// }) -// .map(Named), -// } -// } -// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 57e0c30e..c28cf6af 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -57,6 +57,7 @@ where &self, audience: DID, subject: Option, + via: Option, command: String, new_policy: Vec, metadata: BTreeMap, @@ -77,6 +78,7 @@ where issuer: self.did.clone(), audience, subject, + via, command, metadata, nonce, @@ -107,6 +109,7 @@ where issuer: self.did.clone(), audience, subject, + via, command, policy, metadata, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f830ec23..f74e4a94 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -46,6 +46,10 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, + /// A [`Did`] that must be in the delegation chain at invocation time. + #[builder(default)] + pub via: Option, + /// The command being delegated. pub command: String, diff --git a/src/did/traits.rs b/src/did/traits.rs index 03ed7659..53bebea9 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -2,7 +2,7 @@ use did_url::DID; use std::fmt; pub trait Did: - PartialEq + ToString + TryFrom + Into + signature::Verifier + PartialEq + ToString + TryFrom + Into + signature::Verifier + Ord { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5f5d927d..c380a39b 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -329,7 +329,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - ValidationError(#[source] ValidationError), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2b42391f..fa281e76 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -17,6 +17,7 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; +use std::collections::BTreeSet; use std::{collections::BTreeMap, fmt}; use thiserror::Error; use web_time::SystemTime; @@ -147,7 +148,7 @@ impl Payload { &self, proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError> + ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, @@ -160,45 +161,57 @@ impl Payload { cmd.push('/'); } - proofs.into_iter().try_fold(&self.issuer, |iss, proof| { - if *iss != proof.audience { - return Err(ValidationError::InvalidSubject.into()); - } + let (_, vias) = proofs.into_iter().try_fold( + (&self.issuer, BTreeSet::new()), + |(iss, mut vias), proof| { + if *iss != proof.audience { + return Err(ValidationError::InvalidSubject.into()); + } - if let Some(proof_subject) = &proof.subject { - if self.subject != *proof_subject { - return Err(ValidationError::MisalignedIssAud.into()); + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::MisalignedIssAud.into()); + } } - } - if SystemTime::from(proof.expiration.clone()) > *now { - return Err(ValidationError::Expired.into()); - } + if SystemTime::from(proof.expiration.clone()) > *now { + return Err(ValidationError::Expired.into()); + } - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { - return Err(ValidationError::NotYetValid.into()); + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > *now { + return Err(ValidationError::NotYetValid.into()); + } } - } - if !cmd.starts_with(&proof.command) { - return Err(ValidationError::CommandMismatch(proof.command.clone())); - } + vias.remove(&iss); + if let Some(via_did) = &proof.via { + vias.insert(via_did); + } + + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } - let ipld_args = Ipld::from(args.clone()); + let ipld_args = Ipld::from(args.clone()); - for predicate in proof.policy.iter() { - if !predicate - .clone() - .run(&ipld_args) - .map_err(ValidationError::SelectorError)? - { - return Err(ValidationError::FailedPolicy(predicate.clone())); + for predicate in proof.policy.iter() { + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); + } } - } - Ok(&proof.issuer) - })?; + Ok((&proof.issuer, vias)) + }, + )?; + + if !vias.is_empty() { + todo!() + } Ok(()) } @@ -206,7 +219,7 @@ impl Payload { /// Delegation validation errors. #[derive(Debug, Clone, PartialEq, Error)] -pub enum ValidationError { +pub enum ValidationError { #[error("The subject of the delegation is invalid")] InvalidSubject, @@ -227,6 +240,9 @@ pub enum ValidationError { #[error(transparent)] SelectorError(#[from] SelectorError), + + #[error("via field constraint was unfulfilled: {0:?}")] + UnfulfilledViaConstraint(BTreeSet), } impl Capsule for Payload { From b65ec3e1a69b50d5865ab2223ac2bdab9621d953 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 13 Mar 2024 09:40:15 -0700 Subject: [PATCH 209/234] Defaults --- src/invocation/agent.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index c380a39b..4fad1b07 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,13 +6,13 @@ use super::{ }; use crate::ability::command::ToCommand; use crate::{ - ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, + ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ signature::{self, Envelope}, varsig, Nonce, }, delegation, - did::Did, + did::{self, Did}, invocation::promise, time::Timestamp, }; @@ -32,13 +32,13 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable + ToCommand, - DID: Did, S: Store, P: promise::Store, D: delegation::store::Store, - V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + T: Resolvable + ToCommand = ability::preset::Preset, + DID: Did = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -56,7 +56,7 @@ pub struct Agent< marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, P, D, T, DID, V, C> where Ipld: Encode + From, delegation::Payload: Clone, From 61ed0cae918926d9ff0c28ce1eb6fd0a8d9944bc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 13 Mar 2024 20:47:22 -0700 Subject: [PATCH 210/234] Much work, but clearer what's required --- Cargo.toml | 1 + src/ability/crud.rs | 11 ++ src/ability/crud/create.rs | 17 ++ src/ability/crud/destroy.rs | 13 ++ src/ability/crud/read.rs | 17 ++ src/ability/crud/update.rs | 14 +- src/ability/msg.rs | 9 + src/ability/msg/send.rs | 11 ++ src/ability/preset.rs | 11 ++ src/ability/ucan/revoke.rs | 7 + src/ability/wasm/run.rs | 10 ++ src/crypto/signature/envelope.rs | 42 +++-- src/delegation.rs | 10 +- src/delegation/agent.rs | 13 +- src/delegation/payload.rs | 105 ++++++++++-- src/delegation/policy/predicate.rs | 108 ++++++++++-- src/delegation/policy/selector.rs | 82 +++++++++ src/delegation/policy/selector/select.rs | 203 +++++++++++------------ src/delegation/store/memory.rs | 134 +++++++++++---- src/delegation/store/traits.rs | 9 +- src/did/traits.rs | 5 +- src/invocation.rs | 35 +++- src/invocation/agent.rs | 82 +++++++-- src/invocation/payload.rs | 138 ++++++++++++--- src/invocation/promise/store/memory.rs | 4 +- src/invocation/promise/store/traits.rs | 2 +- src/invocation/store.rs | 28 +++- src/receipt.rs | 15 +- src/receipt/payload.rs | 43 ++++- src/time/timestamp.rs | 24 +++ 30 files changed, 947 insertions(+), 256 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1e3775a..1415bdec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] libipld = "0.16" +pretty_assertions = "1.4" rand = "0.8" testresult = "0.3" test-log = "0.2" diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 0571b77f..e43a47a8 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -70,6 +70,17 @@ pub enum Crud { Destroy(Destroy), } +impl From for arguments::Named { + fn from(crud: Crud) -> Self { + match crud { + Crud::Create(create) => create.into(), + Crud::Read(read) => read.into(), + Crud::Update(update) => update.into(), + Crud::Destroy(destroy) => destroy.into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedCrud { Create(PromisedCreate), diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 86df7070..69acac8c 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -53,6 +54,22 @@ pub struct Create { pub args: Option>, } +impl From for Ipld { + fn from(create: Create) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = create.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + if let Some(args) = create.args { + map.insert("args".to_string(), args.into()); + } + + Ipld::Map(map) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/create` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 1f2516f5..3f52084a 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,18 @@ pub struct Destroy { pub path: Option, } +impl From for Ipld { + fn from(destroy: Destroy) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = destroy.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + Ipld::Map(map) + } +} + const COMMAND: &'static str = "/crud/destroy"; impl Command for Destroy { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 1289a45a..873636e2 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,22 @@ pub struct Read { pub args: Option>, } +impl From for Ipld { + fn from(ready: Read) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = ready.path { + map.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = ready.args { + map.insert("args".to_string(), args.into()); + } + + map.into() + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/read` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index ebf0c281..935ab40f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -189,9 +189,19 @@ impl TryFrom> for Update { } } -impl From for Ipld { +impl From for arguments::Named { fn from(create: Update) -> Self { - create.into() + let mut named = arguments::Named::::new(); + + if let Some(path) = create.path { + named.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = create.args { + named.insert("args".to_string(), args.into()); + } + + named } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 2ad81030..f8682e02 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -27,6 +27,15 @@ pub enum Msg { Receive(Receive), } +impl From for arguments::Named { + fn from(msg: Msg) -> Self { + match msg { + Msg::Send(send) => send.into(), + Msg::Receive(receive) => receive.into(), + } + } +} + /// A promised version of the [`Msg`] ability. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedMsg { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index b37fe78c..c85dd4a7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -51,6 +52,16 @@ pub struct Send { pub message: String, } +impl From for arguments::Named { + fn from(send: Send) -> Self { + arguments::Named::from_iter([ + ("to".to_string(), send.to.into()), + ("from".to_string(), send.from.into()), + ("message".to_string(), send.message.into()), + ]) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 34f74d90..44bd43cf 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -36,6 +36,17 @@ impl ToCommand for Preset { } } +impl From for arguments::Named { + fn from(preset: Preset) -> Self { + match preset { + Preset::Crud(crud) => crud.into(), + Preset::Msg(msg) => msg.into(), + Preset::Ucan(ucan) => ucan.into(), + Preset::Wasm(wasm) => wasm.into(), + } + } +} + #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum PromisedPreset { Crud(PromisedCrud), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 4505fc77..b7ee1313 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -9,6 +9,7 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::fmt::Debug; /// The fully resolved variant: ready to execute. @@ -19,6 +20,12 @@ pub struct Revoke { // FIXME pub witness } +impl From for arguments::Named { + fn from(revoke: Revoke) -> Self { + arguments::Named::from_iter([("ucan".to_string(), Ipld::Link(revoke.ucan).into())]) + } +} + const COMMAND: &'static str = "/ucan/revoke"; impl Command for Revoke { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index dcbe97de..77f95abc 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -33,6 +33,16 @@ pub struct Run { pub args: Vec, } +impl From for arguments::Named { + fn from(run: Run) -> Self { + arguments::Named::from_iter([ + ("mod".into(), Ipld::from(run.module)), + ("fun".into(), run.function.into()), + ("args".into(), run.args.into()), + ]) + } +} + impl TryFrom> for Run { type Error = (); diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 07b18300..0cea6c02 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,3 +1,4 @@ +use crate::ability::arguments::Named; use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, @@ -6,14 +7,14 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; +use signature::SignatureEncoding; use signature::Verifier; -use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; pub trait Envelope: Sized { type DID: Did; - type Payload: Clone + Capsule + TryFrom + Into; + type Payload: Clone + Capsule + TryFrom> + Into>; type VarsigHeader: varsig::Header + Clone; type Encoder: Codec + TryFrom + Into; @@ -29,9 +30,11 @@ pub trait Envelope: Sized { ) -> Self; fn to_ipld_envelope(&self) -> Ipld { + let inner_args: Named = self.payload().clone().into(); + let inner_ipld: Ipld = inner_args.into(); + let wrapped_payload: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into(); let header_bytes: Vec = (*self.varsig_header()).clone().into(); let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); @@ -42,15 +45,15 @@ pub trait Envelope: Sized { fn try_from_ipld_envelope( ipld: Ipld, - ) -> Result>::Error>> { + ) -> Result>>::Error>> { if let Ipld::List(list) = ipld { if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(inner)) = ( + if let (1, Some(Ipld::Map(inner))) = ( btree.len(), btree.get(::TAG.into()), ) { - let payload = Self::Payload::try_from(inner.clone()) + let payload = Self::Payload::try_from(Named(inner.clone())) .map_err(FromIpldError::CannotParsePayload)?; let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) @@ -100,7 +103,8 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, // FIXME force it to be named args not IPLD + Ipld: Encode, + Named: From, { Self::try_sign_generic(signer, varsig_header, payload) } @@ -125,10 +129,14 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.into(), + Named::::from(payload.clone()).into(), + )]) + .into(); let mut buffer = vec![]; ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) @@ -155,12 +163,16 @@ pub trait Envelope: Sized { /// FIXME fn validate_signature(&self) -> Result<(), ValidateError> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { let mut encoded = vec![]; - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.to_string(), + Named::::from(self.payload().clone()).into(), + )]) + .into(); + ipld.encode( *varsig::header::Header::codec(self.varsig_header()), &mut encoded, diff --git a/src/delegation.rs b/src/delegation.rs index 67f873a0..e25aa5fe 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -21,6 +21,7 @@ mod payload; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{signature::Envelope, varsig, Nonce}, @@ -126,7 +127,8 @@ impl, C: Codec + Into + TryFrom> Delega impl + Clone, C: Codec + TryFrom + Into> Envelope for Delegation where - Payload: TryFrom, + Payload: TryFrom>, + Named: From>, { type DID = DID; type Payload = Payload; @@ -166,7 +168,7 @@ where impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation where - Payload: TryFrom, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -179,8 +181,8 @@ where impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> Deserialize<'de> for Delegation where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index c28cf6af..89f86a8b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,4 +1,5 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; +use crate::ability::arguments::Named; use crate::{ crypto::{signature::Envelope, varsig, Nonce}, did::Did, @@ -43,6 +44,8 @@ impl< > Agent<'a, DID, S, V, Enc> where Ipld: Encode, + Payload: TryFrom>, + Named: From>, { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { @@ -65,10 +68,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> - where - Payload: TryFrom, - { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -125,10 +125,7 @@ where &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> - where - Payload: TryFrom, - { + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f74e4a94..41d3048b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,12 @@ use super::policy::Predicate; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{varsig, Nonce}, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; +use core::str::FromStr; use derive_builder::Builder; use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -21,7 +23,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)] +#[derive(Debug, Clone, PartialEq, Builder)] // FIXME Serialize, Deserialize, Builder)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -110,17 +112,96 @@ impl Verifiable for Payload { } } -impl Deserialize<'de>> TryFrom for Payload { - type Error = SerdeError; +impl TryFrom> for Payload { + type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + fn try_from(args: Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut policy = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut not_before = None; + + for (k, ipld) in args { + match k.as_str() { + "sub" => { + subject = Some( + match ipld { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match ipld { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match ipld { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match ipld { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match ipld { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "pol" => match ipld { + Ipld::List(xs) => { + policy = xs + .iter() + .map(|x| Predicate::try_from(x.clone()).ok()) + .collect(); + } + _ => return Err(()), + }, + "metadata" => match ipld { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match ipld { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b).into()), + _ => return Err(()), + }, + "exp" => match ipld { + Ipld::Integer(i) => expiration = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + "nbf" => match ipld { + Ipld::Integer(i) => not_before = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + _ => (), + } + } + + Ok(Payload { + subject, + issuer: issuer.ok_or(())?, + audience: audience.ok_or(())?, + via, + command: command.ok_or(())?, + policy: policy.ok_or(())?, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + expiration: expiration.ok_or(())?, + not_before, + }) } } -impl From> for Ipld { +impl From> for Named { fn from(payload: Payload) -> Self { - let mut btree: BTreeMap = BTreeMap::::from_iter([ + let mut args = Named::::from_iter([ ("iss".to_string(), Ipld::from(payload.issuer.to_string())), ("aud".to_string(), payload.audience.to_string().into()), ("cmd".to_string(), payload.command.into()), @@ -133,20 +214,20 @@ impl From> for Ipld { ]); if let Some(subject) = payload.subject { - btree.insert("sub".to_string(), Ipld::from(subject.to_string())); + args.insert("sub".to_string(), Ipld::from(subject.to_string())); } else { - btree.insert("sub".to_string(), Ipld::Null); + args.insert("sub".to_string(), Ipld::Null); } if let Some(not_before) = payload.not_before { - btree.insert("nbf".to_string(), Ipld::from(not_before)); + args.insert("nbf".to_string(), Ipld::from(not_before)); } if !payload.metadata.is_empty() { - btree.insert("metadata".to_string(), Ipld::Map(payload.metadata)); + args.insert("meta".to_string(), Ipld::Map(payload.metadata)); } - Ipld::from(btree) + args } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index c73dbbf6..d222cc3e 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,11 +1,10 @@ use super::selector::filter::Filter; -use super::selector::{Select, SelectorError}; +use super::selector::{Select, Selector, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use multihash::Hasher; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; +use std::{fmt, str::FromStr}; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -13,7 +12,7 @@ use proptest::prelude::*; // FIXME Normal form? // FIXME exract domain gen selectors first? // FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub enum Predicate { // Comparison Equal(Select, ipld::Newtype), @@ -73,24 +72,24 @@ impl Harmonization { impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { - Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, - Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, - Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, - Predicate::LessThan(lhs, rhs_data) => lhs.resolve(data)? < rhs_data, - Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? <= rhs_data, - Predicate::Like(lhs, rhs_data) => glob(&lhs.resolve(data)?, &rhs_data), + Predicate::Equal(lhs, rhs_data) => lhs.get(data)? == rhs_data, + Predicate::GreaterThan(lhs, rhs_data) => lhs.get(data)? > rhs_data, + Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.get(data)? >= rhs_data, + Predicate::LessThan(lhs, rhs_data) => lhs.get(data)? < rhs_data, + Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.get(data)? <= rhs_data, + Predicate::Like(lhs, rhs_data) => glob(&lhs.get(data)?, &rhs_data), Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, Predicate::Every(xs, p) => xs - .resolve(data)? + .get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, Predicate::Some(xs, p) => { let pred = p.clone(); - xs.resolve(data)? + xs.get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? @@ -692,6 +691,91 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } +impl TryFrom for Predicate { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::List(v) => match v.as_slice() { + [Ipld::String(s), inner] if s == "not" => { + let inner = Box::new(Predicate::try_from(inner.clone())?); + Ok(Predicate::Not(inner)) + } + [Ipld::String(op_str), Ipld::String(sel_str), val] => match op_str.as_str() { + "==" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + Ok(Predicate::Equal(sel, ipld::Newtype(val.clone()))) + } + ">" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThan(sel, num)) + } + ">=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThanOrEqual(sel, num)) + } + "<" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThan(sel, num)) + } + "<=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThanOrEqual(sel, num)) + } + "like" => { + let sel = Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + if let Ipld::String(s) = val { + Ok(Predicate::Like(sel, s.to_string())) + } else { + Err(()) + } + } + "every" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Every(sel, p)) + } + "some" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Some(sel, p)) + } + _ => Err(()), + }, + [Ipld::String(op_str), lhs, rhs] => match op_str.as_str() { + "and" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::And(lhs, rhs)) + } + "or" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::Or(lhs, rhs)) + } + _ => Err(()), + }, + _ => Err(()), + }, + _ => Err(()), + } + } +} + impl From for Ipld { fn from(p: Predicate) -> Self { match p { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 965b89a1..e88cd17f 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -8,7 +8,9 @@ pub use error::{ParseError, SelectorErrorReason}; pub use select::Select; pub use selectable::Selectable; +use crate::ipld; use filter::Filter; +use libipld_core::ipld::Ipld; use nom::{ self, branch::alt, @@ -36,6 +38,86 @@ impl Selector { pub fn is_related(&self, other: &Selector) -> bool { self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) } + + // pub fn get(&self, ctx: &Ipld) -> Result { + // let ipld: Ipld = self + // .0 + // .iter() + // .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + // seen_ops.push(op); + // + // match op { + // Filter::Try(inner) => { + // let op: Filter = *inner.clone(); + // let ipld: Ipld = Select::Get::(vec![op]) + // .resolve(ctx) + // .unwrap_or(Ipld::Null); + // + // Ok((ipld, seen_ops)) + // } + // Filter::ArrayIndex(i) => { + // let result = { + // match ipld { + // Ipld::List(xs) => { + // if i.abs() as usize > xs.len() { + // return Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )); + // }; + // + // xs.get((xs.len() as i32 + *i) as usize) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )) + // .cloned() + // } + // // FIXME behaviour on maps? type error + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAList, + // )), + // } + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Field(k) => { + // let result = match ipld { + // Ipld::Map(xs) => xs + // .get(k) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::KeyNotFound, + // )) + // .cloned(), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAMap, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Values => { + // let result = match ipld { + // Ipld::List(xs) => Ok(Ipld::List(xs)), + // Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotACollection, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // } + // })? + // .0; + // + // Ok(ipld::Newtype(ipld)) + // } } pub fn parse(input: &str) -> IResult<&str, Selector> { diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index e9c32ae2..6d36c02b 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -3,112 +3,108 @@ use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorErro use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::str::FromStr; #[cfg(feature = "test_utils")] use proptest::prelude::*; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Select { - Get(Vec), - Pure(T), +#[derive(Debug, Clone, PartialEq)] +pub struct Select { + filters: Vec, + _marker: std::marker::PhantomData, } impl Select { + pub fn new(filters: Vec) -> Self { + Self { + filters, + _marker: std::marker::PhantomData, + } + } + pub fn is_related(&self, other: &Select) -> bool where Ipld: From + From, { - match (self, other) { - (Select::Pure(lhs_val), Select::Pure(rhs_val)) => { - Ipld::from(lhs_val.clone()) == Ipld::from(rhs_val.clone()) - } - (Select::Get(lhs_path), Select::Get(rhs_path)) => { - Selector(lhs_path.clone()).is_related(&Selector(rhs_path.clone())) - } - _ => false, - } + Selector(self.filters.clone()).is_related(&Selector(other.filters.clone())) } - pub fn resolve(self, ctx: &Ipld) -> Result { - match self { - Select::Pure(inner) => Ok(inner), - Select::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - Filter::Try(inner) => { - let op: Filter = *inner.clone(); - let ipld: Ipld = Select::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - Filter::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )); - }; - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( + + pub fn get(self, ctx: &Ipld) -> Result { + self.filters + .iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + Filter::Try(inner) => { + let op: Filter = *inner.clone(); + let ipld: Ipld = + Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + Filter::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::NotAList, - )), - } - }; + SelectorErrorReason::IndexOutOfBounds, + )); + }; - Ok((result?, seen_ops)) - } - Filter::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) + xs.get((xs.len() as i32 + *i) as usize) .ok_or(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::KeyNotFound, + SelectorErrorReason::IndexOutOfBounds, )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?, seen_ops)) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), } - Filter::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?, seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } + }; + + Ok((result?, seen_ops)) + } + Filter::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + Filter::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) } } @@ -117,28 +113,25 @@ where Ipld: From, { fn from(s: Select) -> Self { - match s { - Select::Get(ops) => Selector(ops).to_string().into(), - Select::Pure(inner) => inner.into(), - } + Selector(s.filters).to_string().into() + } +} + +impl FromStr for Select { + type Err = (); + + fn from_str(s: &str) -> Result { + let selector = Selector::from_str(s).map_err(|_| ())?; + Ok(Select { + filters: selector.0, + _marker: std::marker::PhantomData, + }) } } impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Select::Pure(inner), Select::Pure(other_inner)) => { - if inner == other_inner { - Some(Ordering::Equal) - } else { - None - } - } - (Select::Get(ops), Select::Get(other_ops)) => { - Selector(ops.clone()).partial_cmp(&Selector(other_ops.clone())) - } - _ => None, - } + Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 4b9373bb..5b958d5f 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,5 +1,5 @@ use super::Store; -use crate::crypto::signature::Envelope; +use crate::ability::arguments::Named; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, @@ -7,10 +7,7 @@ use crate::{ }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; +use std::collections::{BTreeMap, BTreeSet}; use web_time::SystemTime; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -113,8 +110,8 @@ impl< Enc: Codec + TryFrom + Into, > Store for MemoryStore where - Ipld: From>, - delegation::Payload: TryFrom, + Named: From>, + delegation::Payload: TryFrom>, Delegation: Encode, { type DelegationStoreError = String; // FIXME misisng @@ -270,6 +267,7 @@ where #[cfg(test)] mod tests { + use crate::ability::arguments::Named; use crate::ability::command::Command; use crate::crypto::signature::Envelope; use crate::delegation::store::Store; @@ -330,10 +328,8 @@ mod tests { .audience(server.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; // 2. server -a-> device let account_device_ucan = crate::Delegation::try_sign( @@ -345,10 +341,8 @@ mod tests { .audience(device.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), // I don't love this is now failable - ) - .expect("signature to work"); + .build()?, // I don't love this is now failable + )?; // 3. dnslink -d-> account let dnslink_ucan = crate::Delegation::try_sign( @@ -360,30 +354,64 @@ mod tests { .audience(account.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; #[derive(Debug, Clone, PartialEq)] - pub struct AccountInfo {} + pub struct AccountManage; - impl Command for AccountInfo { - const COMMAND: &'static str = "/account/info"; + use crate::invocation::promise::CantResolve; + use crate::invocation::promise::Resolvable; + use crate::ipld; + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } } - impl From for AccountInfo { - fn from(_: Ipld) -> Self { - AccountInfo {} + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() } } - impl From for Ipld { - fn from(_: AccountInfo) -> Self { - Ipld::Null + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } } } + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + // #[derive(Debug, Clone, PartialEq)] // pub struct DnsLinkUpdate { // pub cid: Cid, @@ -398,17 +426,15 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, - varsig_header, + varsig_header.clone(), crate::invocation::PayloadBuilder::default() .subject(account.clone()) .issuer(device.clone()) .audience(Some(server.clone())) - .ability(AccountInfo {}) + .ability(AccountManage) .proofs(vec![]) // FIXME - .build() - .expect("FIXME"), - ) - .expect("FIXME"); + .build()?, + )?; // FIXME reenable // let dnslink_invocation = crate::Invocation::try_sign( @@ -432,7 +458,7 @@ mod tests { varsig::encoding::Preset, > = Default::default(); - let agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); let _ = store.insert( account_device_ucan.cid().expect("FIXME"), @@ -461,11 +487,49 @@ mod tests { SystemTime::now(), ); + use crate::invocation::Agent; + let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let mut agent: Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + AccountManage, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + let observed = agent.receive(account_invocation, &SystemTime::now()); + assert!(observed.is_ok()); + + let not_account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(server.clone()) + .audience(Some(device.clone())) + .ability(AccountManage) + .proofs(vec![]) // FIXME + .build()?, + )?; + + let observed_other = agent.receive(not_account_invocation, &SystemTime::now()); + assert!(observed_other.is_err()); + Ok(()) } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 16fe3814..c6e91443 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,7 +8,14 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store< + + + + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, + > { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; diff --git a/src/did/traits.rs b/src/did/traits.rs index 53bebea9..76e9220f 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,9 +1,8 @@ use did_url::DID; use std::fmt; +use std::str::FromStr; -pub trait Did: - PartialEq + ToString + TryFrom + Into + signature::Verifier + Ord -{ +pub trait Did: PartialEq + ToString + FromStr + signature::Verifier + Ord { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/invocation.rs b/src/invocation.rs index 09eaad20..0d64902a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -21,6 +21,7 @@ pub mod store; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ crypto::{signature::Envelope, varsig}, @@ -60,6 +61,22 @@ pub struct Invocation< _marker: std::marker::PhantomData, } +impl< + A: Clone + ToCommand, + DID: Clone + did::Did, + V: Clone + varsig::Header, + C: Codec + TryFrom + Into, + > Encode for Invocation +where + Ipld: Encode, + Named: From + From>, + Payload: TryFrom>, +{ + fn encode(&self, c: C, w: &mut W) -> Result<(), libipld_core::error::Error> { + self.to_ipld_envelope().encode(c, w) + } +} + impl, C: Codec + TryFrom + Into> Invocation where @@ -133,8 +150,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn from(invocation: Invocation) -> Self { invocation.to_ipld_envelope() @@ -148,8 +165,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -193,8 +210,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -212,9 +229,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where - Ipld: From, - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Named: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4fad1b07..aefd5117 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,6 +4,7 @@ use super::{ store::Store, Invocation, }; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -33,8 +34,8 @@ use web_time::SystemTime; pub struct Agent< 'a, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, T: Resolvable + ToCommand = ability::preset::Preset, DID: Did = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, @@ -56,16 +57,17 @@ pub struct Agent< marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, P, D, T, DID, V, C> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, D, P, T, DID, V, C> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, delegation::Payload: Clone, T: Resolvable + ToCommand + Clone, T::Promised: Clone + ToCommand, DID: Did + Clone, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, V: varsig::Header + Clone, C: Codec + Into + TryFrom, { @@ -105,8 +107,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -153,8 +155,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -188,14 +190,14 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where - Ipld: From + From, - Payload: TryFrom, - arguments::Named: From, + arguments::Named: From + From, + Payload: TryFrom>, Invocation: Clone + Encode, -

) -> Self { destroy.into() @@ -227,19 +219,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { @@ -269,3 +248,14 @@ impl Resolvable for Ready { Ok(Ready { path }) } } + +impl From for Ready { + fn from(p: Promised) -> Ready { + Ready { + path: p + .path + .map(|inner_path| inner_path.try_resolve().ok()) + .flatten(), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 7bd237a6..555d7cbc 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -133,12 +133,14 @@ impl Delegable for Ready { impl From for Builder { fn from(promised: Promised) -> Self { Builder { - path: promised.path.map(Into::into), - args: promised.args.map(Into::into), + path: promised.path.and_then(|p| p.try_resolve().ok()), + args: promised.args.and_then(|p| p.try_resolve_option()), // FIXME this needs to read better } } } +// FIXME resolves vs resolvable is confusing + impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { read.into() @@ -216,19 +218,23 @@ impl From for arguments::Named { ); } - if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() - }) - .into(), - ); - } + // FIXME + // if let Some(args_res) = promised.args { + // let v = args_res.map(|a| { + // // FIXME extract + // a.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { + // acc.insert(*k, (*v).try_into().ok()?); + // Some(acc) + // }) + // }); + + // // match v { + // // + // // } + // // named.insert( + // // "args".to_string(), + // // ); + // } named } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 64654b15..5ce08700 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -172,18 +172,6 @@ impl From for Ready { } } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(Into::into), - args: promised.args.and_then(|res| match res.try_resolve() { - Ok(args) => Some(args.into()), - Err(unresolved) => None, - }), - } - } -} - impl From for Ipld { fn from(create: Ready) -> Self { create.into() @@ -202,12 +190,12 @@ impl TryFrom for Ready { Ok(Ready { path: map .get("path") - .map(|ipld| (ipld::Newtype(*ipld)).try_into().map_err(|_| ())) + .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) .transpose()?, args: map .get("args") - .map(|ipld| (*ipld).try_into().map_err(|_| ())) + .map(|ipld| ipld.clone().try_into().map_err(|_| ())) .transpose()?, }) } else { @@ -277,12 +265,14 @@ impl From for arguments::Named { named.insert( "args".to_string(), args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() + .try_resolve() + .expect("FIXME") + .iter() + .try_fold(BTreeMap::new(), |mut map, (k, v)| { + map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); // FIXME double check + Some(map) }) + .expect("FIXME") .into(), ); } @@ -291,19 +281,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { @@ -352,3 +329,12 @@ impl Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(|p| p.try_resolve().ok()), + args: promised.args.and_then(|a| a.try_resolve_option()), + } + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index cc8d6c35..e0ff7556 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -83,6 +83,15 @@ impl Resolvable for Ready { } } +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => Builder::Send(send.into()), + Promised::Receive(receive) => Builder::Receive(receive.into()), + } + } +} + impl CheckSame for Builder { type Error = (); // FIXME @@ -103,7 +112,6 @@ impl CheckParents for Builder { match (self, proof) { (Builder::Send(this), Any) => this.check_parent(&Any), (Builder::Receive(this), Any) => this.check_parent(&Any), - _ => Err(()), } } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 3897a65f..05701fb2 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -46,7 +46,7 @@ pub struct Receive { pub from: Option, } -pub type Builder = Receive; +pub(super) type Builder = Receive; // FIXME needs promisory version @@ -58,13 +58,13 @@ impl Delegable for Receive { type Builder = Receive; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// from: promised.from.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + from: promised.from.and_then(|x| x.try_resolve_option()), + } + } +} impl Checkable for Receive { type Hierarchy = Parentful; @@ -103,19 +103,21 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq)] pub struct Promised { - pub from: promise::Resolves>, + pub from: Option>>, } impl From for arguments::Named { fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); - match promised.from { - promise::Resolves::Ok(from) => { - args.insert("from".into(), from.into()); - } - promise::Resolves::Err(from) => { - args.insert("from".into(), from.into()); + if let Some(from) = promised.from { + match from { + promise::Resolves::Ok(from) => { + args.insert("from".into(), from.into()); + } + promise::Resolves::Err(from) => { + args.insert("from".into(), from.into()); + } } } @@ -127,8 +129,16 @@ impl Resolvable for Receive { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - promise::Resolves::try_resolve(p.from) - .map(|from| Receive { from }) - .map_err(|from| Promised { from }) + match &p.from { + None => Ok(Receive { from: None }), + Some(promise::Resolves::Ok(promiseOk)) => match promiseOk.clone().try_resolve() { + Ok(from) => Ok(Receive { from }), + Err(_from) => Err(Promised { from: p.from }), + }, + Some(promise::Resolves::Err(promiseErr)) => match promiseErr.clone().try_resolve() { + Ok(from) => Ok(Receive { from }), + Err(_from) => Err(Promised { from: p.from }), + }, + } } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 6a523f1b..6bf60a75 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -152,3 +152,13 @@ impl Resolvable for Ready { } } } + +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Crud(promised) => Builder::Crud(promised.into()), + Promised::Msg(promised) => Builder::Msg(promised.into()), + Promised::Wasm(promised) => Builder::Wasm(promised.into()), + } + } +} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index c2d28dc5..ac3aacb8 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -31,13 +31,13 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// ucan: promised.ucan.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + ucan: promised.ucan.try_resolve().ok(), + } + } +} impl Resolvable for Ready { type Promised = Promised; diff --git a/src/delegation/store.rs b/src/delegation/store.rs index af4ceb3f..91502f10 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -130,7 +130,7 @@ pub struct MemoryStore { impl Store for MemoryStore where - B::Hierarchy: Into>, + B::Hierarchy: Into> + Clone, { type Error = (); // FIXME misisng @@ -203,7 +203,7 @@ where } for condition in &conditions { - if !condition.validate(&d.payload.ability_builder.into()) { + if !condition.validate(&d.payload.ability_builder.clone().into()) { return ControlFlow::Continue(()); } } diff --git a/src/invocation.rs b/src/invocation.rs index 4d33d418..522acfc6 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -17,7 +17,7 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID>; +pub type Invocation = signature::Envelope, DID>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index a3ff1d88..6751bef5 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -31,7 +31,7 @@ pub struct Agent< > { pub did: &'a DID, - pub delegation_store: &'a D, + pub delegation_store: &'a mut D, pub invocation_store: &'a mut S, pub unresolved_promise_store: &'a mut P, pub resolved_promise_store: &'a mut P, @@ -49,13 +49,16 @@ impl< P: Store, D: delegation::store::Store, > Agent<'a, T, C, DID, S, P, D> +where + T::Promised: Clone, + // Payload<::Hierarchy, DID>: Clone, // FIXME + delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( did: &'a DID, signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - revocation_store: &'a mut D, unresolved_promise_store: &'a mut P, resolved_promise_store: &'a mut P, ) -> Self { @@ -63,7 +66,6 @@ impl< did, invocation_store, delegation_store, - revocation_store, unresolved_promise_store, resolved_promise_store, signer, @@ -85,7 +87,7 @@ impl< ) -> Result, ()> { let proofs = self .delegation_store - .get_chain(self.did, subject, &ability.into(), vec![], now) + .get_chain(self.did, subject, &ability.clone().into(), vec![], now) .map_err(|_| ())? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -109,7 +111,7 @@ impl< } pub fn receive( - &self, + &mut self, promised: Invocation, now: &SystemTime, // FIXME return type @@ -120,7 +122,7 @@ impl< { // FIXME needs varsig header let mut buffer = vec![]; - Ipld::from(promised) + Ipld::from(promised.clone()) .encode(DagCborCodec, &mut buffer) .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); @@ -143,12 +145,12 @@ impl< .verify( &encoded, &match promised.signature { - Signature::Solo(sig) => sig, + Signature::Solo(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; - let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability) { + let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability.clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store @@ -167,16 +169,16 @@ impl< .get_many(&promised.payload.proofs) .map_err(|_| ())? .into_iter() - .map(|d| d.payload) + .map(|d| d.payload.clone()) .collect(); - let resolved_payload = promised.payload.map_ability(|_| resolved_ability); + let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); - delegation::Payload::::from(resolved_payload) + delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) .map_err(|_| ())?; - if promised.payload.audience != Some(*self.did) { + if promised.payload.audience != Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); } @@ -202,7 +204,7 @@ impl< .get_chain( subject, self.did, - &ability.into(), + &ability.clone().into(), vec![], &SystemTime::now(), ) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index cf5de2b9..cdfade84 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -42,7 +42,7 @@ pub struct Payload { pub nonce: Nonce, pub not_before: Option, - pub expiration: Option, + pub expiration: Option, // FIXME this field may not make sense } // FIXME cleanup traits @@ -66,7 +66,7 @@ impl Payload { metadata: self.metadata, nonce: self.nonce, not_before: self.not_before, - expiration: self.expiration, + expiration: None, } } @@ -105,7 +105,7 @@ impl From> nonce: payload.nonce, not_before: payload.not_before, - expiration: payload.expiration, + expiration: SystemTime::now().into(), // FIXME } } } @@ -122,7 +122,6 @@ impl, DID: Did> From> for arguments::N Ipld::List(payload.proofs.iter().map(Into::into).collect()), ), ("nonce".into(), payload.nonce.into()), - ("exp".into(), payload.expiration.into()), ]); if let Some(audience) = payload.audience { @@ -133,6 +132,10 @@ impl, DID: Did> From> for arguments::N args.insert("nbf".into(), not_before.into()); } + if let Some(expiration) = payload.expiration { + args.insert("exp".into(), expiration.into()); + } + args } } @@ -285,8 +288,9 @@ struct InternalSerializer { #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] not_before: Option, - #[serde(rename = "exp")] - expiration: Timestamp, + + #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] + expiration: Option, } impl From for Ipld { @@ -370,7 +374,7 @@ impl>, DID: Did> From nonce: payload.nonce, not_before: payload.not_before, - expiration: payload.expiration, + expiration: None, } } } @@ -378,9 +382,10 @@ impl>, DID: Did> From impl Encode for Payload where Ipld: Encode, + Payload: Clone, // FIXME { fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - let ipld: Ipld = (*self).into(); + let ipld = Ipld::from(self.clone()); ipld.encode(codec, writer) } } diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 216939aa..026308ee 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -24,6 +24,13 @@ impl Resolves> { }, } } + + pub fn try_resolve_option(self) -> Option { + match self { + Resolves::Ok(p_ok) => p_ok.try_resolve().ok()?, + Resolves::Err(p_err) => p_err.try_resolve().ok()?, + } + } } impl Resolves { diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 9196f041..945aa5a5 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -52,7 +52,10 @@ pub trait PromiseIndex { invocation: Cid, ) -> Result<(), Self::PromiseIndexError>; - fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError>; + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseIndexError>; } pub struct MemoryPromiseIndex { @@ -73,15 +76,18 @@ impl PromiseIndex for MemoryPromiseIndex { Ok(()) } - fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError> { + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseIndexError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on .iter() .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { - let next = self.index.get(cid).ok_or(NotFound)?; + let next = self.index.get(cid).ok_or(())?; - let reduced = acc.intersection(*next.into()).collect(); + let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); if reduced.is_empty() { return Err(()); } diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index c0752c25..71297e6c 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -42,6 +42,7 @@ pub struct PostOrderIpldIter<'a, T> { outbound: Vec>, } +// FIXME not sure if &'a worth it because nbow I'm cloning everywhere #[derive(Clone, Debug, PartialEq)] pub enum Item<'a, T> { Node(&'a Enriched), @@ -59,7 +60,7 @@ impl<'a, T> PostOrderIpldIter<'a, T> { } } -impl<'a, T> IntoIterator for &'a Enriched { +impl<'a, T: Clone> IntoIterator for &'a Enriched { type Item = Item<'a, T>; type IntoIter = PostOrderIpldIter<'a, T>; @@ -68,9 +69,9 @@ impl<'a, T> IntoIterator for &'a Enriched { } } -impl<'a, T: Clone> FromIterator> for &'a Enriched { +impl<'a, T: Clone> FromIterator> for Enriched { fn from_iter>>(it: I) -> Self { - &it.into_iter().fold(Enriched::Null, |acc, x| match x { + it.into_iter().fold(Enriched::Null, |acc, x| match x { Item::Node(Enriched::Null) => Enriched::Null, Item::Node(Enriched::Bool(b)) => Enriched::Bool(*b), Item::Node(Enriched::Integer(i)) => Enriched::Integer(*i), @@ -97,29 +98,29 @@ impl<'a, T: Clone> FromIterator> for &'a Enriched { } } -impl<'a, T> From<&'a Enriched> for PostOrderIpldIter<'a, T> { +impl<'a, T: Clone> From<&'a Enriched> for PostOrderIpldIter<'a, T> { fn from(enriched: &'a Enriched) -> Self { PostOrderIpldIter::new(enriched) } } -impl<'a, T> Iterator for PostOrderIpldIter<'a, T> { +impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { type Item = Item<'a, T>; fn next(&mut self) -> Option { loop { match self.inbound.pop() { None => return self.outbound.pop(), - Some(map @ Item::Node(Enriched::Map(btree))) => { - self.outbound.push(map); + Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { + self.outbound.push(map.clone()); for node in btree.values() { self.inbound.push(Item::Inner(node)); } } - Some(list @ Item::Node(Enriched::List(vector))) => { - self.outbound.push(list); + Some(ref list @ Item::Node(Enriched::List(ref vector))) => { + self.outbound.push(list.clone()); for node in vector { self.inbound.push(Item::Inner(node)); diff --git a/src/signature.rs b/src/signature.rs index 81a3b85e..cb8b0152 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -8,13 +8,11 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use signature::SignatureEncoding; use std::collections::BTreeMap; // FIXME #[cfg(feature = "dag-cbor")] use libipld_cbor::DagCborCodec; -use signature::Signer; +use signature::{SignatureEncoding, Signer}; pub trait Verifiable { fn verifier<'a>(&'a self) -> &'a DID; @@ -36,7 +34,7 @@ pub struct Envelope + Capsule, DID: Did> { pub payload: T, } -impl + Into, DID: Did> Envelope { +impl + Into + Clone, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } @@ -51,7 +49,7 @@ impl + Into, DID: Did> Envelope { where Ipld: Encode, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; ipld.encode(codec, &mut buffer) @@ -67,8 +65,8 @@ impl + Into, DID: Did> Envelope { pub fn validate_signature(&self) -> Result<(), ()> { // FIXME need varsig - let codec = todo!(); - let hasher = todo!(); + let codec = DagCborCodec; + let hasher = Code::Sha2_256; let mut buffer = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); @@ -82,7 +80,7 @@ impl + Into, DID: Did> Envelope { .expect("FIXME expect signing to work..."), ); - match self.signature { + match &self.signature { Signature::Solo(sig) => self .verifier() .verify(&cid.to_bytes(), &sig) @@ -119,23 +117,24 @@ pub enum Signature { impl + Capsule + Into, DID: Did> Encode for Envelope where Ipld: Encode, + Envelope: Clone, // FIXME? { fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - Ipld::from(*self).encode(codec, writer) + Ipld::from((*self).clone()).encode(codec, writer) } } -// impl> From> for Ipld { -// fn from(signature: Signature) -> Self { -// match signature { -// Signature::Solo(sig) => sig.into(), -// // Signature::Batch { -// // signature, -// // merkle_proof, -// // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), -// } -// } -// } +impl From> for Ipld { + fn from(signature: Signature) -> Self { + match signature { + Signature::Solo(sig) => sig.to_vec().into(), + // Signature::Batch { + // signature, + // merkle_proof, + // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), + } + } +} impl + Capsule + Into, DID: Did> From> for Ipld { fn from(Envelope { signature, payload }: Envelope) -> Self { From 70ff160861c17d65db74ee98d02a346a4022bda4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 16:59:56 -0800 Subject: [PATCH 143/234] Fix linter warnings, starting to remove commented out sections --- src/ability/arguments/named.rs | 4 +- src/ability/crud/any.rs | 2 - src/ability/crud/create.rs | 26 +- src/ability/crud/destroy.rs | 15 +- src/ability/crud/read.rs | 13 +- src/ability/crud/update.rs | 13 +- src/ability/dynamic.rs | 4 +- src/ability/msg.rs | 6 +- src/ability/msg/receive.rs | 15 +- src/ability/preset.rs | 13 +- src/agent.rs | 155 ---- src/builder.rs | 307 -------- src/delegation.rs | 2 +- src/delegation/agent.rs | 3 +- src/delegation/error.rs | 17 +- src/delegation/payload.rs | 10 +- src/delegation/store.rs | 2 - src/did.rs | 49 +- src/did_verifier.rs | 105 --- src/did_verifier/did_key.rs | 271 ------- src/error.rs | 49 -- src/invocation.rs | 5 +- src/invocation/agent.rs | 9 +- src/invocation/payload.rs | 3 +- src/invocation/promise.rs | 2 +- src/invocation/promise/any.rs | 4 +- src/invocation/store.rs | 10 +- src/ipld/enriched.rs | 1 - src/ipld/promised.rs | 10 +- src/lib.rs | 108 +-- src/plugins.rs | 251 ------- src/plugins/ucan.rs | 392 ---------- src/plugins/wnfs.rs | 389 ---------- src/proof/internal.rs | 3 +- src/proof/parentless.rs | 4 +- src/proof/prove.rs | 6 +- src/prove/traits/internal/checker.rs | 1 - src/reader.rs | 39 +- src/receipt.rs | 4 +- src/semantics/ability.rs | 54 -- src/semantics/caveat.rs | 125 --- src/semantics/mod.rs | 5 - src/semantics/resource.rs | 27 - src/signature.rs | 3 +- src/store.rs | 92 --- src/test_utils/mod.rs | 5 - src/test_utils/rvg.rs | 63 -- src/time.rs | 6 + src/ucan.rs | 1044 -------------------------- src/wasm.rs | 310 -------- 50 files changed, 127 insertions(+), 3929 deletions(-) delete mode 100644 src/agent.rs delete mode 100644 src/builder.rs delete mode 100644 src/did_verifier.rs delete mode 100644 src/did_verifier/did_key.rs delete mode 100644 src/error.rs delete mode 100644 src/plugins.rs delete mode 100644 src/plugins/ucan.rs delete mode 100644 src/plugins/wnfs.rs delete mode 100644 src/prove/traits/internal/checker.rs delete mode 100644 src/semantics/ability.rs delete mode 100644 src/semantics/caveat.rs delete mode 100644 src/semantics/mod.rs delete mode 100644 src/semantics/resource.rs delete mode 100644 src/store.rs delete mode 100644 src/test_utils/mod.rs delete mode 100644 src/test_utils/rvg.rs delete mode 100644 src/ucan.rs delete mode 100644 src/wasm.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 1cf45018..2e467503 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,5 +1,5 @@ -use crate::{invocation::promise, ipld}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use crate::ipld; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use thiserror::Error; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index d28d867a..077d1d70 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -60,8 +60,6 @@ pub struct Any { pub path: Option, } -use crate::ability::command::ParseAbility; - impl Command for Any { const COMMAND: &'static str = "crud/*"; } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index dff88b40..622cb06a 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,18 +1,15 @@ //! Create new resources. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance @@ -255,19 +252,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 68fd7b39..ec487632 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,18 +1,15 @@ //! Destroy a resource. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 555d7cbc..6c2ce833 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,19 +1,16 @@ //! Read from a resource. -use super::{any as crud, error::ProofError, parents::MutableParents}; +use super::any as crud; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 5ce08700..fdb81752 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,17 +1,14 @@ //! Update existing resources. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::Serialize; use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -182,7 +179,7 @@ impl TryFrom for Ready { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { + if let Ipld::Map(map) = ipld { if map.len() > 2 { return Err(()); // FIXME } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 64e7af1e..d28b4610 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -4,10 +4,10 @@ use super::{ arguments, command::{ParseAbility, ToCommand}, }; -use crate::{ipld, proof::same::CheckSame}; +use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, convert::Infallible, fmt::Debug}; +use std::{convert::Infallible, fmt::Debug}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ability/msg.rs b/src/ability/msg.rs index e0ff7556..01e1ee0a 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -108,10 +108,10 @@ impl CheckParents for Builder { type Parents = Any; type ParentError = (); - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Any) -> Result<(), Self::ParentError> { match (self, proof) { - (Builder::Send(this), Any) => this.check_parent(&Any), - (Builder::Receive(this), Any) => this.check_parent(&Any), + (Builder::Send(this), any) => this.check_parent(&any), + (Builder::Receive(this), any) => this.check_parent(&any), } } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 05701fb2..1c152c9f 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -82,8 +82,15 @@ impl CheckParents for Receive { type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - // self.from.check_same(&proof.from).map_err(|_| ()) - todo!() // FIXME + if let Some(from) = &self.from { + if let Some(proof_from) = &proof.from { + if from != &url::Newtype(proof_from.clone()) { + return Err(()); + } + } + } + + Ok(()) } } @@ -131,11 +138,11 @@ impl Resolvable for Receive { fn try_resolve(p: Promised) -> Result { match &p.from { None => Ok(Receive { from: None }), - Some(promise::Resolves::Ok(promiseOk)) => match promiseOk.clone().try_resolve() { + Some(promise::Resolves::Ok(promise_ok)) => match promise_ok.clone().try_resolve() { Ok(from) => Ok(Receive { from }), Err(_from) => Err(Promised { from: p.from }), }, - Some(promise::Resolves::Err(promiseErr)) => match promiseErr.clone().try_resolve() { + Some(promise::Resolves::Err(promise_err)) => match promise_err.clone().try_resolve() { Ok(from) => Ok(Receive { from }), Err(_from) => Err(Promised { from: p.from }), }, diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 6bf60a75..48ebb30b 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,18 +1,11 @@ use super::{crud, msg, wasm}; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, + ability::{arguments, command::ParseAbility}, delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, - same::CheckSame, - }, + invocation::Resolvable, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { diff --git a/src/agent.rs b/src/agent.rs deleted file mode 100644 index 7192f3bd..00000000 --- a/src/agent.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{ - delegation, - delegation::{condition::Condition, Delegable, Delegation}, - did::Did, - invocation, - nonce::Nonce, - proof::checkable::Checkable, - receipt, - receipt::Responds, - time::JsTime, -}; -use libipld_core::ipld::Ipld; -use nonempty::NonEmpty; -use std::{collections::BTreeMap, marker::PhantomData}; -use web_time::SystemTime; - -// FIXME move proofs to under delegation? - -#[derive(Debug, Clone, PartialEq)] -pub struct Agent< - T: Delegable + Responds, - C: Condition, - S: delegation::store::Store - + invocation::store::Store - + receipt::store::Store, -> { - pub did: Did, - // pub key: signature::Key, - pub store: S, - pub _phantom: PhantomData<(T, C)>, -} - -impl< - T: Delegable + Responds, - C: Condition + Clone, - S: delegation::store::Store - + invocation::store::Store - + receipt::store::Store, - > Agent -{ - fn new(did: Did, store: S) -> Self { - Self { - did, - store, - _phantom: PhantomData, - } - } - - pub fn delegate( - &self, - audience: Did, - subject: Did, - ability_builder: T::Builder, - new_conditions: Vec, - metadata: BTreeMap, - expiration: JsTime, - not_before: Option, - ) -> Result, ()> { - // FIXME check if possible in store first; - - let conditions = if subject == self.did { - new_conditions - } else { - let mut conds = self - .store - .get_chain(&self.did, &subject, &ability_builder, SystemTime::now()) - .map_err(|_| ())? // FIXME - .first() - .1 - .payload - .conditions; - - let mut new = new_conditions; - conds.append(&mut new); - conds - }; - - let mut salt = self.did.clone().to_string().into_bytes(); - - let payload = delegation::Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - conditions, - metadata, - nonce: Nonce::generate_12(&mut salt), - expiration: expiration.into(), - not_before: not_before.map(Into::into), - }; - - Ok(self.sign_delegation(payload)) - } - - pub fn sign_delegation( - &self, - payload: delegation::Payload, - ) -> delegation::Delegation { - // FIXME check if possible in store first; - let signature = todo!(); // self.key.sign(payload); - Delegation { payload, signature } - } - - pub fn receive_delegation() {} -} - -// impl Agent { -// } -// -// -// -// -// -// pub fn delegate(&self, payload: Payload) -> Delegation { -// let signature = self.key.sign(payload); -// signature::Envelope::new(payload, signature) -// } - -// pub fn invoke( -// &self, -// delegation: Delegation, -// proof_chain: Vec>, // FIXME T must also accept Self and * -// ) -> () -// where -// T::Parents: Delegable, -// { -// todo!() -// } - -// pub fn try_invoke(&self, ability: A) { -// todo!() -// } - -// pub fn revoke( -// &self, -// delegation: Delegation, -// ) -> () -// // where -// // T::Parents: Delegable, -// { -// todo!() -// } - -// pub fn receive_delegation( -// &self, -// delegation: Delegation, -// ) -> () { -// todo!() -// } - -// pub fn receive_invocation(&self, invocation: Invocation) -> () { -// todo!() -// } - -// pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index ef062361..00000000 --- a/src/builder.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! A builder for creating UCANs - -use async_signature::AsyncSigner; -use cid::multihash; -use serde::{de::DeserializeOwned, Serialize}; -use signature::Signer; - -use crate::{ - capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, - crypto::{JWSSignature, SignerDid}, - error::Error, - time, - ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, - CidString, DefaultFact, -}; - -/// A builder for creating UCANs -#[derive(Debug, Clone)] -pub struct UcanBuilder { - version: Option, - audience: Option, - nonce: Option, - capabilities: Capabilities, - lifetime: Option, - expiration: Option, - not_before: Option, - facts: Option, - proofs: Option>, -} - -impl Default for UcanBuilder { - fn default() -> Self { - Self { - version: Default::default(), - audience: Default::default(), - nonce: Default::default(), - capabilities: Default::default(), - lifetime: Default::default(), - expiration: Default::default(), - not_before: Default::default(), - facts: Default::default(), - proofs: Default::default(), - } - } -} - -impl UcanBuilder -where - F: Clone + Serialize, - C: CapabilityParser, -{ - /// Set the UCAN version - pub fn version(mut self, version: &str) -> Self { - self.version = Some(version.to_string()); - self - } - - /// Set the audience of the UCAN - pub fn for_audience(mut self, audience: impl AsRef) -> Self { - self.audience = Some(audience.as_ref().to_string()); - self - } - - /// Set the nonce of the UCAN - pub fn with_nonce(mut self, nonce: impl AsRef) -> Self { - self.nonce = Some(nonce.as_ref().to_string()); - self - } - - /// Set the lifetime of the UCAN - pub fn with_lifetime(mut self, seconds: u64) -> Self { - self.lifetime = Some(seconds); - self - } - - /// Set the expiration of the UCAN - pub fn with_expiration(mut self, timestamp: u64) -> Self { - self.expiration = Some(timestamp); - self - } - - /// Set the not before of the UCAN - pub fn not_before(mut self, timestamp: u64) -> Self { - self.not_before = Some(timestamp); - self - } - - /// Set the fact of the UCAN - pub fn with_fact(mut self, fact: F) -> Self { - self.facts = Some(fact); - self - } - - /// Add a witness to the proofs of the UCAN - pub fn witnessed_by( - mut self, - authority: &Ucan, - hasher: Option, - ) -> Self - where - F2: Clone + DeserializeOwned, - C2: CapabilityParser, - { - match authority.to_cid(hasher) { - Ok(cid) => { - self.proofs - .get_or_insert(Default::default()) - .push(CidString(cid)); - } - Err(e) => panic!("Failed to add authority: {}", e), - } - - self - } - - /// Claim a capability for the UCAN - pub fn claiming_capability(mut self, capability: Capability) -> Self { - self.capabilities.push(capability); - self - } - - /// Claim multiple capabilities for the UCAN - pub fn claiming_capabilities(mut self, capabilities: &[Capability]) -> Self { - self.capabilities.extend_from_slice(capabilities); - self - } - - /// Sign the UCAN with the given signer - pub fn sign(self, signer: &S) -> Result, Error> - where - S: Signer + SignerDid, - K: JWSSignature, - { - let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); - - let issuer = signer.did().map_err(|e| Error::SigningError { - msg: format!("failed to construct DID, {}", e), - })?; - - let Some(audience) = self.audience else { - return Err(Error::SigningError { - msg: "an audience is required".to_string(), - }); - }; - - let header = jose_b64::serde::Json::new(UcanHeader { - alg: K::ALGORITHM.to_string(), - typ: "JWT".to_string(), - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let expiration = match (self.expiration, self.lifetime) { - (None, None) => None, - (None, Some(lifetime)) => Some(time::now() + lifetime), - (Some(expiration), None) => Some(expiration), - (Some(_), Some(_)) => { - return Err(Error::SigningError { - msg: "only one of expiration or lifetime may be set".to_string(), - }) - } - }; - - let payload = jose_b64::serde::Json::new(UcanPayload { - ucv: version, - iss: issuer, - aud: audience, - exp: expiration, - nbf: self.not_before, - nnc: self.nonce, - cap: self.capabilities, - fct: self.facts, - prf: self.proofs, - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let header_b64 = serde_json::to_value(&header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload_b64 = serde_json::to_value(&payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - let signature = signer.sign(signed_data.as_bytes()).to_vec().into(); - - Ok(Ucan:: { - header, - payload, - signature, - }) - } - - /// Sign the UCAN with the given async signer - pub async fn sign_async(self, signer: &S) -> Result, Error> - where - S: AsyncSigner + SignerDid, - K: JWSSignature + 'static, - { - let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); - - let issuer = signer.did().map_err(|e| Error::SigningError { - msg: format!("failed to construct DID, {}", e), - })?; - - let Some(audience) = self.audience else { - return Err(Error::SigningError { - msg: "an audience is required".to_string(), - }); - }; - - let header = jose_b64::serde::Json::new(UcanHeader { - alg: K::ALGORITHM.to_string(), - typ: "JWT".to_string(), - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let expiration = match (self.expiration, self.lifetime) { - (None, None) => None, - (None, Some(lifetime)) => Some(time::now() + lifetime), - (Some(expiration), None) => Some(expiration), - (Some(_), Some(_)) => { - return Err(Error::SigningError { - msg: "only one of expiration or lifetime may be set".to_string(), - }) - } - }; - - let payload = jose_b64::serde::Json::new(UcanPayload { - ucv: version, - iss: issuer, - aud: audience, - exp: expiration, - nbf: self.not_before, - nnc: self.nonce, - cap: self.capabilities, - fct: self.facts, - prf: self.proofs, - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let header_b64 = serde_json::to_value(&header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload_b64 = serde_json::to_value(&payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - let signature = signer - .sign_async(signed_data.as_bytes()) - .await - .map_err(|e| Error::SigningError { msg: e.to_string() })? - .to_vec() - .into(); - - Ok(Ucan:: { - header, - payload, - signature, - }) - } -} - -#[cfg(test)] -mod tests { - use signature::rand_core; - use std::str::FromStr; - - use crate::did_verifier::DidVerifierMap; - - use super::*; - - #[test] - fn test_round_trip_validate() -> Result<(), anyhow::Error> { - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .sign(&iss_key)?; - - let token = ucan.encode()?; - let decoded: Ucan = Ucan::from_str(&token)?; - - assert!(decoded.validate(0, &did_verifier_map).is_ok()); - - Ok(()) - } -} diff --git a/src/delegation.rs b/src/delegation.rs index f248ef26..eea99dd2 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope, DID>; +pub type Delegation = signature::Envelope, DID>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 87769369..23019ab0 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,6 +5,7 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; +#[derive(Debug)] pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { pub did: &'a DID, pub store: &'a mut S, @@ -21,7 +22,7 @@ impl< B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store, + S: Store + Clone, > Agent<'a, B, C, DID, S> { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { diff --git a/src/delegation/error.rs b/src/delegation/error.rs index 1ff31a12..21bafe1e 100644 --- a/src/delegation/error.rs +++ b/src/delegation/error.rs @@ -1,3 +1,5 @@ +// FIXME rename this is not for the sign envelope +#[derive(Debug, Clone, PartialEq, Eq)] pub enum EnvelopeError { InvalidSubject, MisalignedIssAud, @@ -6,24 +8,13 @@ pub enum EnvelopeError { } // FIXME Error, etc +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DelegationError { Envelope(EnvelopeError), FailedCondition, // FIXME add context? - SemanticError(Semantic), // // FIXME these are duplicated in Outcome - // // - // /// An error in the command chain. - // CommandEscelation, - - // /// An error in the argument chain. - // ArgumentEscelation(ArgErr), - - // /// An error in the proof chain. - // InvalidProofChain(ChainErr), - - // /// An error in the parents. - // InvalidParents(ParentErr), + SemanticError(Semantic), } impl From for DelegationError { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5e83b655..0047767c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,7 +8,6 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, - did, did::Did, nonce::Nonce, proof::{ @@ -365,15 +364,16 @@ impl From> for Ipld { } } -impl Payload { - pub fn check<'a>( - &'a self, +impl>, C: Condition, DID: Did + Clone> + Payload +{ + pub fn check( + &self, proofs: Vec>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, - T: Clone + Checkable + Prove + Into>, { let start: Acc = Acc { issuer: self.issuer.clone(), diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 91502f10..184e2f96 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -8,8 +8,6 @@ use libipld_core::{cid::Cid, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, - convert::Infallible, - fmt, ops::ControlFlow, }; use web_time::SystemTime; diff --git a/src/did.rs b/src/did.rs index 30784caf..493cc901 100644 --- a/src/did.rs +++ b/src/did.rs @@ -10,41 +10,42 @@ use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; -#[cfg(feature = "eddsa")] -use ed25519_dalek; - -#[cfg(feature = "es256")] -use p256; - -#[cfg(feature = "es256k")] -use k256; - -#[cfg(feature = "es384")] -use p384; - -#[cfg(feature = "es512")] -use crate::crypto::p521; - -#[cfg(feature = "rs256")] -use crate::crypto::rs256; - -#[cfg(feature = "rs512")] -use crate::crypto::rs512; - -#[cfg(feature = "bls")] -use blst; +// #[cfg(feature = "eddsa")] +// use ed25519_dalek; +// +// #[cfg(feature = "es256")] +// use p256; +// +// #[cfg(feature = "es256k")] +// use k256; +// +// #[cfg(feature = "es384")] +// use p384; +// +// #[cfg(feature = "es512")] +// use crate::crypto::p521; +// +// #[cfg(feature = "rs256")] +// use crate::crypto::rs256; +// +// #[cfg(feature = "rs512")] +// use crate::crypto::rs512; +// +// #[cfg(feature = "bls")] +// use blst; pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; - type Signer: signature::Signer; + type Signer: signature::Signer + fmt::Debug; } // impl Did for ed25519_dalek::VerifyingKey {} // //impl Did for key::Verifier {} // +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Preset { Key(key::Verifier), // Dns(did_url::DID), diff --git a/src/did_verifier.rs b/src/did_verifier.rs deleted file mode 100644 index 14a2f47a..00000000 --- a/src/did_verifier.rs +++ /dev/null @@ -1,105 +0,0 @@ -// //! DID verifier methods - -use core::fmt; -use std::collections::HashMap; - -use crate::error::Error; - -#[cfg(feature = "did-key")] -pub mod did_key; - -/// A map from did method to verifier -#[derive(Debug)] -pub struct DidVerifierMap { - map: HashMap>, -} - -impl Default for DidVerifierMap { - fn default() -> Self { - #[allow(unused_mut)] - let mut did_verifier_map = Self { - map: HashMap::new(), - }; - - #[cfg(feature = "did-key")] - did_verifier_map.register(did_key::DidKeyVerifier::default()); - - did_verifier_map - } -} - -impl DidVerifierMap { - /// Register a verifier - pub fn register(&mut self, verifier: V) -> &mut Self - where - V: DidVerifier + 'static, - { - self.map - .insert(verifier.method().to_string(), Box::new(verifier)); - self - } - - /// Register a verifier that's already boxed - pub fn register_box(&mut self, verifier: Box) -> &mut Self { - self.map.insert(verifier.method().to_string(), verifier); - self - } - - /// Verify a signature using the registered verifier for the given method - pub fn verify( - &self, - method: &str, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), Error> { - self.map - .get(method) - .ok_or_else(|| Error::VerifyingError { - msg: format!("Unrecognized DID method, {}", method), - })? - .verify(identifier, payload, signature) - .map_err(|e| Error::VerifyingError { msg: e.to_string() }) - } -} - -impl FromIterator> for DidVerifierMap { - fn from_iter>>(iter: T) -> Self { - let mut map = Self::default(); - for verifier in iter { - map.register_box(verifier); - } - - map - } -} - -impl Extend> for DidVerifierMap { - fn extend>>(&mut self, iter: T) { - for verifier in iter { - self.register_box(verifier); - } - } -} - -/// A trait for implementing DID method verification -pub trait DidVerifier { - /// The DID method for this verifier - fn method(&self) -> &'static str; - - /// Verify a signature - fn verify( - &self, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), anyhow::Error>; -} - -impl fmt::Debug for dyn DidVerifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DidVerifier") - .field("method", &self.method()) - .finish() - } -} diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs deleted file mode 100644 index 426df184..00000000 --- a/src/did_verifier/did_key.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! did:key method verifier - -#[cfg(feature = "eddsa-verifier")] -use crate::crypto::eddsa::eddsa_verifier; -#[cfg(feature = "es256-verifier")] -use crate::crypto::es256::es256_verifier; -#[cfg(feature = "es256k-verifier")] -use crate::crypto::es256k::es256k_verifier; -#[cfg(feature = "es384-verifier")] -use crate::crypto::es384::es384_verifier; -#[cfg(feature = "ps256-verifier")] -use crate::crypto::ps256::ps256_verifier; -#[cfg(feature = "rs256-verifier")] -use crate::crypto::rs256::rs256_verifier; - -use core::fmt; -use std::{any::TypeId, collections::HashMap}; - -use anyhow::anyhow; -use multibase::Base; - -use super::DidVerifier; - -/// A closure for verifying a signature -pub type SignatureVerifier = dyn Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error>; - -/// did:key method verifier -pub struct DidKeyVerifier { - /// map from type id of signature to verifier function - verifier_map: HashMap>, -} - -impl Default for DidKeyVerifier { - fn default() -> Self { - #[allow(unused_mut)] - let mut did_key_verifier = Self { - verifier_map: HashMap::new(), - }; - - #[cfg(feature = "eddsa-verifier")] - did_key_verifier.set::(eddsa_verifier); - - #[cfg(feature = "es256-verifier")] - did_key_verifier.set::(es256_verifier); - - #[cfg(feature = "es256k-verifier")] - did_key_verifier.set::(es256k_verifier); - - #[cfg(feature = "es384-verifier")] - did_key_verifier.set::(es384_verifier); - - #[cfg(feature = "ps256-verifier")] - did_key_verifier.set::(ps256_verifier); - - #[cfg(feature = "rs256-verifier")] - did_key_verifier.set::(rs256_verifier); - - did_key_verifier - } -} - -impl DidKeyVerifier { - /// set verifier function for type `T` - pub fn set(&mut self, f: F) -> &mut Self - where - T: 'static, - F: Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error> + 'static, - { - self.verifier_map.insert(TypeId::of::(), Box::new(f)); - self - } - - /// check if verifier function for type `T` is set - pub fn has(&self) -> bool - where - T: 'static, - { - self.verifier_map.contains_key(&TypeId::of::()) - } -} - -impl DidVerifier for DidKeyVerifier { - fn method(&self) -> &'static str { - "key" - } - - fn verify( - &self, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), anyhow::Error> { - let (base, data) = multibase::decode(identifier).map_err(|e| anyhow!(e))?; - - let Base::Base58Btc = base else { - return Err(anyhow!("expected base58btc, got {:?}", base)); - }; - - let (multicodec, public_key) = - unsigned_varint::decode::u128(&data).map_err(|e| anyhow!(e))?; - - let multicodec_pub_key = MulticodecPubKey::try_from(multicodec)?; - - multicodec_pub_key.validate_pub_key_len(public_key)?; - - #[allow(unreachable_patterns)] - let verifier = match multicodec_pub_key { - #[cfg(feature = "es256k")] - MulticodecPubKey::Secp256k1Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "eddsa")] - MulticodecPubKey::X25519 => return Err(anyhow!("x25519 not supported for signing")), - #[cfg(feature = "eddsa")] - MulticodecPubKey::Ed25519 => self.verifier_map.get(&TypeId::of::()), - #[cfg(feature = "es256")] - MulticodecPubKey::P256Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "es384")] - MulticodecPubKey::P384Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "es512")] - MulticodecPubKey::P521Compressed => self - .verifier_map - .get(&TypeId::of::>()), - #[cfg(feature = "rs256")] - MulticodecPubKey::RSAPKCS1 => self - .verifier_map - .get(&TypeId::of::()), - _ => Option::<&Box>::None, - } - .ok_or_else(|| anyhow!("no registered verifier for signature type"))?; - - verifier(public_key, payload, signature) - } -} - -impl fmt::Debug for DidKeyVerifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DidKeyVerifier").finish() - } -} - -/// Multicodec public key -#[derive(Debug)] -pub enum MulticodecPubKey { - /// secp256k1 compressed public key - #[cfg(feature = "es256k")] - Secp256k1Compressed, - /// x25519 public key - #[cfg(feature = "eddsa")] - X25519, - /// ed25519 public key - #[cfg(feature = "eddsa")] - Ed25519, - /// p256 compressed public key - #[cfg(feature = "es256")] - P256Compressed, - /// p384 compressed public key - #[cfg(feature = "es384")] - P384Compressed, - /// p521 compressed public key - #[cfg(feature = "es512")] - P521Compressed, - /// rsa pkcs1 public key - #[cfg(feature = "rs256")] - RSAPKCS1, -} - -impl MulticodecPubKey { - #[allow(unused_variables)] - fn validate_pub_key_len(&self, pub_key: &[u8]) -> Result<(), anyhow::Error> { - #[allow(unreachable_patterns)] - match self { - #[cfg(feature = "es256k")] - MulticodecPubKey::Secp256k1Compressed => { - if pub_key.len() != 33 { - return Err(anyhow!( - "expected 33 bytes for secp256k1 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "eddsa")] - MulticodecPubKey::X25519 => { - if pub_key.len() != 32 { - return Err(anyhow!( - "expected 32 bytes for x25519 public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "eddsa")] - MulticodecPubKey::Ed25519 => { - if pub_key.len() != 32 { - return Err(anyhow!( - "expected 32 bytes for ed25519 public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es256")] - MulticodecPubKey::P256Compressed => { - if pub_key.len() != 33 { - return Err(anyhow!( - "expected 33 bytes for p256 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es384")] - MulticodecPubKey::P384Compressed => { - if pub_key.len() != 49 { - return Err(anyhow!( - "expected 49 bytes for p384 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es512")] - MulticodecPubKey::P521Compressed => { - if pub_key.len() > 67 { - return Err(anyhow!( - "expected <= 67 bytes for p521 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "rs256")] - MulticodecPubKey::RSAPKCS1 => match pub_key.len() { - 94 | 126 | 162 | 226 | 294 | 422 | 546 => {} - n => { - return Err(anyhow!( - "expected 94, 126, 162, 226, 294, 422, or 546 bytes for RSA PKCS1 public key, got {}", - n - )); - } - }, - _ => return Err(anyhow!("unsupported public key type")), - }; - - #[allow(unreachable_code)] - Ok(()) - } -} - -impl TryFrom for MulticodecPubKey { - type Error = anyhow::Error; - - fn try_from(value: u128) -> Result { - match value { - #[cfg(feature = "es256k")] - 0xe7 => Ok(MulticodecPubKey::Secp256k1Compressed), - #[cfg(feature = "eddsa")] - 0xec => Ok(MulticodecPubKey::X25519), - #[cfg(feature = "eddsa")] - 0xed => Ok(MulticodecPubKey::Ed25519), - #[cfg(feature = "es256")] - 0x1200 => Ok(MulticodecPubKey::P256Compressed), - #[cfg(feature = "es384")] - 0x1201 => Ok(MulticodecPubKey::P384Compressed), - #[cfg(feature = "es512")] - 0x1202 => Ok(MulticodecPubKey::P521Compressed), - #[cfg(feature = "rs256")] - 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), - _ => Err(anyhow!("unsupported multicodec")), - } - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index a67fb364..00000000 --- a/src/error.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Error types for UCAN - -use thiserror::Error; - -/// Error types for UCAN -#[derive(Error, Debug)] -pub enum Error { - /// Parsing errors - #[error("An error occurred while parsing the token: {msg}")] - TokenParseError { - /// Error message - msg: String, - }, - /// Verification errors - #[error("An error occurred while verifying the token: {msg}")] - VerifyingError { - /// Error message - msg: String, - }, - /// Signing errors - #[error("An error occurred while signing the token: {msg}")] - SigningError { - /// Error message - msg: String, - }, - /// Plugin errors - #[error(transparent)] - PluginError(PluginError), - /// Internal errors - #[error("An unexpected error occurred in ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/ucan/issues")] - InternalUcanError { - /// Error message - msg: String, - }, -} - -/// Error types for plugins -#[derive(Error, Debug)] -#[error(transparent)] -pub struct PluginError { - #[from] - inner: anyhow::Error, -} - -impl From for Error { - fn from(inner: anyhow::Error) -> Self { - Self::PluginError(PluginError { inner }) - } -} diff --git a/src/invocation.rs b/src/invocation.rs index 522acfc6..3eff3a76 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -19,15 +19,12 @@ use crate::{ability, did, did::Did, signature}; /// `Invocation>`. pub type Invocation = signature::Envelope, DID>; -// FIXME rename -pub type PromisedInvocation = Invocation; - // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; impl Invocation { - fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { + pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { let mut payload = self.payload; payload.ability = f(payload.ability); Self { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 6751bef5..0c96c26a 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -17,9 +17,9 @@ use libipld_core::{ multihash::{Code, MultihashGeneric}, }; use std::{collections::BTreeMap, marker::PhantomData}; -use thiserror::Error; use web_time::SystemTime; +#[derive(Debug)] pub struct Agent< 'a, T: Resolvable + Delegable, @@ -190,7 +190,7 @@ where subject: &DID, cause: Option, cid: Cid, - now: &JsTime, + now: JsTime, // FIXME return type ) -> Result, ()> where @@ -206,7 +206,7 @@ where self.did, &ability.clone().into(), vec![], - &SystemTime::now(), + &now.into(), ) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) @@ -219,7 +219,7 @@ where audience: Some(self.did.clone()), ability, proofs, - cause: None, + cause, metadata: BTreeMap::new(), nonce: Nonce::generate_12(&mut vec![]), expiration: None, @@ -233,6 +233,7 @@ where } } +#[derive(Debug)] pub enum Recipient { You(T), Other(T), diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index cdfade84..6d89672b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -11,11 +11,10 @@ use crate::{ }; use anyhow; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, error::SerdeError, ipld::Ipld, - multihash::{Code, MultihashGeneric}, serde as ipld_serde, }; use serde::{Serialize, Serializer}; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index d74bf61d..aaa9ad83 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -7,7 +7,7 @@ mod err; mod ok; mod resolves; -pub mod js; +// FIXME pub mod js; pub use any::PromiseAny; pub use err::PromiseErr; diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 81e56c48..e37d1e87 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -185,7 +185,7 @@ impl TryFrom> for PromiseOk { fn try_from(p_any: PromiseAny) -> Result, Self::Error> { match p_any { PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), - PromiseAny::Rejected(err) => Err(()), + PromiseAny::Rejected(_err) => Err(()), PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), } } @@ -196,7 +196,7 @@ impl TryFrom> for PromiseErr { fn try_from(p_any: PromiseAny) -> Result, Self::Error> { match p_any { - PromiseAny::Fulfilled(val) => Err(()), + PromiseAny::Fulfilled(_val) => Err(()), PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 945aa5a5..e168836e 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,10 +1,7 @@ use super::Invocation; use crate::{did::Did, invocation::Resolvable}; -use libipld_core::{cid::Cid, link::Link}; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; +use libipld_core::cid::Cid; +use std::collections::{BTreeMap, BTreeSet}; use thiserror::Error; pub trait Store { @@ -58,6 +55,7 @@ pub trait PromiseIndex { ) -> Result, Self::PromiseIndexError>; } +#[derive(Debug, Clone, PartialEq)] pub struct MemoryPromiseIndex { pub index: BTreeMap>, } @@ -84,7 +82,7 @@ impl PromiseIndex for MemoryPromiseIndex { None => BTreeSet::new(), Some(first) => waiting_on .iter() - .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { + .try_fold(BTreeSet::from_iter([first]), |acc, cid| { let next = self.index.get(cid).ok_or(())?; let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 71297e6c..3cb6616d 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,4 +1,3 @@ -use crate::invocation::Resolvable; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 83ae2029..322c2db1 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,10 +1,6 @@ -use super::enriched::{Enriched, Item}; -use crate::ability::arguments; -use crate::invocation::{ - promise::{self, Promise, PromiseAny, PromiseErr, PromiseOk}, - Resolvable, // FIXME this shoudl be under promise -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use super::enriched::Enriched; +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; /// A promise to recursively resolve to an [`Ipld`] value. diff --git a/src/lib.rs b/src/lib.rs index dd97bfe2..c9d44ab7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,13 @@ #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] +#![warn( + missing_debug_implementations, + future_incompatible, + let_underscore, + // FIXME missing_docs, + rust_2021_compatibility, + nonstandard_style, +)] +// FIXME consider removing for Prove #![deny(unreachable_pub)] //! ucan @@ -7,68 +15,11 @@ #[cfg(target_arch = "wasm32")] extern crate alloc; -// use std::str::FromStr; -// -// use cid::{multihash, Cid}; -// use serde::{de, Deserialize, Deserializer, Serialize}; -// -// pub mod builder; -// pub mod capability; -// pub mod crypto; -// pub mod error; -// pub mod plugins; -// pub mod semantics; -// pub mod store; -// pub mod ucan; -// -// #[cfg(target_arch = "wasm32")] -// mod wasm; -// -// #[cfg(target_arch = "wasm32")] -// pub use wasm::*; -// -// #[doc(hidden)] -// #[cfg(not(target_arch = "wasm32"))] -// pub use linkme; -// -// /// The default multihash algorithm used for UCANs -// pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; -// -// /// A decentralized identifier. -// pub type Did = String; -// -// use std::fmt::Debug; - -// FIXME concrete abilitiy types in addition to promised version - -// impl DelegationPayload { -// fn check( -// &self, -// proof: &DelegationPayload + Capability, Cond>, -// now: SystemTime, -// ) -> Result<(), ()> { -// // FIXME heavily WIP -// // FIXME signature -// self.check_time(now).unwrap(); -// self.check_issuer(&proof.audience)?; // FIXME alignment -// self.check_subject(&proof.subject)?; -// self.check_conditions(&proof.conditions)?; -// -// proof.check_expiration(now)?; -// proof.check_not_before(now)?; -// -// self.check_ability(&proof.capability_builder)?; -// Ok(()) -// } -// } - pub mod ability; -pub mod crypto; -// pub mod agent; FIXME put back? pub mod capsule; +pub mod crypto; pub mod delegation; pub mod did; -// pub mod did_verifier; pub mod invocation; pub mod ipld; pub mod nonce; @@ -84,42 +35,3 @@ pub mod url; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; - -// FIXME consider a fact-system -// /// The empty fact -// #[derive(Debug, Clone, Default, Serialize, Deserialize)] -// pub struct EmptyFact {} -// -// /// The default fact -// pub type DefaultFact = EmptyFact; -// -// /// A newtype around Cid that (de)serializes as a string -// #[derive(Debug, Clone)] -// pub struct CidString(pub(crate) Cid); -// -// impl Serialize for CidString { -// fn serialize(&self, serializer: S) -> Result -// where -// S: serde::Serializer, -// { -// serializer.serialize_str(self.0.to_string().as_str()) -// } -// } -// -// impl<'de> Deserialize<'de> for CidString { -// fn deserialize(deserializer: D) -> Result -// where -// D: Deserializer<'de>, -// { -// let s = String::deserialize(deserializer)?; -// -// Cid::from_str(&s) -// .map(CidString) -// .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) -// } -// } -// -// /// Test utilities. -// #[cfg(any(test, feature = "test_utils"))] -// #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] -// pub mod test_utils; diff --git a/src/plugins.rs b/src/plugins.rs deleted file mode 100644 index 6f19c827..00000000 --- a/src/plugins.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! Plugins for definining custom semantics - -#[cfg(not(target_arch = "wasm32"))] -use linkme::distributed_slice; - -use core::fmt; -use downcast_rs::{impl_downcast, Downcast}; -use std::sync::RwLock; -use url::Url; - -use crate::{ - error::Error, - semantics::{ - ability::{Ability, TopAbility}, - caveat::{Caveat, EmptyCaveat}, - resource::Resource, - }, -}; - -pub mod ucan; -pub mod wnfs; - -#[cfg(not(target_arch = "wasm32"))] -#[distributed_slice] -#[doc(hidden)] -pub static STATIC_PLUGINS: [&dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, ->] = [..]; - -type ErasedPlugin = dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, ->; - -lazy_static::lazy_static! { - static ref RUNTIME_PLUGINS: RwLock> = RwLock::new(Vec::new()); -} - -/// A plugin for handling a specific scheme -pub trait Plugin: Send + Sync + Downcast + 'static { - /// The type of resource this plugin handles - type Resource; - - /// The type of ability this plugin handles - type Ability; - - /// The type of caveat this plugin handles - type Caveat; - - /// The type of error this plugin may return - type Error; - - /// The scheme this plugin handles - fn scheme(&self) -> &'static str; - - /// Handle a resource - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error>; - - /// Handle an ability - fn try_handle_ability( - &self, - resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error>; - - /// Handle a caveat - fn try_handle_caveat( - &self, - resource: &Self::Resource, - ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error>; -} - -impl_downcast!(Plugin assoc Resource, Ability, Caveat, Error); - -/// A wrapped plugin that unifies plugin error handling, and handles common semantics, such -/// as top abilities. -pub struct WrappedPlugin -where - R: 'static, - A: 'static, - C: 'static, - E: 'static, -{ - #[doc(hidden)] - pub inner: &'static dyn Plugin, -} - -impl Plugin for WrappedPlugin -where - R: Resource, - A: Ability, - C: Caveat, - E: Into, -{ - type Resource = Box; - type Ability = Box; - type Caveat = Box; - - type Error = Error; - - fn scheme(&self) -> &'static str { - self.inner.scheme() - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - self.inner.try_handle_resource(resource_uri).map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |r| Ok(r.map(|r| Box::new(r) as Box)), - ) - } - - fn try_handle_ability( - &self, - resource: &Self::Resource, - ability: &str, - ) -> Result>, Self::Error> { - if ability == "*" { - return Ok(Some(Box::new(TopAbility))); - } - - let Some(resource) = resource.downcast_ref::() else { - return Ok(None); - }; - - self.inner - .try_handle_ability(resource, ability) - .map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |a| Ok(a.map(|a| Box::new(a) as Box)), - ) - } - - fn try_handle_caveat( - &self, - resource: &Self::Resource, - ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - let Some(resource) = resource.downcast_ref::() else { - return Ok(None); - }; - - if ability.is::() { - return Ok(Some(Box::new( - erased_serde::deserialize::(deserializer) - .map_err(|e| anyhow::anyhow!(e))?, - ))); - } - - let Some(ability) = ability.downcast_ref::() else { - return Ok(None); - }; - - self.inner - .try_handle_caveat(resource, ability, deserializer) - .map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |c| Ok(c.map(|c| Box::new(c) as Box)), - ) - } -} - -impl fmt::Debug for WrappedPlugin { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WrappedPlugin") - .field("scheme", &self.inner.scheme()) - .finish() - } -} - -/// Get an iterator over all plugins -pub fn plugins() -> impl Iterator< - Item = &'static dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, - >, -> { - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - RUNTIME_PLUGINS - .read() - .expect("plugin lock poisoned") - .clone() - .into_iter() - } else { - let static_plugins = STATIC_PLUGINS.iter().copied(); - let runtime_plugins = RUNTIME_PLUGINS - .read() - .expect("plugin lock poisoned") - .clone() - .into_iter(); - - static_plugins.chain(runtime_plugins) - } - } -} - -/// Register a plugin -pub fn register_plugin( - plugin: &'static dyn Plugin, -) where - R: Resource, - A: Ability, - C: Caveat, - E: Into, -{ - let erased = Box::new(WrappedPlugin { inner: plugin }); - let leaked = Box::leak::<'static>(erased); - - RUNTIME_PLUGINS - .write() - .expect("plugin lock poisoned") - .push(leaked); -} - -/// Register a plugin at compile time -#[cfg(not(target_arch = "wasm32"))] -#[macro_export] -macro_rules! register_plugin { - ($name:ident, $plugin:expr) => { - #[$crate::linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] - static $name: &'static dyn $crate::plugins::Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = $crate::error::Error, - > = &$crate::plugins::WrappedPlugin { inner: $plugin }; - }; -} - -/// Register a plugin at compile time -#[cfg(target_arch = "wasm32")] -#[macro_export] -macro_rules! register_plugin { - ($name:ident, $plugin:expr) => {}; -} diff --git a/src/plugins/ucan.rs b/src/plugins/ucan.rs deleted file mode 100644 index 9190d590..00000000 --- a/src/plugins/ucan.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! A plugin for handling the `ucan` scheme. - -use std::fmt::Display; - -use cid::Cid; -use url::Url; - -use crate::{ - semantics::{ability::Ability, caveat::EmptyCaveat}, - Did, -}; - -use super::{Plugin, Resource}; - -/// A plugin for handling the `ucan` scheme. -#[derive(Debug)] -pub struct UcanPlugin; - -crate::register_plugin!(UCAN, &UcanPlugin); - -impl Plugin for UcanPlugin { - type Resource = UcanResource; - type Ability = UcanAbilityDelegation; - type Caveat = EmptyCaveat; - - type Error = anyhow::Error; - - fn scheme(&self) -> &'static str { - "ucan" - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - // TODO: I'm not handling the OwnedBy or OwnedByWithScheme cases yet, - // because the spec probably needs to be modified to treat the DID as - // a literal, by wrapping it in square brackets, to avoid parsing issues - // from treating it as an authority with a port. - match resource_uri.path() { - "*" => Ok(Some(UcanResource::AllProvable)), - "./*" => Ok(Some(UcanResource::LocallyProvable)), - path => { - if let Ok(cid) = Cid::try_from(path) { - return Ok(Some(UcanResource::ByCid(cid))); - } - - Ok(None) - } - } - } - - fn try_handle_ability( - &self, - _resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error> { - match ability { - "ucan/*" => Ok(Some(UcanAbilityDelegation)), - _ => Ok(None), - } - } - - fn try_handle_caveat( - &self, - _resource: &Self::Resource, - _ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) - } -} - -/// A resource for the `ucan` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum UcanResource { - /// ucan: - ByCid(Cid), - /// ucan:* - AllProvable, - /// ucan:./* - LocallyProvable, - /// ucan:///* - OwnedBy(Did), - /// ucan:/// - OwnedByWithScheme(Did, String), -} - -impl Display for UcanResource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let hier_part = match self { - UcanResource::ByCid(cid) => cid.to_string(), - UcanResource::AllProvable => "*".to_string(), - UcanResource::LocallyProvable => "./*".to_string(), - UcanResource::OwnedBy(did) => format!("//{}/*", did), - UcanResource::OwnedByWithScheme(did, scheme) => format!("//{}/{}", did, scheme), - }; - - f.write_fmt(format_args!("ucan:{}", hier_part)) - } -} - -impl Resource for UcanResource { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - if let Some(resource) = other.downcast_ref::() { - return self == resource; - }; - - false - } -} - -/// The UCAN delegation ability from the `ucan` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct UcanAbilityDelegation; - -impl Ability for UcanAbilityDelegation { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - if let Some(ability) = other.downcast_ref::() { - return self == ability; - }; - - false - } -} - -impl Display for UcanAbilityDelegation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ucan/*") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plugin_scheme() { - assert_eq!(UcanPlugin.scheme(), "ucan"); - } - - #[test] - fn test_plugin_try_handle_resource_by_cid() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" - )?)) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_all_provable() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:*")?)?; - - assert_eq!(resource, Some(UcanResource::AllProvable)); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_locally_provable() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:./*")?)?; - - assert_eq!(resource, Some(UcanResource::LocallyProvable)); - - Ok(()) - } - - #[test] - #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] - fn test_plugin_try_handle_resource_owned_by() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string() - )) - ); - - Ok(()) - } - - #[test] - #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] - fn test_plugin_try_handle_resource_owned_with_scheme() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string() - )) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_ability_delegation() -> anyhow::Result<()> { - let ability = UcanPlugin.try_handle_ability(&UcanResource::AllProvable, "ucan/*")?; - - assert_eq!(ability, Some(UcanAbilityDelegation)); - - Ok(()) - } - - #[test] - fn test_resource_by_cid_display() -> anyhow::Result<()> { - let resource = UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?); - - assert_eq!( - resource.to_string(), - "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" - ); - - Ok(()) - } - - #[test] - fn test_resource_all_provable_display() { - let resource = UcanResource::AllProvable; - - assert_eq!(resource.to_string(), "ucan:*"); - } - - #[test] - fn test_resource_locally_provable_display() { - let resource = UcanResource::LocallyProvable; - - assert_eq!(resource.to_string(), "ucan:./*"); - } - - #[test] - fn test_resource_owned_by_display() { - let resource = UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - ); - - assert_eq!( - resource.to_string(), - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*" - ); - } - - #[test] - fn test_resource_owned_by_with_scheme_display() { - let resource = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string(), - ); - - assert_eq!( - resource.to_string(), - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs" - ); - } - - #[test] - fn test_ability_delegation_display() { - let ability = UcanAbilityDelegation; - - assert_eq!(ability.to_string(), "ucan/*"); - } - - #[test] - fn test_resource_attenuation() -> anyhow::Result<()> { - let all_provable = UcanResource::AllProvable; - let locally_provable = UcanResource::LocallyProvable; - - let by_cid_1 = UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?); - - let by_cid_2 = UcanResource::ByCid(Cid::try_from( - "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", - )?); - - let owned_by_1 = UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - ); - - let owned_by_2 = UcanResource::OwnedBy("did:example:123456789abcdefghi".to_string()); - - let owned_by_with_scheme_1 = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string(), - ); - - let owned_by_with_scheme_2 = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "ucan".to_string(), - ); - - assert!(all_provable.is_valid_attenuation(&all_provable)); - assert!(!all_provable.is_valid_attenuation(&locally_provable)); - assert!(!all_provable.is_valid_attenuation(&by_cid_1)); - assert!(!all_provable.is_valid_attenuation(&by_cid_2)); - assert!(!all_provable.is_valid_attenuation(&owned_by_1)); - assert!(!all_provable.is_valid_attenuation(&owned_by_2)); - assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!locally_provable.is_valid_attenuation(&all_provable)); - assert!(locally_provable.is_valid_attenuation(&locally_provable)); - assert!(!locally_provable.is_valid_attenuation(&by_cid_1)); - assert!(!locally_provable.is_valid_attenuation(&by_cid_2)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_1)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_2)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!by_cid_1.is_valid_attenuation(&all_provable)); - assert!(!by_cid_1.is_valid_attenuation(&locally_provable)); - assert!(by_cid_1.is_valid_attenuation(&by_cid_1)); - assert!(!by_cid_1.is_valid_attenuation(&by_cid_2)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_1)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_2)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!by_cid_2.is_valid_attenuation(&all_provable)); - assert!(!by_cid_2.is_valid_attenuation(&locally_provable)); - assert!(!by_cid_2.is_valid_attenuation(&by_cid_1)); - assert!(by_cid_2.is_valid_attenuation(&by_cid_2)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_1)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_2)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_1.is_valid_attenuation(&all_provable)); - assert!(!owned_by_1.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_1.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_1.is_valid_attenuation(&by_cid_2)); - assert!(owned_by_1.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_2.is_valid_attenuation(&all_provable)); - assert!(!owned_by_2.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_2.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_2.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_1)); - assert!(owned_by_2.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&all_provable)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_2)); - assert!(owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&all_provable)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - Ok(()) - } - - #[test] - fn test_ability_attenuation() -> anyhow::Result<()> { - let ability = UcanAbilityDelegation; - - assert!(ability.is_valid_attenuation(&ability)); - - Ok(()) - } -} diff --git a/src/plugins/wnfs.rs b/src/plugins/wnfs.rs deleted file mode 100644 index 66e3045b..00000000 --- a/src/plugins/wnfs.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! A plugin for handling the `wnfs` scheme. - -use std::fmt::Display; - -use crate::semantics::{ability::Ability, caveat::EmptyCaveat, resource::Resource}; -use url::Url; - -use super::Plugin; - -/// A plugin for handling the `wnfs` scheme. -#[derive(Debug)] -pub struct WnfsPlugin; - -crate::register_plugin!(WNFS, &WnfsPlugin); - -impl Plugin for WnfsPlugin { - type Resource = WnfsResource; - type Ability = WnfsAbility; - type Caveat = EmptyCaveat; - - type Error = anyhow::Error; - - fn scheme(&self) -> &'static str { - "wnfs" - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - let Some(user) = resource_uri.host_str() else { - return Ok(None); - }; - - let Some(path_segments) = resource_uri.path_segments() else { - return Ok(None); - }; - - match path_segments.collect::>().as_slice() { - ["public", path @ ..] => Ok(Some(WnfsResource::PublicPath { - user: user.to_string(), - path: path.iter().map(|s| s.to_string()).collect(), - })), - ["private", ..] => todo!(), - _ => Ok(None), - } - } - - fn try_handle_ability( - &self, - _resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error> { - match ability { - "wnfs/create" => Ok(Some(WnfsAbility::Create)), - "wnfs/revise" => Ok(Some(WnfsAbility::Revise)), - "wnfs/soft_delete" => Ok(Some(WnfsAbility::SoftDelete)), - "wnfs/overwrite" => Ok(Some(WnfsAbility::Overwrite)), - "wnfs/super_user" => Ok(Some(WnfsAbility::SuperUser)), - _ => Ok(None), - } - } - - fn try_handle_caveat( - &self, - _resource: &Self::Resource, - _ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) - } -} - -/// A resource for the `wnfs` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum WnfsResource { - /// wnfs:///public/ - PublicPath { - /// The user - user: String, - /// The path - path: Vec, - }, - /// wnfs:///private/ - PrivatePath { - /// The user - user: String, - }, // TODO -} - -impl Resource for WnfsResource { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - let Some(other) = other.downcast_ref::() else { - return false; - }; - - match self { - WnfsResource::PublicPath { user, path } => { - let WnfsResource::PublicPath { - user: other_user, - path: other_path, - } = other - else { - return false; - }; - - if user != other_user { - return false; - } - - path.strip_prefix(other_path.as_slice()).is_some() - } - WnfsResource::PrivatePath { .. } => todo!(), - } - } -} - -impl Display for WnfsResource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WnfsResource::PublicPath { user, path } => { - f.write_fmt(format_args!("wnfs://{}/public/{}", user, path.join("/"))) - } - - WnfsResource::PrivatePath { .. } => todo!(), - } - } -} - -/// An ability for the `wnfs` scheme. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum WnfsAbility { - /// wnfs/create - Create, - /// wnfs/revise - Revise, - /// wnfs/soft_delete - SoftDelete, - /// wnfs/overwrite - Overwrite, - /// wnfs/super_user - SuperUser, -} - -impl Ability for WnfsAbility { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - let Some(other) = other.downcast_ref::() else { - return false; - }; - - self <= other - } -} - -impl Display for WnfsAbility { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WnfsAbility::Create => f.write_str("wnfs/create"), - WnfsAbility::Revise => f.write_str("wnfs/revise"), - WnfsAbility::SoftDelete => f.write_str("wnfs/soft_delete"), - WnfsAbility::Overwrite => f.write_str("wnfs/overwrite"), - WnfsAbility::SuperUser => f.write_str("wnfs/super_user"), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plugin_scheme() { - assert_eq!(WnfsPlugin.scheme(), "wnfs"); - } - - #[test] - fn test_plugin_try_handle_resource_public() -> anyhow::Result<()> { - let resource = - WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/public/path/to/file")?)?; - - assert_eq!( - resource, - Some(WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["path".to_string(), "to".to_string(), "file".to_string()], - }) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_invalid() -> anyhow::Result<()> { - let resource = - WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/invalid/path/to/file")?)?; - - assert_eq!(resource, None); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_ability_public() -> anyhow::Result<()> { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["path".to_string(), "to".to_string(), "file".to_string()], - }; - - let ability_create = WnfsPlugin.try_handle_ability(&resource, "wnfs/create")?; - let ability_revise = WnfsPlugin.try_handle_ability(&resource, "wnfs/revise")?; - let ability_soft_delete = WnfsPlugin.try_handle_ability(&resource, "wnfs/soft_delete")?; - let ability_overwrite = WnfsPlugin.try_handle_ability(&resource, "wnfs/overwrite")?; - let ability_super_user = WnfsPlugin.try_handle_ability(&resource, "wnfs/super_user")?; - let ability_invalid = WnfsPlugin.try_handle_ability(&resource, "wnfs/not-an-ability")?; - - assert_eq!(ability_create, Some(WnfsAbility::Create)); - assert_eq!(ability_revise, Some(WnfsAbility::Revise)); - assert_eq!(ability_soft_delete, Some(WnfsAbility::SoftDelete)); - assert_eq!(ability_overwrite, Some(WnfsAbility::Overwrite)); - assert_eq!(ability_super_user, Some(WnfsAbility::SuperUser)); - assert_eq!(ability_invalid, None); - - Ok(()) - } - - #[test] - fn test_resource_public_display() { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert_eq!(resource.to_string(), "wnfs://user/public/foo/bar"); - } - - #[test] - fn test_resource_public_attenuation_identity() { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert!(resource.is_valid_attenuation(&resource)); - } - - #[test] - fn test_resource_public_attenuation_child() { - let parent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let child = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], - }; - - assert!(child.is_valid_attenuation(&parent)); - } - - #[test] - fn test_resource_public_attenuation_descendent() { - let ancestor = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let descendent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec![ - "foo".to_string(), - "bar".to_string(), - "baz".to_string(), - "qux".to_string(), - ], - }; - - assert!(descendent.is_valid_attenuation(&ancestor)); - } - - #[test] - fn test_resource_public_attenuation_parent() { - let parent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let child = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], - }; - - assert!(!parent.is_valid_attenuation(&child)); - } - - #[test] - fn test_resource_public_attenuation_ancestor() { - let ancestor = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let descendent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec![ - "foo".to_string(), - "bar".to_string(), - "baz".to_string(), - "qux".to_string(), - ], - }; - - assert!(!ancestor.is_valid_attenuation(&descendent)); - } - - #[test] - fn test_resource_public_attenuation_sibling() { - let sibling_1 = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let sibling_2 = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "baz".to_string()], - }; - - assert!(!sibling_1.is_valid_attenuation(&sibling_2)); - assert!(!sibling_2.is_valid_attenuation(&sibling_1)); - } - - #[test] - fn test_resource_public_attenuation_distinct_users() { - let path_1 = WnfsResource::PublicPath { - user: "user1".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let path_2 = WnfsResource::PublicPath { - user: "user2".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert!(!path_1.is_valid_attenuation(&path_2)); - assert!(!path_2.is_valid_attenuation(&path_1)); - } - - #[test] - fn test_ability_attenuation() { - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Create)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Create)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); - } - - #[test] - fn test_ability_display() { - assert_eq!(WnfsAbility::Create.to_string(), "wnfs/create"); - assert_eq!(WnfsAbility::Revise.to_string(), "wnfs/revise"); - assert_eq!(WnfsAbility::SoftDelete.to_string(), "wnfs/soft_delete"); - assert_eq!(WnfsAbility::Overwrite.to_string(), "wnfs/overwrite"); - assert_eq!(WnfsAbility::SuperUser.to_string(), "wnfs/super_user"); - } -} diff --git a/src/proof/internal.rs b/src/proof/internal.rs index 42df1327..06fd97fd 100644 --- a/src/proof/internal.rs +++ b/src/proof/internal.rs @@ -1,2 +1,3 @@ // NOTE: Must not get exported -pub(crate) trait Checker {} +// FIXME either mark downstream as ok to be provate, OOOOOR just leave this in an internal modukle +pub trait Checker {} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 1af372c4..645246db 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -6,8 +6,8 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; /// The possible cases for an [ability][crate::ability]'s /// [Delegation][crate::delegation::Delegation] chain when diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 9c2a517c..69e0feaf 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -2,14 +2,17 @@ use super::internal::Checker; +// FIXME move to internal? + /// An internal trait that checks based on the other traits for an ability type. -pub(crate) trait Prove: Checker { +pub trait Prove: Checker { type Error; // FIXME make the same as the trait name (prove) fn check(&self, proof: &Self) -> Result; } +#[derive(Debug, Clone, PartialEq)] pub enum Success { /// Success Proven, @@ -18,6 +21,7 @@ pub enum Success { ProvenByAny, } +#[derive(Debug, Clone, PartialEq)] pub enum Failure { /// An error in the command chain. CommandEscelation, diff --git a/src/prove/traits/internal/checker.rs b/src/prove/traits/internal/checker.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/prove/traits/internal/checker.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/reader.rs b/src/reader.rs index 599673a9..e5d69913 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,12 +1,8 @@ //! Configure & attach an ambient environment to a value. -use crate::{ - ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, - }, - delegation::Delegable, - invocation::Resolvable, +use crate::ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -244,32 +240,3 @@ impl From>> for Reader { reader.map(|p| p.0) } } - -// use crate::proof::{checkable::Checkable, same::CheckSame}; -// -// impl Delegable for Reader -// where -// Reader: Checkable + CheckSame, -// { -// type Builder = Reader; -// } -// -// impl Resolvable for Reader -// where -// Reader: Into>, -// { -// type Promised = Reader; -// -// fn try_resolve(promised: Self::Promised) -> Result { -// match T::try_resolve(promised.val) { -// Ok(val) => Ok(Reader { -// env: promised.env, -// val, -// }), -// Err(val) => Err(Reader { -// env: promised.env, -// val, -// }), -// } -// } -// } diff --git a/src/receipt.rs b/src/receipt.rs index 05544091..445cdd9d 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,9 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::{ability, did, did::Did, signature}; +use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID>; +pub type Receipt = signature::Envelope, DID>; pub type Preset = Receipt; diff --git a/src/semantics/ability.rs b/src/semantics/ability.rs deleted file mode 100644 index d4758496..00000000 --- a/src/semantics/ability.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! UCAN Abilities - -use std::fmt::{self, Display}; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; - -use super::caveat::Caveat; - -/// An ability defined as part of a semantics -pub trait Ability: Send + Sync + Display + DynClone + Downcast + 'static { - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool; - - /// Returns true if caveat is a valid caveat for self - fn is_valid_caveat(&self, _caveat: &dyn Caveat) -> bool { - false - } -} - -clone_trait_object!(Ability); -impl_downcast!(Ability); - -impl Ability for Box { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl fmt::Debug for dyn Ability { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, r#"Ability("{}")"#, self) - } -} - -/// The top ability -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TopAbility; - -impl Ability for TopAbility { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - if let Some(ability) = other.downcast_ref::() { - return self == ability; - }; - - false - } -} - -impl Display for TopAbility { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "*") - } -} diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs deleted file mode 100644 index 836d5023..00000000 --- a/src/semantics/caveat.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! UCAN Caveats - -use std::fmt; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; -use erased_serde::serialize_trait_object; -use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize}; - -/// A caveat defined as part of a semantics -pub trait Caveat: Send + Sync + DynClone + Downcast + erased_serde::Serialize + 'static { - /// Returns true if the caveat is valid - fn is_valid(&self) -> bool; - - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool; -} - -clone_trait_object!(Caveat); -impl_downcast!(Caveat); -serialize_trait_object!(Caveat); - -impl fmt::Debug for dyn Caveat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Caveat({})", std::any::type_name::()) - } -} - -/// A caveat that is always valid -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct EmptyCaveat; - -impl Caveat for EmptyCaveat { - fn is_valid(&self) -> bool { - true - } - - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { - if let Some(resource) = other.downcast_ref::() { - return self == resource; - }; - - false - } -} - -impl Caveat for Box { - fn is_valid(&self) -> bool { - (**self).is_valid() - } - - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl<'de> Deserialize<'de> for EmptyCaveat { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct NoFieldsVisitor; - - impl<'de> Visitor<'de> for NoFieldsVisitor { - type Value = EmptyCaveat; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an empty object") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - if let Some(field) = map.next_key()? { - return Err(serde::de::Error::unknown_field(field, &[])); - } - - Ok(EmptyCaveat) - } - } - - deserializer.deserialize_map(NoFieldsVisitor) - } -} - -impl Serialize for EmptyCaveat { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_map(Some(0))?.end() - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_serialize_empty_caveat() { - let caveat = EmptyCaveat; - let serialized = serde_json::to_string(&caveat).unwrap(); - - assert_eq!(serialized, "{}"); - } - - #[test] - fn test_deserialize_empty_caveat() { - let deserialized: EmptyCaveat = serde_json::from_value(json!({})).unwrap(); - - assert_eq!(deserialized, EmptyCaveat); - } - - #[test] - fn test_deserialize_empty_caveat_unexpected_fields() { - let deserialized: Result = serde_json::from_value(json!({ - "foo": true - })); - - assert!(deserialized.is_err()); - } -} diff --git a/src/semantics/mod.rs b/src/semantics/mod.rs deleted file mode 100644 index 1d88f3b0..00000000 --- a/src/semantics/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Semantics for UCAN schemes - -pub mod ability; -pub mod caveat; -pub mod resource; diff --git a/src/semantics/resource.rs b/src/semantics/resource.rs deleted file mode 100644 index a7f5360b..00000000 --- a/src/semantics/resource.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! UCAN Resources - -use std::fmt::{self, Display}; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; - -/// A resource defined as part of a semantics -pub trait Resource: Send + Sync + Display + DynClone + Downcast + 'static { - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool; -} - -clone_trait_object!(Resource); -impl_downcast!(Resource); - -impl Resource for Box { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl fmt::Debug for dyn Resource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, r#"Resource("{}")"#, self) - } -} diff --git a/src/signature.rs b/src/signature.rs index cb8b0152..2d4e9dac 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -36,13 +36,12 @@ pub struct Envelope + Capsule, DID: Did> { impl + Into + Clone, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { - Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) + Self::try_sign_generic::(signer, DagCborCodec, payload) } pub fn try_sign_generic>( signer: &DID::Signer, codec: C, - hasher: H, payload: T, ) -> Result, ()> // FIXME err = () diff --git a/src/store.rs b/src/store.rs deleted file mode 100644 index 84826f56..00000000 --- a/src/store.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! A store for persisting UCAN tokens, to be referencable as proofs by other UCANs - -use std::{collections::HashMap, io::Cursor, marker::PhantomData}; - -use async_trait::async_trait; -use cid::{multihash, Cid}; -use libipld_core::{ - codec::{Codec, Decode, Encode}, - raw::RawCodec, -}; -use multihash::MultihashDigest; - -use crate::DEFAULT_MULTIHASH; - -/// A store for persisting UCAN tokens, to be referencable as proofs by other UCANs -pub trait Store -where - C: Codec, -{ - /// The error type for this store - type Error; - - /// Read a token from the store - fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode; - - /// Write a token to the store, using the specified hasher - fn write(&mut self, token: T, hasher: Option) -> Result - where - T: Encode; -} - -/// An async store for persisting UCAN tokens, to be referencable as proofs by other UCANs -// TODO: The send / sync bounds need to be conditional based on the target, to support wasm32 -#[async_trait] -pub trait AsyncStore: Send + Sync -where - C: Codec, -{ - /// The error type for this store - type Error; - - /// Read a token from the store - async fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode; - - /// Write a token to the store, using the specified hasher - async fn write( - &mut self, - token: T, - hasher: Option, - ) -> Result - where - T: Encode + Send; -} - -/// An in-memory store for development and testing -#[derive(Debug, Clone, Default)] -pub struct InMemoryStore { - store: HashMap>, - _phantom: PhantomData, -} - -impl Store for InMemoryStore { - type Error = anyhow::Error; - - fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode, - { - match self.store.get(cid) { - Some(block) => Ok(Some(T::decode(RawCodec, &mut Cursor::new(block))?)), - None => Ok(None), - } - } - - fn write(&mut self, token: T, hasher: Option) -> Result - where - T: Encode, - { - let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); - let block = RawCodec.encode(&token)?; - let digest = hasher.digest(&block); - let cid = Cid::new_v1(RawCodec.into(), digest); - - self.store.insert(cid, block); - - Ok(cid) - } -} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs deleted file mode 100644 index 4a30e2ad..00000000 --- a/src/test_utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Random value generator for sampling data. -#[cfg(feature = "test_utils")] -mod rvg; -#[cfg(feature = "test_utils")] -pub use rvg::*; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs deleted file mode 100644 index 834c437e..00000000 --- a/src/test_utils/rvg.rs +++ /dev/null @@ -1,63 +0,0 @@ -use proptest::{ - collection::vec, - strategy::{Strategy, ValueTree}, - test_runner::{Config, TestRunner}, -}; - -/// A random value generator (RVG), which, given proptest strategies, will -/// generate random values based on those strategies. -#[derive(Debug, Default)] -pub struct Rvg { - runner: TestRunner, -} - -impl Rvg { - /// Creates a new RVG with the default random number generator. - pub fn new() -> Self { - Rvg { - runner: TestRunner::new(Config::default()), - } - } - - /// Creates a new RVG with a deterministic random number generator, - /// using the same seed across test runs. - pub fn deterministic() -> Self { - Rvg { - runner: TestRunner::deterministic(), - } - } - - /// Samples a value for the given strategy. - /// - /// # Example - /// - /// ``` - /// use ucan::test_utils::Rvg; - /// - /// let mut rvg = Rvg::new(); - /// let int = rvg.sample(&(0..100i32)); - /// ``` - pub fn sample(&mut self, strategy: &S) -> S::Value { - strategy - .new_tree(&mut self.runner) - .expect("No value can be generated") - .current() - } - - /// Samples a vec of some length with a value for the given strategy. - /// - /// # Example - /// - /// ``` - /// use ucan::test_utils::Rvg; - /// - /// let mut rvg = Rvg::new(); - /// let ints = rvg.sample_vec(&(0..100i32), 10); - /// ``` - pub fn sample_vec(&mut self, strategy: &S, len: usize) -> Vec { - vec(strategy, len..=len) - .new_tree(&mut self.runner) - .expect("No value can be generated") - .current() - } -} diff --git a/src/time.rs b/src/time.rs index b47a09d9..a0f7a355 100644 --- a/src/time.rs +++ b/src/time.rs @@ -118,6 +118,12 @@ pub struct JsTime { time: SystemTime, } +impl From for SystemTime { + fn from(js_time: JsTime) -> Self { + js_time.time + } +} + #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsTime { diff --git a/src/ucan.rs b/src/ucan.rs deleted file mode 100644 index c394710b..00000000 --- a/src/ucan.rs +++ /dev/null @@ -1,1044 +0,0 @@ -//! JWT embedding of a UCAN - -use std::{collections::vec_deque::VecDeque, str::FromStr}; - -use crate::{ - capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, - did_verifier::DidVerifierMap, - error::Error, - semantics::{ability::Ability, resource::Resource}, - store::Store, - CidString, DefaultFact, DEFAULT_MULTIHASH, -}; -use cid::{ - multihash::{self, MultihashDigest}, - Cid, -}; -use libipld_core::{ipld::Ipld, raw::RawCodec}; -use semver::Version; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; -use tracing::{span, Level}; - -/// The current UCAN version -pub const UCAN_VERSION: &str = "0.10.0"; - -/// The UCAN header -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UcanHeader { - pub(crate) alg: String, - pub(crate) typ: String, -} - -/// The UCAN payload -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UcanPayload { - pub(crate) ucv: String, - pub(crate) iss: String, - pub(crate) aud: String, - #[serde(deserialize_with = "deserialize_required_nullable")] - pub(crate) exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) nbf: Option, - // TODO: nonce required in 1.0 - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) nnc: Option, - #[serde(bound = "C: CapabilityParser")] - pub(crate) cap: Capabilities, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) fct: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) prf: Option>, -} - -/// A UCAN -#[derive(Clone, Debug)] -pub struct Ucan { - pub(crate) header: jose_b64::serde::Json, - pub(crate) payload: jose_b64::serde::Json>, - pub(crate) signature: jose_b64::serde::Bytes, -} - -impl Ucan -where - F: Clone + DeserializeOwned, - C: CapabilityParser, -{ - /// Validate the UCAN's signature and timestamps - pub fn validate(&self, at_time: u64, did_verifier_map: &DidVerifierMap) -> Result<(), Error> { - if self.typ() != "JWT" { - return Err(Error::VerifyingError { - msg: format!("expected header typ field to be 'JWT', got {}", self.typ()), - }); - } - - if Version::parse(self.version()).is_err() { - return Err(Error::VerifyingError { - msg: format!( - "expected header ucv field to be a semver, got {}", - self.version() - ), - }); - } - - if self.is_expired(at_time) { - return Err(Error::VerifyingError { - msg: "token is expired".to_string(), - }); - } - - if self.is_too_early(at_time) { - return Err(Error::VerifyingError { - msg: "current time is before token validity period begins".to_string(), - }); - } - - // TODO: parse and validate iss and aud DIDs during deserialization - self.payload - .aud - .strip_prefix("did:") - .and_then(|did| did.split_once(':')) - .ok_or(Error::VerifyingError { - msg: format!( - "expected did::, got {}", - self.payload.aud - ), - })?; - - let (method, identifier) = self - .payload - .iss - .strip_prefix("did:") - .and_then(|did| did.split_once(':')) - .ok_or(Error::VerifyingError { - msg: format!( - "expected did::, got {}", - self.payload.iss - ), - })?; - - let header = serde_json::to_value(&self.header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload = serde_json::to_value(&self.payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - did_verifier_map.verify(method, identifier, signed_data.as_bytes(), &self.signature) - } - - /// Returns true if the UCAN is authorized by the given issuer to - /// perform the ability against the resource - #[tracing::instrument(level = "trace", skip_all, fields(issuer = issuer.as_ref(), %resource, %ability, %at_time, self = %self.to_cid(None)?))] - pub fn capabilities_for( - &self, - issuer: impl AsRef, - resource: R, - ability: A, - at_time: u64, - did_verifier_map: &DidVerifierMap, - store: &S, - ) -> Result, Error> - where - R: Resource, - A: Ability, - S: Store, - { - let issuer = issuer.as_ref(); - - let mut capabilities = vec![]; - let mut proof_queue: VecDeque<(Ucan, Capability, Capability)> = VecDeque::default(); - - self.validate(at_time, did_verifier_map)?; - - for capability in self.capabilities() { - let span = span!(Level::TRACE, "capability", ?capability); - let _enter = span.enter(); - - let attenuated = Capability::clone_box(&resource, &ability, capability.caveat()); - - if !attenuated.is_subsumed_by(capability) { - tracing::trace!("skipping (not subsumed by)"); - - continue; - } - - if self.issuer() == issuer { - tracing::trace!("matched (by parenthood)"); - - capabilities.push(attenuated.clone()) - } - - proof_queue.push_back((self.clone(), capability.clone(), attenuated)); - - tracing::trace!("enqueued"); - } - - while let Some((ucan, attenuated_cap, leaf_cap)) = proof_queue.pop_front() { - let span = - span!(Level::TRACE, "ucan", ucan = %ucan.to_cid(None)?, ?attenuated_cap, ?leaf_cap); - - let _enter = span.enter(); - - for proof_cid in ucan.proofs().unwrap_or(vec![]) { - let span = span!(Level::TRACE, "proof", cid = %proof_cid); - let _enter = span.enter(); - - match store - .read::(proof_cid) - .map_err(|e| Error::InternalUcanError { - msg: format!( - "error while retrieving proof ({}) from store, {}", - proof_cid, e - ), - })? { - Some(Ipld::Bytes(bytes)) => { - let token = - String::from_utf8(bytes).map_err(|e| Error::InternalUcanError { - msg: format!( - "error converting token for proof ({}) into UTF-8 string, {}", - proof_cid, e - ), - })?; - - let proof_ucan = - Ucan::from_str(&token).map_err(|e| Error::InternalUcanError { - msg: format!( - "error decoding token for proof ({}) into UCAN, {}", - proof_cid, e - ), - })?; - - if !proof_ucan.lifetime_encompasses(&ucan) { - tracing::trace!("skipping (lifetime not encompassed)"); - - continue; - } - - if ucan.issuer() != proof_ucan.audience() { - tracing::trace!("skipping (issuer != audience)"); - - continue; - } - - if proof_ucan.validate(at_time, did_verifier_map).is_err() { - tracing::trace!("skipping (validation failed)"); - - continue; - } - - for capability in proof_ucan.capabilities() { - if !attenuated_cap.is_subsumed_by(capability) { - tracing::trace!("skipping (not subsumed by)"); - - continue; - } - - if proof_ucan.issuer() == issuer { - tracing::trace!("matched (by parenthood)"); - - capabilities.push(leaf_cap.clone()); - } - - proof_queue.push_back(( - proof_ucan.clone(), - capability.clone(), - leaf_cap.clone(), - )); - - tracing::trace!("enqueued"); - } - } - Some(ipld) => { - return Err(Error::InternalUcanError { - msg: format!( - "expected proof ({}) to map to bytes, got {:?}", - proof_cid, ipld - ), - }) - } - None => continue, - } - } - } - - Ok(capabilities) - } - - /// Encode the UCAN as a JWT token - pub fn encode(&self) -> Result { - let header = serde_json::to_value(&self.header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload = serde_json::to_value(&self.payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signature = serde_json::to_value(&self.signature) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - Ok(format!( - "{}.{}.{}", - header.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - signature.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of signature".to_string(), - })? - )) - } - - /// Returns true if the UCAN has past its expiration date - pub fn is_expired(&self, at_time: u64) -> bool { - if let Some(exp) = self.payload.exp { - exp < at_time - } else { - false - } - } - - /// Returns the UCAN's signature - pub fn signature(&self) -> &jose_b64::serde::Bytes { - &self.signature - } - - /// Returns true if the not-before ("nbf") time is still in the future - pub fn is_too_early(&self, at_time: u64) -> bool { - match self.payload.nbf { - Some(nbf) => nbf > at_time, - None => false, - } - } - - /// Returns true if this UCAN's lifetime begins no later than the other - pub fn lifetime_begins_before(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - match (self.payload.nbf, other.payload.nbf) { - (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, - (Some(_), None) => false, - _ => true, - } - } - - /// Returns true if this UCAN expires no earlier than the other - pub fn lifetime_ends_after(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - match (self.payload.exp, other.payload.exp) { - (Some(exp), Some(other_exp)) => exp >= other_exp, - (Some(_), None) => false, - (None, _) => true, - } - } - - /// Returns true if this UCAN's lifetime fully encompasses the other - pub fn lifetime_encompasses(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - self.lifetime_begins_before(other) && self.lifetime_ends_after(other) - } - - /// Return the `typ` field of the UCAN header - pub fn typ(&self) -> &str { - &self.header.typ - } - - /// Return the `alg` field of the UCAN header - pub fn algorithm(&self) -> &str { - &self.header.alg - } - - /// Return the `iss` field of the UCAN payload - pub fn issuer(&self) -> &str { - &self.payload.iss - } - - /// Return the `aud` field of the UCAN payload - pub fn audience(&self) -> &str { - &self.payload.aud - } - - /// Return the `prf` field of the UCAN payload - pub fn proofs(&self) -> Option> { - self.payload - .prf - .as_ref() - .map(|f| f.iter().map(|c| &c.0).collect()) - } - - /// Return the `exp` field of the UCAN payload - pub fn expires_at(&self) -> Option { - self.payload.exp - } - - /// Return the `nbf` field of the UCAN payload - pub fn not_before(&self) -> Option { - self.payload.nbf - } - - /// Return the `nnc` field of the UCAN payload - pub fn nonce(&self) -> Option<&String> { - self.payload.nnc.as_ref() - } - - /// Return an iterator over the `cap` field of the UCAN payload - pub fn capabilities(&self) -> impl Iterator { - self.payload.cap.iter() - } - - /// Return the `fct` field of the UCAN payload - pub fn facts(&self) -> Option<&F> { - self.payload.fct.as_ref() - } - - /// Return the `ucv` field of the UCAN payload - pub fn version(&self) -> &str { - &self.payload.ucv - } - - /// Return the CID v1 of the UCAN encoded as a JWT token - pub fn to_cid(&self, hasher: Option) -> Result { - static RAW_CODEC: u64 = 0x55; - - let token = self.encode()?; - let digest = hasher.unwrap_or(DEFAULT_MULTIHASH).digest(token.as_bytes()); - let cid = Cid::new_v1(RAW_CODEC, digest); - - Ok(cid) - } -} - -impl<'a, F, C> TryFrom<&'a str> for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Error = Error; - - fn try_from(ucan_token: &str) -> Result { - Ucan::::from_str(ucan_token) - } -} - -impl TryFrom for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Error = Error; - - fn try_from(ucan_token: String) -> Result { - Ucan::from_str(ucan_token.as_str()) - } -} - -impl FromStr for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Err = Error; - - fn from_str(ucan_token: &str) -> Result { - let &[header, payload, signature] = - ucan_token.splitn(3, '.').collect::>().as_slice() - else { - return Err(Error::TokenParseError { - msg: "malformed token, expected 3 parts separated by dots".to_string(), - }); - }; - - let header = - jose_b64::serde::Json::from_str(header).map_err(|_| Error::TokenParseError { - msg: "malformed header".to_string(), - })?; - - let payload = - jose_b64::serde::Json::from_str(payload).map_err(|_| Error::TokenParseError { - msg: "malformed payload".to_string(), - })?; - - let signature = - jose_b64::serde::Bytes::from_str(signature).map_err(|_| Error::TokenParseError { - msg: "malformed signature".to_string(), - })?; - - Ok(Ucan:: { - header, - payload, - signature, - }) - } -} - -impl<'de, F, C> Deserialize<'de> for Ucan -where - C: CapabilityParser, - F: DeserializeOwned, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ucan::::from_str(&String::deserialize(deserializer)?) - .map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - -impl Serialize for Ucan -where - C: CapabilityParser, - F: Clone + DeserializeOwned, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.encode() - .map_err(|e| serde::ser::Error::custom(e.to_string()))? - .serialize(serializer) - } -} - -fn deserialize_required_nullable<'de, T, D>(deserializer: D) -> Result -where - T: Deserialize<'de>, - D: Deserializer<'de>, -{ - Deserialize::deserialize(deserializer) - .map_err(|_| serde::de::Error::custom("required field is missing or has invalid type")) -} - -#[cfg(test)] -mod tests { - use signature::rand_core; - - use crate::{ - builder::UcanBuilder, - crypto::SignerDid, - did_verifier::DidVerifierMap, - plugins::wnfs::{WnfsAbility, WnfsResource}, - semantics::{ability::TopAbility, caveat::EmptyCaveat}, - store::InMemoryStore, - time, - }; - - use super::*; - - #[test] - fn test_capabilities_for_empty() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert!(capabilities.is_empty()); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_exact() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Create) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_subsumed_by_semantics() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Overwrite, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Create) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_subsumed_by_top() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }, - WnfsAbility::Overwrite, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Overwrite) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_no_lifetime() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - time::now(), - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Revise) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_lifetime_encompassed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .with_lifetime(60) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .with_lifetime(30) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - time::now(), - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Revise) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_nbf_exposed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .not_before(1) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .not_before(0) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_exp_exposed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .with_expiration(0) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .with_expiration(1) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_lifetime_disjoint() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .not_before(0) - .with_expiration(1) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .not_before(2) - .with_expiration(3) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 2, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } -} diff --git a/src/wasm.rs b/src/wasm.rs deleted file mode 100644 index 0575d3c1..00000000 --- a/src/wasm.rs +++ /dev/null @@ -1,310 +0,0 @@ -use anyhow::{anyhow, bail}; -use async_signature::AsyncSigner; -use async_trait::async_trait; -use js_sys::{Date, Error, Reflect, Uint8Array}; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; -use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto}; - -use crate::{builder::UcanBuilder, capability::DefaultCapabilityParser, crypto::SignerDid}; - -/// Convenience alias around `Result` -pub type JsResult = Result; - -/// A UCAN whose facts are a JSON value -#[wasm_bindgen] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Ucan { - ucan: crate::ucan::Ucan, -} - -struct JsSigner { - key_pair: CryptoKeyPair, - _marker: std::marker::PhantomData K>, -} - -impl JsSigner { - fn subtle_crypto() -> Result { - let global = js_sys::global(); - - match Reflect::get(&global, &JsValue::from_str("crypto")) { - Ok(value) => { - let crypto = value - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to Crypto"))? - .subtle(); - - Ok(crypto) - } - Err(_) => bail!("Failed to get crypto from global object"), - } - } - - fn new(key_pair: CryptoKeyPair) -> Self { - Self { - key_pair, - _marker: std::marker::PhantomData, - } - } - - fn signing_key(&self) -> Result { - match Reflect::get(&self.key_pair, &JsValue::from_str("privateKey")) { - Ok(key) => key - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), - Err(_) => bail!("Failed to get privateKey from CryptoKeyPair"), - } - } - - fn verifying_key(&self) -> Result { - match Reflect::get(&self.key_pair, &JsValue::from_str("publicKey")) { - Ok(key) => key - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), - Err(_) => bail!("Failed to get publicKey from CryptoKeyPair"), - } - } -} - -impl SignerDid for JsSigner { - fn did(&self) -> Result { - Ok("test".to_string()) - } -} - -#[async_trait(?Send)] -impl AsyncSigner for JsSigner { - async fn sign_async( - &self, - msg: &[u8], - ) -> Result { - let subtle = Self::subtle_crypto().map_err(|e| async_signature::Error::from_source(e))?; - - // This can be done without copying using the unsafe `Uint8Array::view` method, - // but I've opted to stick to safe APIs for now, until we benchmark signing. - let data = Uint8Array::from(msg).buffer(); - - let key = self - .signing_key() - .map_err(|e| async_signature::Error::from_source(e))?; - - let promise = subtle - .sign_with_str_and_buffer_source("RSASSA-PKCS1-v1_5", &key, &data) - .map_err(|_| async_signature::Error::new())?; - - let result = JsFuture::from(promise) - .await - .map_err(|_| async_signature::Error::new())?; - - let signature = - rsa::pkcs1v15::Signature::try_from(Uint8Array::new(&result).to_vec().as_slice()) - .map_err(|_| async_signature::Error::new())?; - - Ok(signature) - } -} - -#[wasm_bindgen] -impl Ucan { - /// Returns a boolean indicating whether the given UCAN is expired at the given date - #[wasm_bindgen(js_name = "isExpired")] - pub fn is_expired(&self, at_time: &Date) -> bool { - let at_time = f64::floor(at_time.get_time() / 1000.) as u64; - - self.ucan.is_expired(at_time) - } - - /// Returns true if the UCAN is not yet valid at the given date - #[wasm_bindgen(js_name = "isTooEarly")] - pub fn is_too_early(&self, at_time: &Date) -> bool { - let at_time = f64::floor(at_time.get_time() / 1000.) as u64; - - self.ucan.is_too_early(at_time) - } - - /// Returns the UCAN's signature as a `Uint8Array` - #[wasm_bindgen(getter)] - pub fn signature(&self) -> Vec { - self.ucan.signature().to_vec() - } - - /// Returns the `typ` field of the UCAN's JWT header - #[wasm_bindgen(getter)] - pub fn typ(&self) -> String { - self.ucan.typ().to_string() - } - - /// Returns the `alg` field of the UCAN's JWT header - #[wasm_bindgen(getter)] - pub fn algorithm(&self) -> String { - self.ucan.algorithm().to_string() - } - - /// Returns the `iss` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn issuer(&self) -> String { - self.ucan.issuer().to_string() - } - - /// Returns the `aud` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn audience(&self) -> String { - self.ucan.audience().to_string() - } - - /// Returns the `exp` field of the UCAN's JWT payload - #[wasm_bindgen(getter, js_name = "expiresAt")] - pub fn expires_at(&self) -> Option { - self.ucan - .expires_at() - .map(|expires_at| Date::new(&JsValue::from_f64((expires_at as f64) * 1000.))) - } - - /// Returns the `nbf` field of the UCAN's JWT payload - #[wasm_bindgen(getter, js_name = "notBefore")] - pub fn not_before(&self) -> Option { - self.ucan - .not_before() - .map(|not_before| Date::new(&JsValue::from_f64((not_before as f64) * 1000.))) - } - - /// Returns the `nnc` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn nonce(&self) -> Option { - self.ucan.nonce().map(String::to_string) - } - - /// Returns the `fct` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn facts(&self) -> JsResult { - self.ucan - .facts() - .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|e| Error::new(&format!("Failed to serialize facts: {}", e))) - } - - /// Returns the `vsn` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn version(&self) -> String { - self.ucan.version().to_string() - } - - /// Returns the CID of the UCAN - #[wasm_bindgen] - pub fn cid(&self) -> JsResult { - match self.ucan.to_cid(None) { - Ok(cid) => Ok(cid.to_string()), - Err(e) => Err(Error::new(&format!("Failed to convert to CID: {}", e))), - } - } -} - -/// Decode a UCAN -#[wasm_bindgen] -pub async fn decode(token: String) -> JsResult { - let ucan = - crate::ucan::Ucan::from_str(&token).map_err(|e| Error::new(e.to_string().as_ref()))?; - - Ok(Ucan { ucan }) -} - -/// Options for building a UCAN -#[derive(Debug, Deserialize)] -pub struct BuildOptions { - /// The lifetime of the UCAN in seconds - #[serde(rename = "lifetimeInSeconds")] - pub lifetime_in_seconds: Option, - /// The expiration time of the UCAN in seconds since epoch - pub expiration: Option, - /// The time before which the UCAN is not valid in seconds since epoch - #[serde(rename = "notBefore")] - pub not_before: Option, - /// The facts included in the UCAN - pub facts: Option, - /// The proof CIDs referenced by the UCAN - pub proofs: Option>, - /// The nonce of the UCAN - pub nonce: Option, - // TODO: capabilities -} - -/// Build a UCAN -#[wasm_bindgen] -pub async fn build(issuer: CryptoKeyPair, audience: &str, options: JsValue) -> JsResult { - let options: BuildOptions = - serde_wasm_bindgen::from_value(options).map_err(|e| Error::new(e.to_string().as_ref()))?; - - let builder = - UcanBuilder::::default().for_audience(audience); - - let builder = match options.lifetime_in_seconds { - Some(lifetime_in_seconds) => builder.with_lifetime(lifetime_in_seconds), - None => builder, - }; - - let builder = match options.expiration { - Some(expiration) => builder.with_expiration(expiration), - None => builder, - }; - - let builder = match options.not_before { - Some(not_before) => builder.not_before(not_before), - None => builder, - }; - - let builder = match options.facts { - Some(facts) => builder.with_fact(facts), - None => builder, - }; - - let builder = match options.nonce { - Some(nonce) => builder.with_nonce(nonce), - None => builder, - }; - - // TODO: proofs (need store) - - let signer = JsSigner::::new(issuer); - - let ucan = builder - .sign_async(&signer) - .await - .map_err(|e| Error::new(e.to_string().as_ref()))?; - - Ok(Ucan { ucan }) -} - -/// Panic hook lets us get better error messages if our Rust code ever panics. -/// -/// For more details see -/// -#[wasm_bindgen(js_name = "setPanicHook")] -pub fn set_panic_hook() { - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); -} - -#[wasm_bindgen] -extern "C" { - // For alerting - pub(crate) fn alert(s: &str); - // For logging in the console. - #[wasm_bindgen(js_namespace = console)] - pub fn log(s: &str); -} - -/// Return a representation of an object owned by JS. -#[macro_export] -macro_rules! value { - ($value:expr) => { - wasm_bindgen::JsValue::from($value) - }; -} - -/// Calls the wasm_bindgen console.log. -#[macro_export] -macro_rules! console_log { - ($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) -} From a48e35cc33be28035f6ad706830228ba32823e05 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 17:11:51 -0800 Subject: [PATCH 144/234] Attempting to remove some crates --- Cargo.toml | 7 +----- src/crypto.rs | 58 ++++++++++++++++++++++----------------------- src/crypto/eddsa.rs | 42 -------------------------------- src/crypto/es256.rs | 24 ------------------- src/url.rs | 21 +++++++++++++--- 5 files changed, 48 insertions(+), 104 deletions(-) delete mode 100644 src/crypto/eddsa.rs delete mode 100644 src/crypto/es256.rs diff --git a/Cargo.toml b/Cargo.toml index d653a035..35f36a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,16 +30,11 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" aquamarine = { version = "0.5", optional = true } -async-signature = "0.4.0" +# FIXME actually use? async-signature = "0.4.0" async-trait = "0.1.73" base64 = "0.21" blst = { version = "0.3.11", optional = true, default-features = false } -cfg-if = "0.1" -# FIXME attempt to remove -cid = "0.11" did_url = "0.1" -downcast-rs = "1.2.0" -dyn-clone = "1.0.14" ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } diff --git a/src/crypto.rs b/src/crypto.rs index 18fecc8a..bc02b8bc 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,35 +4,35 @@ use signature::SignatureEncoding; pub mod domain_separator; -#[cfg(feature = "bls")] -pub mod bls12381; - -#[cfg(feature = "es512")] -pub mod p521; - -#[cfg(feature = "eddsa")] -pub mod eddsa; - -#[cfg(feature = "es256")] -pub mod es256; - -#[cfg(feature = "es256k")] -pub mod es256k; - -#[cfg(feature = "es384")] -pub mod es384; - -#[cfg(feature = "es512")] -pub mod es512; - -#[cfg(feature = "ps256")] -pub mod ps256; - -#[cfg(feature = "rs256")] -pub mod rs256; - -#[cfg(feature = "rs512")] -pub mod rs512; +// #[cfg(feature = "bls")] +// pub mod bls12381; +// +// #[cfg(feature = "es512")] +// pub mod p521; +// +// #[cfg(feature = "eddsa")] +// pub mod eddsa; +// +// #[cfg(feature = "es256")] +// pub mod es256; +// +// #[cfg(feature = "es256k")] +// pub mod es256k; +// +// #[cfg(feature = "es384")] +// pub mod es384; +// +// #[cfg(feature = "es512")] +// pub mod es512; +// +// #[cfg(feature = "ps256")] +// pub mod ps256; +// +// #[cfg(feature = "rs256")] +// pub mod rs256; +// +// #[cfg(feature = "rs512")] +// pub mod rs512; // FIXME switch to varsig /// A trait for mapping a SignatureEncoding to its algorithm name under JWS diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs deleted file mode 100644 index 6bfc130f..00000000 --- a/src/crypto/eddsa.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! EdDSA signature support - -#[cfg(feature = "eddsa-verifier")] -use anyhow::anyhow; -#[cfg(feature = "eddsa-verifier")] -use signature::Verifier; - -use multibase::Base; - -use super::{JWSSignature, SignerDid}; - -impl JWSSignature for ed25519::Signature { - const ALGORITHM: &'static str = "EdDSA"; -} - -impl SignerDid for ed25519_dalek::SigningKey { - fn did(&self) -> Result { - let mut buf = unsigned_varint::encode::u128_buffer(); - let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); - - Ok(format!( - "did:key:{}", - multibase::encode( - Base::Base58Btc, - [multicodec, self.verifying_key().to_bytes().as_ref()].concat() - ) - )) - } -} - -/// A verifier for Ed25519 signatures using the `ed25519-dalek` crate -#[cfg(feature = "eddsa-verifier")] -pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = ed25519_dalek::VerifyingKey::try_from(key) - .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; - - let signature = ed25519_dalek::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs deleted file mode 100644 index 48f030ca..00000000 --- a/src/crypto/es256.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES256 signature support - -#[cfg(feature = "es256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256-verifier")] -use signature::Verifier; - -use super::JWSSignature; - -impl JWSSignature for p256::ecdsa::Signature { - const ALGORITHM: &'static str = "ES256"; -} - -/// A verifier for PS256 signatures -#[cfg(feature = "es256-verifier")] -pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; - - let signature = - p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} diff --git a/src/url.rs b/src/url.rs index b97d9b64..4e7a50a4 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,7 +1,8 @@ //! URL utilities. -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use thiserror::Error; use url::Url; /// A wrapper around [`Url`] that has additional trait implementations @@ -39,9 +40,23 @@ impl From for Ipld { } impl TryFrom for Newtype { - type Error = SerdeError; + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::String(s) => Url::parse(&s) + .map(Newtype) + .map_err(FromIpldError::UrlParseError), + _ => Err(FromIpldError::NotAString), + } } } + +#[derive(Debug, Error)] +pub enum FromIpldError { + #[error("Not an IPLD string")] + NotAString, + + #[error(transparent)] + UrlParseError(#[from] url::ParseError), +} From bcf6a507ed1bd1a6480ca41f6e49bc9f679c91f5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 17:44:06 -0800 Subject: [PATCH 145/234] Remove unused crates --- Cargo.toml | 113 +++++++++++++------------------------- src/crypto.rs | 94 +++++++++++++++---------------- src/crypto/eddsa.rs | 36 ++++++++++++ src/crypto/es256.rs | 24 ++++++++ src/crypto/es256k.rs | 34 ++++++------ src/crypto/es384.rs | 34 ++++++------ src/crypto/es512.rs | 10 ++-- src/crypto/ps256.rs | 50 ++++++++--------- src/invocation/agent.rs | 3 +- src/invocation/payload.rs | 21 +------ src/nonce.rs | 17 +++--- src/signature.rs | 20 +++---- 12 files changed, 231 insertions(+), 225 deletions(-) create mode 100644 src/crypto/eddsa.rs create mode 100644 src/crypto/es256.rs diff --git a/Cargo.toml b/Cargo.toml index 35f36a86..fa92eb96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,11 @@ readme = "README.md" edition = "2021" rust-version = "1.75" documentation = "https://docs.rs/ucan" -repository = "https://github.com/ucan-wg/ucan" -authors = ["Quinn Wilton ", "Brooklyn Zelenka ", + "Brooklyn Zelenka " +] [lib] crate-type = ["cdylib", "rlib"] @@ -19,7 +22,7 @@ path = "src/lib.rs" bench = false [[bench]] -name = "a_benchmark" # TODO rename +name = "a_benchmark" # FIXME rename harness = false required-features = ["test_utils"] @@ -28,48 +31,47 @@ name = "counterparts" path = "examples/counterparts.rs" [dependencies] -anyhow = "1.0.75" -aquamarine = { version = "0.5", optional = true } -# FIXME actually use? async-signature = "0.4.0" -async-trait = "0.1.73" -base64 = "0.21" +getrandom = { version = "0.2", features = ["js", "rdrand"] } +nonempty = { version = "0.9" } +proptest = { version = "1.1", optional = true } +regex = "1.10" +unsigned-varint = "0.7.2" + +# Crypto blst = { version = "0.3.11", optional = true, default-features = false } -did_url = "0.1" ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } -ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } -enum-as-inner = "0.6" -erased-serde = "0.3.31" -getrandom = { version = "0.2", features = ["js", "rdrand"] } -jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } -lazy_static = "1.4.0" -libipld-core = { version = "0.16", features = ["serde-codec"] } -libipld-cbor = "0.16" -multibase = "0.9" -multihash = { version = "0.18", features = ["sha2"] } -nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -proptest = { version = "1.1", optional = true } -regex = "1.10" rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } -semver = "1.0.19" +signature = { version = "2.1.0", features = ["alloc"] } + +# Encoding +base64 = "0.21" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" -serde_json = "1.0.107" -serde_with = {version = "3.5", features = ["alloc"] } -signature = { version = "2.1.0", features = ["alloc"] } -thiserror = "1.0" -tracing = "0.1.40" -unsigned-varint = "0.7.2" + +# Web Stack +did_url = "0.1" url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -# `linkme` relies on linker features that aren't available in wasm32 -linkme = "0.3.15" +# Interplanetary Stack +libipld-core = { version = "0.16", features = ["serde-codec"] } +libipld-cbor = "0.16" +multihash = { version = "0.18" } + +# Code Convenience +enum-as-inner = "0.6" +thiserror = "1.0" + +# Docs +aquamarine = { version = "0.5", optional = true } + +# FIXME actually use? async-signature = "0.4.0" +# FIXME see if possible to push aquamarkine into dev dependencies? # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -78,11 +80,10 @@ js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" wasm-bindgen-derive = "0.2" -wasm-bindgen-futures = { version = "0.4" } +# FIXME? wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] -multihash = "0.18" libipld = "0.16" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] @@ -96,17 +97,6 @@ wasm-bindgen-test = "0.2" [features] default = [ - "did-key", - "eddsa-verifier", - "es256-verifier", - "es256k-verifier", - "es384-verifier", - "ps256-verifier", - "rs256-verifier", - "rs512-verifier", - "es512-verifier", - "bls-verifier", - # FIXME the below while debugging "es256", "es256k", "es384", @@ -116,10 +106,8 @@ default = [ "eddsa", "bls" ] - test_utils = ["proptest"] -did-key = [] # FIXME remove? -eddsa = ["dep:ed25519", "dep:ed25519-dalek"] +eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] es384 = ["dep:p384"] @@ -128,40 +116,15 @@ ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] rs512 = ["dep:rsa"] bls = ["dep:blst"] -# FIXME rename for varsig? -# FIXME actually remove these since they got ported to traits -eddsa-verifier = ["eddsa"] -es256-verifier = ["es256"] -es256k-verifier = ["es256k"] -es384-verifier = ["es384"] -es512-verifier = ["es512"] -ps256-verifier = ["ps256"] -rs256-verifier = ["rs256"] -rs512-verifier = ["rs512"] -bls-verifier = ["bls"] mermaid_docs = ["aquamarine"] [package.metadata.docs.rs] all-features = true +# # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] cargo-args = ["--features='mermaid_docs'"] -# See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. -# [profile.release] -# Do not perform backtrace for panic on release builds. -## panic = 'abort' -# Perform optimizations on all codegen units. -## codegen-units = 1 -# Tell `rustc` to optimize for small code size. -## opt-level = "s" # or 'z' to optimize "aggressively" for size -# Enable link time optimization. -## lto = true -# Amount of debug information. -# 0/false: no debug info at all; 1: line tables only; 2/true: full debug info -## debug = false -# Strip debug symbols -## strip = "symbols" - +# # Speedup build on macOS # See https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#splitting-debug-information [profile.dev] diff --git a/src/crypto.rs b/src/crypto.rs index bc02b8bc..12df6c78 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,52 +1,52 @@ //! Cryptography utilities -use signature::SignatureEncoding; +// use signature::SignatureEncoding; pub mod domain_separator; -// #[cfg(feature = "bls")] -// pub mod bls12381; -// -// #[cfg(feature = "es512")] -// pub mod p521; -// -// #[cfg(feature = "eddsa")] -// pub mod eddsa; -// -// #[cfg(feature = "es256")] -// pub mod es256; -// -// #[cfg(feature = "es256k")] -// pub mod es256k; -// -// #[cfg(feature = "es384")] -// pub mod es384; -// -// #[cfg(feature = "es512")] -// pub mod es512; -// -// #[cfg(feature = "ps256")] -// pub mod ps256; -// -// #[cfg(feature = "rs256")] -// pub mod rs256; -// -// #[cfg(feature = "rs512")] -// pub mod rs512; - -// FIXME switch to varsig -/// A trait for mapping a SignatureEncoding to its algorithm name under JWS -pub trait JWSSignature: SignatureEncoding { - /// The algorithm name under JWS - // I'd originally referenced JWA types directly here, but supporting - // unspecified algorithms, like BLS, means leaving things more open-ended. - const ALGORITHM: &'static str; -} - -/// A trait for mapping a Signer to its DID. In most cases, this will -/// be a DID with method did-key, however other methods can be supported -/// by implementing this trait for a custom signer. -pub trait SignerDid { - /// The DID of the signer - fn did(&self) -> Result; -} +#[cfg(feature = "bls")] +pub mod bls12381; + +#[cfg(feature = "es512")] +pub mod p521; + +#[cfg(feature = "eddsa")] +pub mod eddsa; + +#[cfg(feature = "es256")] +pub mod es256; + +#[cfg(feature = "es256k")] +pub mod es256k; + +#[cfg(feature = "es384")] +pub mod es384; + +#[cfg(feature = "es512")] +pub mod es512; + +#[cfg(feature = "ps256")] +pub mod ps256; + +#[cfg(feature = "rs256")] +pub mod rs256; + +#[cfg(feature = "rs512")] +pub mod rs512; + +// // FIXME switch to varsig +// /// A trait for mapping a SignatureEncoding to its algorithm name under JWS +// pub trait JWSSignature: SignatureEncoding { +// /// The algorithm name under JWS +// // I'd originally referenced JWA types directly here, but supporting +// // unspecified algorithms, like BLS, means leaving things more open-ended. +// const ALGORITHM: &'static str; +// } + +// /// A trait for mapping a Signer to its DID. In most cases, this will +// /// be a DID with method did-key, however other methods can be supported +// /// by implementing this trait for a custom signer. +// pub trait SignerDid { +// /// The DID of the signer +// fn did(&self) -> Result; +// } diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs new file mode 100644 index 00000000..0b8ef8c7 --- /dev/null +++ b/src/crypto/eddsa.rs @@ -0,0 +1,36 @@ +//! EdDSA signature support + +#[cfg(feature = "eddsa-verifier")] +use anyhow::anyhow; +#[cfg(feature = "eddsa-verifier")] +use signature::Verifier; + +// use multibase::Base; + +// impl SignerDid for ed25519_dalek::SigningKey { +// fn did(&self) -> Result { +// let mut buf = unsigned_varint::encode::u128_buffer(); +// let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); +// +// Ok(format!( +// "did:key:{}", +// multibase::encode( +// Base::Base58Btc, +// [multicodec, self.verifying_key().to_bytes().as_ref()].concat() +// ) +// )) +// } +// } + +// /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate +// #[cfg(feature = "eddsa-verifier")] +// pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = ed25519_dalek::VerifyingKey::try_from(key) +// .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; +// +// let signature = ed25519_dalek::Signature::try_from(signature) +// .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs new file mode 100644 index 00000000..390b3074 --- /dev/null +++ b/src/crypto/es256.rs @@ -0,0 +1,24 @@ +//! ES256 signature support + +#[cfg(feature = "es256-verifier")] +use anyhow::anyhow; +#[cfg(feature = "es256-verifier")] +use signature::Verifier; + +// use super::JWSSignature; +// +// impl JWSSignature for p256::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES256"; +// } +// +// /// A verifier for PS256 signatures +// #[cfg(feature = "es256-verifier")] +// pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; +// +// let signature = +// p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs index e521b50e..1000f553 100644 --- a/src/crypto/es256k.rs +++ b/src/crypto/es256k.rs @@ -5,21 +5,21 @@ use anyhow::anyhow; #[cfg(feature = "es256k-verifier")] use signature::Verifier; -use super::JWSSignature; +// use super::JWSSignature; +// +// impl JWSSignature for k256::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES256K"; +// } -impl JWSSignature for k256::ecdsa::Signature { - const ALGORITHM: &'static str = "ES256K"; -} - -/// A verifier for ES256k signatures -#[cfg(feature = "es256k-verifier")] -pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = - k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; - - let signature = k256::ecdsa::Signature::try_from(signature) - .map_err(|_| anyhow!("invalid secp256k1 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +// A verifier for ES256k signatures +// #[cfg(feature = "es256k-verifier")] +// pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = +// k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; +// +// let signature = k256::ecdsa::Signature::try_from(signature) +// .map_err(|_| anyhow!("invalid secp256k1 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs index 8f8687dc..25709142 100644 --- a/src/crypto/es384.rs +++ b/src/crypto/es384.rs @@ -5,20 +5,20 @@ use anyhow::anyhow; #[cfg(feature = "es384-verifier")] use signature::Verifier; -use super::JWSSignature; - -impl JWSSignature for p384::ecdsa::Signature { - const ALGORITHM: &'static str = "ES384"; -} - -/// A verifier for ES384 signatures -#[cfg(feature = "es384-verifier")] -pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; - - let signature = - p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +//use super::JWSSignature; +// +//impl JWSSignature for p384::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES384"; +//} +// +///// A verifier for ES384 signatures +//#[cfg(feature = "es384-verifier")] +//pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; +// +// let signature = +// p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +//} diff --git a/src/crypto/es512.rs b/src/crypto/es512.rs index f62653ca..0d4d4284 100644 --- a/src/crypto/es512.rs +++ b/src/crypto/es512.rs @@ -1,7 +1,7 @@ //! ES512 signature support -use super::JWSSignature; - -impl JWSSignature for ecdsa::Signature { - const ALGORITHM: &'static str = "ES512"; -} +// use super::JWSSignature; +// +// impl JWSSignature for ecdsa::Signature { +// const ALGORITHM: &'static str = "ES512"; +// } diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs index 547548d7..9895df2c 100644 --- a/src/crypto/ps256.rs +++ b/src/crypto/ps256.rs @@ -1,27 +1,27 @@ //! PS256 signature support -#[cfg(feature = "ps256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "ps256-verifier")] -use signature::Verifier; - -use super::JWSSignature; - -impl JWSSignature for rsa::pss::Signature { - const ALGORITHM: &'static str = "PS256"; -} - -/// A verifier for RS256 signatures -#[cfg(feature = "ps256-verifier")] -pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) - .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; - - let key = rsa::pss::VerifyingKey::::new(key); - - let signature = rsa::pss::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +// #[cfg(feature = "ps256-verifier")] +// use anyhow::anyhow; +// #[cfg(feature = "ps256-verifier")] +// use signature::Verifier; +// +// use super::JWSSignature; +// +// impl JWSSignature for rsa::pss::Signature { +// const ALGORITHM: &'static str = "PS256"; +// } +// +// /// A verifier for RS256 signatures +// #[cfg(feature = "ps256-verifier")] +// pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) +// .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; +// +// let key = rsa::pss::VerifyingKey::::new(key); +// +// let signature = rsa::pss::Signature::try_from(signature) +// .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0c96c26a..4a52204c 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -134,8 +134,7 @@ where ); let mut encoded = vec![]; - promised - .payload + Ipld::from(promised.payload.clone()) // FIXME use the varsig headre to get the codec .encode(DagCborCodec, &mut encoded) .expect("FIXME"); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6d89672b..9fa5dcf4 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,14 +9,8 @@ use crate::{ signature::Verifiable, time::Timestamp, }; -use anyhow; -use libipld_core::{ - cid::Cid, - codec::{Codec, Encode}, - error::SerdeError, - ipld::Ipld, - serde as ipld_serde, -}; +// use anyhow; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -377,14 +371,3 @@ impl>, DID: Did> From } } } - -impl Encode for Payload -where - Ipld: Encode, - Payload: Clone, // FIXME -{ - fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - let ipld = Ipld::from(self.clone()); - ipld.encode(codec, writer) - } -} diff --git a/src/nonce.rs b/src/nonce.rs index 1cee2fc3..a6a81a85 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -2,6 +2,7 @@ //! //! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce +// FIXME use enum_as_inner more? use enum_as_inner::EnumAsInner; use getrandom::getrandom; use libipld_core::{ @@ -296,12 +297,12 @@ mod test { } // FIXME prop test with lots of inputs - #[test] - fn ser_de() { - let gen = Nonce::generate_16(&mut vec![]); - let ser = serde_json::to_string(&gen).unwrap(); - let de = serde_json::from_str(&ser).unwrap(); - - assert_eq!(gen, de); - } + // #[test] + // fn ser_de() { + // let gen = Nonce::generate_16(&mut vec![]); + // let ser = serde_json::to_string(&gen).unwrap(); + // let de = serde_json::from_str(&ser).unwrap(); + + // assert_eq!(gen, de); + // } } diff --git a/src/signature.rs b/src/signature.rs index 2d4e9dac..414a9075 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,7 +1,7 @@ //! Signatures and cryptographic envelopes. use crate::{capsule::Capsule, did::Did}; -use anyhow; +// use anyhow; use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, @@ -113,15 +113,15 @@ pub enum Signature { // Right = 1, //} -impl + Capsule + Into, DID: Did> Encode for Envelope -where - Ipld: Encode, - Envelope: Clone, // FIXME? -{ - fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - Ipld::from((*self).clone()).encode(codec, writer) - } -} +// impl + Capsule + Into, DID: Did> Encode for Envelope +// where +// Ipld: Encode, +// Envelope: Clone, // FIXME? +// { +// fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { +// Ipld::from((*self).clone()).encode(codec, writer) +// } +// } impl From> for Ipld { fn from(signature: Signature) -> Self { From a8ff1f3f0e7d91af938a82bcafd47ce4d0ac55fc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 21:32:13 -0800 Subject: [PATCH 146/234] Custom serilaizer --- src/ability/command.rs | 6 +- src/delegation/payload.rs | 19 +- src/invocation/payload.rs | 410 ++++++++++++++++++-------------------- 3 files changed, 204 insertions(+), 231 deletions(-) diff --git a/src/ability/command.rs b/src/ability/command.rs index 18458fc9..1ab0d979 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -57,14 +57,14 @@ pub trait Command { // FIXME definitely needs a better name pub trait ParseAbility: Sized { - type Error: fmt::Display; + type Error: fmt::Debug; // FIXME rename this trait to Ability? fn try_parse(cmd: &str, args: &arguments::Named) -> Result; } #[derive(Debug, Clone, Error)] -pub enum ParseAbilityError { +pub enum ParseAbilityError { #[error("Unknown command")] UnknownCommand, @@ -74,7 +74,7 @@ pub enum ParseAbilityError { impl> ParseAbility for T where - >::Error: fmt::Display, + >::Error: fmt::Debug, { type Error = ParseAbilityError<>::Error>; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0047767c..b0024c14 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -168,14 +168,16 @@ where where S: Serializer, { - let mut state = serializer.serialize_struct("delegation::Payload", 9)?; + let count_nbf = if self.not_before.is_some() { 1 } else { 0 }; + + let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; + state.serialize_field("iss", &self.issuer.clone().into().to_string())?; state.serialize_field("sub", &self.subject.clone().into().to_string())?; state.serialize_field("aud", &self.audience.clone().into().to_string())?; state.serialize_field("meta", &self.metadata)?; state.serialize_field("nonce", &self.nonce)?; state.serialize_field("exp", &self.expiration)?; - state.serialize_field("nbf", &self.not_before)?; state.serialize_field("cmd", &self.ability_builder.to_command())?; @@ -193,6 +195,10 @@ where .collect::>(), )?; + if let Some(nbf) = self.not_before { + state.serialize_field("nbf", &nbf)?; + } + state.end() } } @@ -218,7 +224,7 @@ impl< impl< 'de, - T: ParseAbility + ToCommand + Deserialize<'de>, + T: ParseAbility + Deserialize<'de>, C: Condition + Deserialize<'de>, DID: Did + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor @@ -229,10 +235,7 @@ impl< formatter.write_str("struct delegation::Payload") } - fn visit_map(self, mut map: M) -> Result - where - M: MapAccess<'de>, - { + fn visit_map>(self, mut map: M) -> Result { let mut issuer = None; let mut subject = None; let mut audience = None; @@ -318,7 +321,7 @@ impl< let ability_builder = ::try_parse(cmd.as_str(), &args).map_err(|e| { de::Error::custom(format!( - "Unable to parse ability field for {} because {}", + "Unable to parse ability field for {:?} because {:?}", cmd, e )) })?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9fa5dcf4..ed03a41d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,17 +1,24 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments, command::ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ToCommand}, + }, capsule::Capsule, delegation::{self, condition::Condition, error::DelegationError, Delegable}, - did::{self, Did}, + did::Did, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, signature::Verifiable, time::Timestamp, }; // use anyhow; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Serialize, Serializer}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{ + de::{self, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -133,241 +140,204 @@ impl, DID: Did> From> for arguments::N } } -/// A variant that accepts [`Promise`]s. -/// -/// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised, DID>; - -// impl Delegable for Payload { -// type Builder = Payload; -// } - -// use crate::proof::parentful::Parentful; -// -// impl Checkable for Payload -// where -// T::Builder: Checkable>, -// { -// type Hierarchy = (); -// } - -// impl TryFrom> for Payload { -// fn from(payload: Payload) -> Self { -// Payload { -// issuer: payload.issuer, -// subject: payload.subject, -// audience: payload.audience, -// -// ability: T::from(payload.ability), -// -// proofs: payload.proofs, -// cause: payload.cause, -// metadata: payload.metadata, -// nonce: payload.nonce, -// -// not_before: payload.not_before, -// expiration: payload.expiration, -// } -// } -// } - -// impl Resolvable for Payload -// where -// arguments::Named: From, -// Ipld: From, -// T::Promised: ToCommand, -// { -// type Promised = Promised; -// -// fn try_resolve(promised: Promised) -> Result { -// match ::try_resolve(promised.ability) { -// Ok(resolved_ability) => Ok(Payload { -// issuer: promised.issuer, -// subject: promised.subject, -// audience: promised.audience, -// -// ability: resolved_ability, -// -// proofs: promised.proofs, -// cause: promised.cause, -// metadata: promised.metadata, -// nonce: promised.nonce, -// -// not_before: promised.not_before, -// expiration: promised.expiration, -// }), -// Err(promised_ability) => Err(Payload { -// ability: promised_ability, -// ..promised -// }), -// } -// } -// } - -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + T: ToCommand + Into + Serialize, + DID: Did + Serialize, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); - serde::Serialize::serialize(&s, serializer) + let mut field_count = 9; + if self.audience.is_some() { + field_count += 1 + }; + if self.not_before.is_some() { + field_count += 1 + }; + if self.expiration.is_some() { + field_count += 1 + }; + + let mut state = serializer.serialize_struct("invocation::Payload", field_count)?; + + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("sub", &self.subject)?; + + state.serialize_field("cmd", &self.ability.to_command())?; + state.serialize_field("args", &self.ability)?; + + state.serialize_field("prf", &self.proofs)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("cause", &self.cause)?; + state.serialize_field("meta", &self.metadata)?; + + if let Some(aud) = &self.audience { + state.serialize_field("aud", aud)?; + } + + if let Some(nbf) = &self.not_before { + state.serialize_field("nbf", nbf)?; + } + + if let Some(exp) = &self.expiration { + state.serialize_field("exp", &exp)?; + } + + state.end() } } -impl<'de, T, DID: Did> serde::Deserialize<'de> for Payload -where - Payload: TryFrom, - as TryFrom>::Error: Debug, +impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> + for Payload { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: de::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => s - .try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error + struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); + + const FIELDS: &'static [&'static str] = &[ + "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "nbf", "exp", + ]; + + impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Visitor<'de> + for InvocationPayloadVisitor + { + type Value = Payload; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct invocation::Payload") + } + + fn visit_map>(self, mut map: M) -> Result { + let mut issuer = None; + let mut subject = None; + let mut audience = None; + let mut command = None; + let mut arguments = None; + let mut proofs = None; + let mut nonce = None; + let mut cause = None; + let mut metadata = None; + let mut not_before = None; + let mut expiration = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "sub" => { + if subject.is_some() { + return Err(de::Error::duplicate_field("sub")); + } + subject = Some(map.next_value()?); + } + "aud" => { + if audience.is_some() { + return Err(de::Error::duplicate_field("aud")); + } + audience = map.next_value()?; + } + "cmd" => { + if command.is_some() { + return Err(de::Error::duplicate_field("cmd")); + } + command = Some(map.next_value()?); + } + "args" => { + if arguments.is_some() { + return Err(de::Error::duplicate_field("args")); + } + arguments = Some(map.next_value()?); + } + "prf" => { + if proofs.is_some() { + return Err(de::Error::duplicate_field("prf")); + } + proofs = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "cause" => { + if cause.is_some() { + return Err(de::Error::duplicate_field("cause")); + } + cause = map.next_value()?; + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nbf" => { + if not_before.is_some() { + return Err(de::Error::duplicate_field("nbf")); + } + not_before = map.next_value()?; + } + "exp" => { + if expiration.is_some() { + return Err(de::Error::duplicate_field("exp")); + } + expiration = map.next_value()?; + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } + + let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; + let args = arguments.ok_or(de::Error::missing_field("args"))?; + + let ability = ::try_parse(cmd.as_str(), &args).map_err(|e| { + de::Error::custom(format!( + "Unable to parse ability field for {:?} becuase {:?}", + cmd, e + )) + })?; + + Ok(Payload { + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + subject: subject.ok_or(de::Error::missing_field("sub"))?, + proofs: proofs.ok_or(de::Error::missing_field("prf"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, + audience, + ability, + cause, + not_before, + expiration, + }) + } } - } -} -impl TryFrom for Payload -where - Payload: TryFrom, -{ - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; - s.try_into().map_err(|_| ()) // FIXME + deserializer.deserialize_struct( + "invocation::Payload", + FIELDS, + InvocationPayloadVisitor(Default::default()), + ) } } +/// A variant that accepts [`Promise`]s. +/// +/// [`Promise`]: crate::invocation::promise::Promise +pub type Promised = Payload<::Promised, DID>; + impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalSerializer { - #[serde(rename = "iss")] - issuer: did::Newtype, - #[serde(rename = "sub")] - subject: did::Newtype, - #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - audience: Option, - - #[serde(rename = "cmd")] - command: String, - #[serde(rename = "args")] - arguments: arguments::Named, - - #[serde(rename = "prf")] - proofs: Vec, - #[serde(rename = "nonce")] - nonce: Nonce, - - #[serde(rename = "cause")] - cause: Option, - #[serde(rename = "meta")] - metadata: BTreeMap, - - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - not_before: Option, - - #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] - expiration: Option, -} - -impl From for Ipld { - fn from(serializer: InternalSerializer) -> Self { - serializer.into() - } -} - -impl TryFrom for InternalSerializer { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// FIXME -// impl From for Payload { -// fn from(s: InternalSerializer) -> Self { -// Payload { -// issuer: s.issuer, -// subject: s.subject, -// audience: s.audience, -// -// ability: dynamic::Dynamic { -// cmd: s.command, -// args: s.arguments.into(), -// }, -// -// proofs: s.proofs, -// cause: s.cause, -// metadata: s.metadata, -// -// nonce: s.nonce, -// -// not_before: s.not_before, -// expiration: s.expiration, -// } -// } -// } - -// FIXME -// impl From> for InternalSerializer { -// fn from(p: Payload) -> Self { -// InternalSerializer { -// issuer: p.issuer, -// subject: p.subject, -// audience: p.audience, -// -// command: p.ability.cmd, -// arguments: p.ability.args, -// -// proofs: p.proofs, -// cause: p.cause, -// metadata: p.metadata, -// -// nonce: p.nonce, -// -// not_before: p.not_before, -// expiration: p.expiration, -// } -// } -// } - -impl>, DID: Did> From> - for InternalSerializer -{ - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer.into(), - subject: payload.subject.into(), - audience: payload.audience.map(Into::into), - - command: payload.ability.to_command(), - arguments: payload.ability.into(), - - proofs: payload.proofs, - cause: payload.cause, - metadata: payload.metadata, - - nonce: payload.nonce, - - not_before: payload.not_before, - expiration: None, - } - } -} From eef78f3316a2ff48cd137296acd333f72172c8a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 00:08:17 -0800 Subject: [PATCH 147/234] Cleaning up naming, squashing TODOs, adding error types --- Cargo.toml | 12 +- src/ability.rs | 10 +- src/ability/:63 | 132 ------------------- src/crypto.rs | 21 +-- src/crypto/rs256.rs | 3 +- src/delegation/payload.rs | 3 +- src/did.rs | 32 +---- src/did/verifiable.rs | 5 + src/invocation/agent.rs | 6 +- src/invocation/payload.rs | 3 +- src/ipld.rs | 6 + src/ipld/enriched.rs | 19 ++- src/ipld/error.rs | 1 - src/ipld/newtype.rs | 6 +- src/ipld/promised.rs | 61 +-------- src/reader.rs | 247 ++---------------------------------- src/reader/builder.rs | 44 +++++++ src/reader/generic.rs | 176 +++++++++++++++++++++++++ src/reader/promised.rs | 41 ++++++ src/receipt.rs | 10 +- src/receipt/payload.rs | 14 +- src/receipt/store.rs | 53 +------- src/receipt/store/memory.rs | 33 +++++ src/receipt/store/traits.rs | 25 ++++ src/signature.rs | 159 +---------------------- src/signature/envelope.rs | 111 ++++++++++++++++ src/signature/witness.rs | 27 ++++ src/task.rs | 28 +--- src/task/id.rs | 27 ++++ src/time.rs | 229 ++------------------------------- src/time/error.rs | 24 ++++ src/time/js.rs | 109 ++++++++++++++++ src/time/timestamp.rs | 102 +++++++++++++++ src/url.rs | 3 + 34 files changed, 829 insertions(+), 953 deletions(-) delete mode 100644 src/ability/:63 create mode 100644 src/did/verifiable.rs delete mode 100644 src/ipld/error.rs create mode 100644 src/reader/builder.rs create mode 100644 src/reader/generic.rs create mode 100644 src/reader/promised.rs create mode 100644 src/receipt/store/memory.rs create mode 100644 src/receipt/store/traits.rs create mode 100644 src/signature/envelope.rs create mode 100644 src/signature/witness.rs create mode 100644 src/task/id.rs create mode 100644 src/time/error.rs create mode 100644 src/time/js.rs create mode 100644 src/time/timestamp.rs diff --git a/Cargo.toml b/Cargo.toml index fa92eb96..936493f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,9 +104,13 @@ default = [ "rs256", "rs512", "eddsa", - "bls" + "bls", + + "ability-preset" ] + test_utils = ["proptest"] + eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] @@ -116,6 +120,12 @@ ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] rs512 = ["dep:rsa"] bls = ["dep:blst"] + +ability-preset = ["ability-crud", "ability-msg", "ability-wasm"] +ability-crud = [] +ability-msg = [] +ability-wasm = [] + mermaid_docs = ["aquamarine"] [package.metadata.docs.rs] diff --git a/src/ability.rs b/src/ability.rs index 63a05c60..2c3cebee 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -36,11 +36,19 @@ // FIXME feature flag each? // FIXME ability implementers guide (e.g. serde deny fields) // + +pub mod ucan; + +#[cfg(feature = "ability-crud")] pub mod crud; + +#[cfg(feature = "ability-msg")] pub mod msg; -pub mod ucan; + +#[cfg(feature = "ability-wasm")] pub mod wasm; +#[cfg(feature = "ability-preset")] pub mod preset; pub mod arguments; diff --git a/src/ability/:63 b/src/ability/:63 deleted file mode 100644 index b1803b0c..00000000 --- a/src/ability/:63 +++ /dev/null @@ -1,132 +0,0 @@ -use super::{msg, wasm}; -use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, - delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, - same::CheckSame, - }, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Ready { - //Crud(), - Msg(msg::Ready), - Wasm(wasm::run::Ready), -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Builder { - Msg(msg::Builder), - Wasm(wasm::run::Builder), -} - -pub enum Parents { - Msg(msg::Any), -} // NOTE WasmRun has no parents - -impl CheckSame for Parents { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), - } - } -} - -impl ParseAbility for Parents { - type Error = String; // FIXME - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - todo!() - // FIXME Ok(Self {}) - } -} - -impl Delegable for Ready { - type Builder = Builder; -} - -impl CheckParents for Builder { - type Parents = Parents; - type ParentError = (); // FIXME - - fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { - match self { - Builder::Msg(builder) => builder.check_parent(proof), - Builder::Wasm(builder) => Ok(()), - } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Msg(ready) => Builder::Msg(ready.into()), - Ready::Wasm(ready) => Builder::Wasm(ready.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Msg(builder) => builder.try_into().map(Ready::Msg), - Builder::Wasm(builder) => Ok(Ready::Wasm(builder.into())), - } - } -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Promised { - Msg(msg::Promised), - Wasm(wasm::run::Promised), -} - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), - } - } -} - -impl Resolvable for Ready { - type Promised = Promised; - - fn try_resolve(promised: Self::Promised) -> Result { - match promised { - Promised::Msg(promised) => Resolvable::try_resolve(promised) - .map(Ready::Msg) - .map_err(Promised::Msg), - Promised::Wasm(promised) => Resolvable::try_resolve(promised) - .map(Ready::Wasm) - .map_err(Promised::Wasm), - } - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} diff --git a/src/crypto.rs b/src/crypto.rs index 12df6c78..5f961c47 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,4 @@ -//! Cryptography utilities - -// use signature::SignatureEncoding; +//! Cryptographic signature utilities pub mod domain_separator; @@ -33,20 +31,3 @@ pub mod rs256; #[cfg(feature = "rs512")] pub mod rs512; - -// // FIXME switch to varsig -// /// A trait for mapping a SignatureEncoding to its algorithm name under JWS -// pub trait JWSSignature: SignatureEncoding { -// /// The algorithm name under JWS -// // I'd originally referenced JWA types directly here, but supporting -// // unspecified algorithms, like BLS, means leaving things more open-ended. -// const ALGORITHM: &'static str; -// } - -// /// A trait for mapping a Signer to its DID. In most cases, this will -// /// be a DID with method did-key, however other methods can be supported -// /// by implementing this trait for a custom signer. -// pub trait SignerDid { -// /// The DID of the signer -// fn did(&self) -> Result; -// } diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index 419093d2..f08729ee 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -8,8 +8,7 @@ pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); impl PartialEq for VerifyingKey { fn eq(&self, other: &Self) -> bool { - // FIXME yikes that clone - rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + self.0.as_ref() == other.0.as_ref() } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index b0024c14..0970bcb8 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,7 +8,7 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{ checkable::Checkable, @@ -16,7 +16,6 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, - signature::Verifiable, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/did.rs b/src/did.rs index 493cc901..7b2fe96d 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,38 +2,18 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +mod verifiable; + pub mod key; +pub use verifiable::Verifiable; + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; -// #[cfg(feature = "eddsa")] -// use ed25519_dalek; -// -// #[cfg(feature = "es256")] -// use p256; -// -// #[cfg(feature = "es256k")] -// use k256; -// -// #[cfg(feature = "es384")] -// use p384; -// -// #[cfg(feature = "es512")] -// use crate::crypto::p521; -// -// #[cfg(feature = "rs256")] -// use crate::crypto::rs256; -// -// #[cfg(feature = "rs512")] -// use crate::crypto::rs512; -// -// #[cfg(feature = "bls")] -// use blst; - pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { @@ -41,10 +21,6 @@ pub trait Did: type Signer: signature::Signer + fmt::Debug; } -// impl Did for ed25519_dalek::VerifyingKey {} -// -//impl Did for key::Verifier {} -// #[derive(Debug, Clone, PartialEq, Eq)] pub enum Preset { Key(key::Verifier), diff --git a/src/did/verifiable.rs b/src/did/verifiable.rs new file mode 100644 index 00000000..26adb53c --- /dev/null +++ b/src/did/verifiable.rs @@ -0,0 +1,5 @@ +use super::Did; + +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4a52204c..0cd992c1 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -3,10 +3,10 @@ use crate::{ ability::{arguments, ucan}, delegation, delegation::{condition::Condition, Delegable}, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::{Signature, Verifiable}, + signature::Witness, time::JsTime, }; use libipld_cbor::DagCborCodec; @@ -144,7 +144,7 @@ where .verify( &encoded, &match promised.signature { - Signature::Solo(ref sig) => sig.clone(), + Witness::Signature(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed03a41d..252ca24e 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,10 +6,9 @@ use crate::{ }, capsule::Capsule, delegation::{self, condition::Condition, error::DelegationError, Delegable}, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::Verifiable, time::Timestamp, }; // use anyhow; diff --git a/src/ipld.rs b/src/ipld.rs index d492e4da..131c47d8 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,10 @@ //! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld]. +//! +//! [`Ipld`] is a fully concrete data type, and only has a few trait implementations. +//! This module provides a few newtype wrappers that allow you to add trait implementations, +//! and generalized forms to embed non-IPLD into IPLD structure. +//! +//! [`Ipld`]: libipld_core::ipld::Ipld mod enriched; mod newtype; diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 3cb6616d..1c8d8fb0 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,7 +1,16 @@ +//! A generalized version of [`Ipld`][libipld_core::ipld::Ipld] +//! that can contain non-IPLD leaves. + use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// A generalized version of [`Ipld`][libipld_core::ipld::Ipld] +/// that can contain non-IPLD leaves. +/// +/// This is helpful especially when building (mutually) recursive +/// data strutcures that are reducable to [`Ipld`], such as +/// [`ipld::Promised`][crate::ipld::Promised]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Enriched { /// Lifted [`Ipld::Null`] @@ -22,14 +31,14 @@ pub enum Enriched { /// Lifted [`Ipld::Bytes`] (byte array) Bytes(Vec), - /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + /// Lifted [`Ipld::Link`] + Link(Cid), + + /// [`Ipld::List`], but where the values are the provided [`T`]. List(Vec), - /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + /// [`Ipld::Map`], but where the values are the provided [`T`]. Map(BTreeMap), - - /// Lifted [`Ipld::Link`] - Link(Cid), } /// A post-order [`Ipld`] iterator diff --git a/src/ipld/error.rs b/src/ipld/error.rs deleted file mode 100644 index 4752292a..00000000 --- a/src/ipld/error.rs +++ /dev/null @@ -1 +0,0 @@ -// pub enum diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 25b0c5cc..72c3110c 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*; use js_sys::{Array, Map, Object, Uint8Array}; // FIXME push into the submodules -/// A wrapper around [`Ipld`] that has additional trait implementations +/// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. /// @@ -79,7 +79,7 @@ impl Newtype { } } -// TODO testme +// FIXME testme #[cfg(target_arch = "wasm32")] impl From for JsValue { fn from(wrapped: Newtype) -> Self { @@ -115,7 +115,7 @@ impl From for JsValue { } } -// TODO testme +// FIXME testme #[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = (); // FIXME diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 322c2db1..fce3cdac 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -3,7 +3,9 @@ use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -/// A promise to recursively resolve to an [`Ipld`] value. +/// A recursive data structure whose leaves may be [`Ipld`] or promises. +/// +/// [`Promised`] resolves to regular [`Ipld`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); @@ -32,16 +34,12 @@ impl From>> for Promised { } } -// IPLD - impl From for Promised { fn from(ipld: Ipld) -> Self { Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) } } -// FIXME THIS is a great example of a try_resolve -// FIXME is this recursive? Will this blow the stack? impl TryFrom for Ipld { type Error = Promised; @@ -75,56 +73,3 @@ impl TryFrom for Ipld { } } } - -// FIXME surely the other version in this module can't be right if this also works? -// FIXME this is more iterative right? -// impl TryFrom for Ipld { -// // impl Resolvable for Ipld { -// type Error = Self; -// // type Promised = Promised; -// -// fn try_from(promised: Promised) -> Result { -// fn handle(enriched: super::Enriched) -> Result { -// enriched -// .into_iter() -// .try_fold(vec![], |mut acc, next| { -// match next { -// Item::Inner(promised) => { -// let inner: Ipld = Resolvable::try_resolve(*promised).map_err(|_| ())?; -// -// acc.push(inner); -// } -// Item::Node(node) => { -// let _ = Ipld::try_from(*node).map_err(|_| ())?; -// } -// } -// Ok(acc) -// }) -// .map(|vec| vec.first().expect("FIXME").clone()) -// } -// -// match promised.0 { -// Promise::Ok(promise_ok) => match promise_ok { -// PromiseOk::Fulfilled(enriched) => { -// handle(enriched).map_err(|_| PromiseOk::Fulfilled(enriched).into()) -// } -// PromiseOk::Pending(_) => Err(promised), -// }, -// Promise::Err(promise_err) => match promise_err { -// PromiseErr::Rejected(enriched) => { -// handle(enriched).map_err(|_| PromiseErr::Rejected(enriched).into()) -// } -// PromiseErr::Pending(_) => Err(promised), -// }, -// Promise::Any(promise_any) => match promise_any { -// PromiseAny::Fulfilled(enriched) => { -// handle(enriched).map_err(|_| PromiseAny::Fulfilled(enriched).into()) -// } -// PromiseAny::Rejected(enriched) => { -// handle(enriched).map_err(|_| PromiseAny::Rejected(enriched).into()) -// } -// PromiseAny::Pending(_) => Err(promised), -// }, -// } -// } -// } diff --git a/src/reader.rs b/src/reader.rs index e5d69913..a108640f 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,242 +1,11 @@ //! Configure & attach an ambient environment to a value. +//! +//! See the [`Reader`] struct for more information. -use crate::ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; +mod builder; +mod generic; +mod promised; -/// A struct that attaches an ambient environment to a value -/// -/// This is helpful for dependency injection and/or passing around values that -/// would otherwise need to be threaded through next to the value. -/// -/// This is loosely based on the [`Reader`][SO] from Haskell, but is not implemented -/// monadically. The fully "ambient" features of the `Reader` monad are not present here. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::reader::Reader; -/// # use std::string::ToString; -/// # -/// struct Config { -/// name: String, -/// formatter: Box String>, -/// trimmer: Box String>, -/// } -/// -/// fn run(r: Reader) -> String { -/// let formatted = (r.env.formatter)(r.val.to_string()); -/// (r.env.trimmer)(formatted) -/// } -/// -/// let cfg1 = Config { -/// name: "cfg1".into(), -/// formatter: Box::new(|s| s.to_uppercase()), -/// trimmer: Box::new(|mut s| s.trim().into()) -/// }; -/// -/// let cfg2 = Config { -/// name: "cfg2".into(), -/// formatter: Box::new(|s| s.to_lowercase()), -/// trimmer: Box::new(|mut s| s.split_off(5).into()) -/// }; -/// -/// -/// let reader1 = Reader { -/// env: cfg1, -/// val: " value", -/// }; -/// -/// let reader2 = Reader { -/// env: cfg2, -/// val: " value", -/// }; -/// -/// assert_eq!(run(reader1), "VALUE"); -/// assert_eq!(run(reader2), "e"); -/// ``` -/// -/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad -#[derive(Clone, PartialEq, Debug)] -pub struct Reader { - /// The environment (or configuration) being passed with the value - pub env: Env, - - /// The raw value - pub val: T, -} - -impl Reader { - /// Map a function over the `val` of the [`Reader`] - pub fn map(self, func: F) -> Reader - where - F: FnOnce(T) -> U, - { - Reader { - env: self.env, - val: func(self.val), - } - } - - /// Modify the `env` field of the [`Reader`] - pub fn map_env(self, func: F) -> Reader - where - F: FnOnce(Env) -> NewEnv, - { - Reader { - env: func(self.env), - val: self.val, - } - } - - /// Temporarily modify the environment - /// - /// # Examples - /// - /// ```rust - /// # use ucan::reader::Reader; - /// # use std::string::ToString; - /// # - /// # #[derive(Clone)] - /// struct Config<'a> { - /// name: String, - /// formatter: &'a dyn Fn(String) -> String, - /// trimmer: &'a dyn Fn(String) -> String, - /// } - /// - /// fn run(r: Reader) -> String { - /// let formatted = (r.env.formatter)(r.val.to_string()); - /// (r.env.trimmer)(formatted) - /// } - /// - /// let cfg = Config { - /// name: "cfg1".into(), - /// formatter: &|s| s.to_uppercase(), - /// trimmer: &|mut s| s.trim().into() - /// }; - /// - /// let my_reader = Reader { - /// env: cfg, - /// val: " value", - /// }; - /// - /// assert_eq!(run(my_reader.clone()), "VALUE"); - /// - /// // Modify the env locally - /// let observed = my_reader.clone().local(|mut env| { - /// // Modifying env - /// env.trimmer = &|mut s: String| s.split_off(5).into(); - /// env - /// }, |r| run(r)); // Running - /// assert_eq!(observed, "E"); - /// - /// // Back to normal (the above was in fact "local") - /// assert_eq!(run(my_reader.clone()), "VALUE"); - /// ``` - pub fn local(&self, modify_env: F, closure: G) -> U - where - T: Clone, - Env: Clone, - F: Fn(Env) -> Env, - G: Fn(Reader) -> U, - { - closure(Reader { - val: self.val.clone(), - env: modify_env(self.env.clone()), - }) - } -} - -impl>> From> for arguments::Named { - fn from(reader: Reader) -> Self { - reader.val.into() - } -} - -impl ToCommand for Reader { - fn to_command(&self) -> String { - self.env.to_command() - } -} - -impl ParseAbility for Reader { - type Error = ParseAbilityError<::Error>; - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - Ok(Reader { - env: Default::default(), - val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, - }) - } -} - -/// A helper newtype that marks a value as being a [`Delegable::Builder`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Builder}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Builder(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -impl>> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Builder) - } -} - -/// A helper newtype that marks a value as being a [`Resolvable::Promised`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Promised}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Promised(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Promised(pub T); - -impl>> From> for arguments::Named { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|b| b.0) - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Promised) - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|p| p.0) - } -} +pub use builder::Builder; +pub use generic::Reader; +pub use promised::Promised; diff --git a/src/reader/builder.rs b/src/reader/builder.rs new file mode 100644 index 00000000..d7298de7 --- /dev/null +++ b/src/reader/builder.rs @@ -0,0 +1,44 @@ +use super::Reader; +use crate::{ability::arguments, delegation::Delegable, proof::checkable::Checkable}; +use serde::{Deserialize, Serialize}; + +/// A helper newtype that marks a value as being a [`Delegable::Builder`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Builder}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Builder(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +impl Delegable for Reader +where + Reader>: Checkable, +{ + type Builder = Reader>; +} + +impl>> From> for arguments::Named { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Builder) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|b| b.0) + } +} diff --git a/src/reader/generic.rs b/src/reader/generic.rs new file mode 100644 index 00000000..ae713d82 --- /dev/null +++ b/src/reader/generic.rs @@ -0,0 +1,176 @@ +use crate::ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, +}; +use libipld_core::ipld::Ipld; + +/// A struct that attaches an ambient environment to a value. +/// +/// This is a simple way to perform runtime [dependency injection][DI] in a way +/// that plumbs through traits. +/// +/// This is helpful for dependency injection and/or passing around values that +/// would otherwise need to be threaded through next to the value. +/// +/// This is loosely based on the [functional `Reader`][SO] type, +/// but is not implemented with forced purity. Many of the "ambient" features +/// and guarantees of the [functional `Reader`][SO] monad are not present here. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::reader::Reader; +/// # use std::string::ToString; +/// # +/// struct Config { +/// name: String, +/// formatter: Box String>, +/// trimmer: Box String>, +/// } +/// +/// fn run(r: Reader) -> String { +/// let formatted = (r.env.formatter)(r.val.to_string()); +/// (r.env.trimmer)(formatted) +/// } +/// +/// let cfg1 = Config { +/// name: "cfg1".into(), +/// formatter: Box::new(|s| s.to_uppercase()), +/// trimmer: Box::new(|mut s| s.trim().into()) +/// }; +/// +/// let cfg2 = Config { +/// name: "cfg2".into(), +/// formatter: Box::new(|s| s.to_lowercase()), +/// trimmer: Box::new(|mut s| s.split_off(5).into()) +/// }; +/// +/// +/// let reader1 = Reader { +/// env: cfg1, +/// val: " value", +/// }; +/// +/// let reader2 = Reader { +/// env: cfg2, +/// val: " value", +/// }; +/// +/// assert_eq!(run(reader1), "VALUE"); +/// assert_eq!(run(reader2), "e"); +/// ``` +/// +/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad +/// [DI]: https://en.wikipedia.org/wiki/Dependency_injection +#[derive(Clone, PartialEq, Debug)] +pub struct Reader { + /// The environment (or configuration) being passed with the value + pub env: Env, + + /// The raw value + pub val: T, +} + +impl Reader { + /// Map a function over the `val` of the [`Reader`] + pub fn map(self, func: F) -> Reader + where + F: FnOnce(T) -> U, + { + Reader { + env: self.env, + val: func(self.val), + } + } + + /// Modify the `env` field of the [`Reader`] + pub fn map_env(self, func: F) -> Reader + where + F: FnOnce(Env) -> NewEnv, + { + Reader { + env: func(self.env), + val: self.val, + } + } + + /// Temporarily modify the environment + /// + /// # Examples + /// + /// ```rust + /// # use ucan::reader::Reader; + /// # use std::string::ToString; + /// # + /// # #[derive(Clone)] + /// struct Config<'a> { + /// name: String, + /// formatter: &'a dyn Fn(String) -> String, + /// trimmer: &'a dyn Fn(String) -> String, + /// } + /// + /// fn run(r: Reader) -> String { + /// let formatted = (r.env.formatter)(r.val.to_string()); + /// (r.env.trimmer)(formatted) + /// } + /// + /// let cfg = Config { + /// name: "cfg1".into(), + /// formatter: &|s| s.to_uppercase(), + /// trimmer: &|mut s| s.trim().into() + /// }; + /// + /// let my_reader = Reader { + /// env: cfg, + /// val: " value", + /// }; + /// + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// + /// // Modify the env locally + /// let observed = my_reader.clone().local(|mut env| { + /// // Modifying env + /// env.trimmer = &|mut s: String| s.split_off(5).into(); + /// env + /// }, |r| run(r)); // Running + /// assert_eq!(observed, "E"); + /// + /// // Back to normal (the above was in fact "local") + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// ``` + pub fn local(&self, modify_env: F, closure: G) -> U + where + T: Clone, + Env: Clone, + F: Fn(Env) -> Env, + G: Fn(Reader) -> U, + { + closure(Reader { + val: self.val.clone(), + env: modify_env(self.env.clone()), + }) + } +} + +impl>> From> for arguments::Named { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +impl ParseAbility for Reader { + type Error = ParseAbilityError<::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Reader { + env: Default::default(), + val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + }) + } +} diff --git a/src/reader/promised.rs b/src/reader/promised.rs new file mode 100644 index 00000000..bea7d705 --- /dev/null +++ b/src/reader/promised.rs @@ -0,0 +1,41 @@ +use super::Reader; +use crate::ability::{arguments, command::ToCommand}; +use serde::{Deserialize, Serialize}; + +/// A helper newtype that marks a value as being a [`Resolvable::Promised`][crate::invocation::Resolvable::Promised]. +/// +/// Despite this being the intention, due to constraits, the consuming type needs to +/// implement the [`Resolvable`][crate::invocation::Resolvable] trait. +/// For example, there is a `wasm_bindgen` implementation in this crate if +/// compiled for `wasm32`. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Promised}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Promised(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl>> From> for arguments::Named { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Promised) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|p| p.0) + } +} diff --git a/src/receipt.rs b/src/receipt.rs index 445cdd9d..8d81e06f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,4 +1,9 @@ -//! The (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. +//! A [`Receipt`] is the (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. +//! +//! - [`Receipt`]s are the result of an [`Invocation`][`crate::invocation::Invocation`]. +//! - [`Payload`] contains the pimary semantic information for a [`Receipt`]. +//! - [`Store`] is the storage interface for [`Receipt`]s. +//! - [`Responds`] associates the response success type to an [Ability][crate::ability]. mod payload; mod responds; @@ -7,10 +12,13 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; +pub use store::Store; use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. pub type Receipt = signature::Envelope, DID>; +/// An alias for the [`Receipt`] type with the library preset +/// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index b093f6c7..773b884f 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -4,7 +4,10 @@ use super::responds::Responds; use crate::{ - ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, signature::Verifiable, + ability::arguments, + capsule::Capsule, + did::{Did, Verifiable}, + nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -26,9 +29,7 @@ impl Verifiable for Payload { /// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] pub struct Payload { - /// The issuer of the [`Receipt`]. - /// - /// This [`Did`] *must* match the signature on + /// The issuer of the [`Receipt`]. This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. /// /// [`Receipt`]: super::Receipt @@ -39,9 +40,8 @@ pub struct Payload { /// [`Invocation`]: crate::invocation::Invocation pub ran: Cid, - /// The output of the [`Invocation`]. - /// - /// This is always of the form `{"ok": ...}` or `{"err": ...}`. + /// The output of the [`Invocation`]. This is always of + /// the form `{"ok": ...}` or `{"err": ...}`. /// /// [`Invocation`]: crate::invocation::Invocation pub out: Result>, diff --git a/src/receipt/store.rs b/src/receipt/store.rs index d9a7b53c..ff1ecad8 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,50 +1,7 @@ -use super::{Receipt, Responds}; -use crate::{did::Did, task}; -use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, fmt}; -use thiserror::Error; +//! Store trait and MemoryStore implementation. -/// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { - /// The error type representing all the ways a store operation can fail. - type Error; +mod memory; +mod traits; - /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> - where - ::Success: TryFrom; - - /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> - where - ::Success: Into; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore -where - T::Success: fmt::Debug + Clone + PartialEq, -{ - store: BTreeMap>, -} - -// FIXME extract -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - -impl Store for MemoryStore -where - ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, -{ - type Error = NotFound; - - fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { - self.store.get(id).ok_or(NotFound) - } - - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { - self.store.insert(id, receipt); - Ok(()) - } -} +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs new file mode 100644 index 00000000..c3bd3ad4 --- /dev/null +++ b/src/receipt/store/memory.rs @@ -0,0 +1,33 @@ +use super::Store; +use crate::{ + did::Did, + receipt::{Receipt, Responds}, + task, +}; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, convert::Infallible, fmt}; + +/// An in-memory [`receipt::Store`][crate::receipt::Store]. +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore +where + T::Success: fmt::Debug + Clone + PartialEq, +{ + store: BTreeMap>, +} + +impl Store for MemoryStore +where + ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, +{ + type Error = Infallible; + + fn get(&self, id: &task::Id) -> Result>, Self::Error> { + Ok(self.store.get(id)) + } + + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + self.store.insert(id, receipt); + Ok(()) + } +} diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs new file mode 100644 index 00000000..c2bed62b --- /dev/null +++ b/src/receipt/store/traits.rs @@ -0,0 +1,25 @@ +use crate::{ + did::Did, + receipt::{Receipt, Responds}, + task, +}; +use libipld_core::ipld::Ipld; + +/// A store for [`Receipt`]s indexed by their [`task::Id`]s. +pub trait Store { + /// The error type representing all the ways a store operation can fail. + type Error; + + /// Retrieve a [`Receipt`] by its [`task::Id`]. + /// + /// If the store itself did not experience an error, but the value + /// was not found, the result will be `Ok(None)`. + fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> + where + ::Success: TryFrom; + + /// Store a [`Receipt`] by its [`task::Id`]. + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + where + ::Success: Into; +} diff --git a/src/signature.rs b/src/signature.rs index 414a9075..ca66a2a4 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,158 +1,7 @@ //! Signatures and cryptographic envelopes. -use crate::{capsule::Capsule, did::Did}; -// use anyhow; -use libipld_core::{ - cid::{Cid, CidGeneric}, - codec::{Codec, Encode}, - ipld::Ipld, - multihash::{Code, MultihashGeneric}, -}; -use std::collections::BTreeMap; +mod envelope; +mod witness; -// FIXME #[cfg(feature = "dag-cbor")] -use libipld_cbor::DagCborCodec; -use signature::{SignatureEncoding, Signer}; - -pub trait Verifiable { - fn verifier<'a>(&'a self) -> &'a DID; -} - -impl + Capsule, DID: Did> Verifiable for Envelope { - fn verifier(&self) -> &DID { - &self.payload.verifier() - } -} - -/// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] -pub struct Envelope + Capsule, DID: Did> { - /// The signture of the `payload`. - pub signature: Signature, - - /// The payload that's being signed over. - pub payload: T, -} - -impl + Into + Clone, DID: Did> Envelope { - pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { - Self::try_sign_generic::(signer, DagCborCodec, payload) - } - - pub fn try_sign_generic>( - signer: &DID::Signer, - codec: C, - payload: T, - ) -> Result, ()> - // FIXME err = () - where - Ipld: Encode, - { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); - - let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let sig = signer.try_sign(&buffer).map_err(|_| ())?; - - Ok(Envelope { - signature: Signature::Solo(sig), - payload, - }) - } - - pub fn validate_signature(&self) -> Result<(), ()> { - // FIXME need varsig - let codec = DagCborCodec; - let hasher = Code::Sha2_256; - - let mut buffer = vec![]; - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let cid: Cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - match &self.signature { - Signature::Solo(sig) => self - .verifier() - .verify(&cid.to_bytes(), &sig) - .map_err(|_| ()), - } - } -} - -// FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] - // #[serde(untagged)] -pub enum Signature { - Solo(S), - // Batch { - // signature: S, - // root: Vec, - // merkle_proof: Vec, - // }, -} - -//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -//pub enum MerkleStep { -// Node(Vec, Vec, Direction), -// Turn(Direction), -// End, -//} -// -//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -//pub enum Direction { -// Left = 0, -// Right = 1, -//} - -// impl + Capsule + Into, DID: Did> Encode for Envelope -// where -// Ipld: Encode, -// Envelope: Clone, // FIXME? -// { -// fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { -// Ipld::from((*self).clone()).encode(codec, writer) -// } -// } - -impl From> for Ipld { - fn from(signature: Signature) -> Self { - match signature { - Signature::Solo(sig) => sig.to_vec().into(), - // Signature::Batch { - // signature, - // merkle_proof, - // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), - } - } -} - -impl + Capsule + Into, DID: Did> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - - let codec = DagCborCodec; // FIXME get this from the payload - let hasher = Code::Sha2_256; // FIXME get this from the payload - - let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), cid.into())]).into() - } -} +pub use envelope::*; +pub use witness::Witness; diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs new file mode 100644 index 00000000..d371aa9c --- /dev/null +++ b/src/signature/envelope.rs @@ -0,0 +1,111 @@ +use super::Witness; +use crate::{ + capsule::Capsule, + did::{Did, Verifiable}, +}; +use libipld_core::{ + codec::{Codec, Encode}, + error::Result, + ipld::Ipld, + multihash::Code, +}; +use std::collections::BTreeMap; +use thiserror::Error; + +// FIXME #[cfg(feature = "dag-cbor")] +use libipld_cbor::DagCborCodec; +use signature::Signer; + +/// A container associating a `payload` with its signature over it. +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +pub struct Envelope + Capsule, DID: Did> { + /// The signture of the `payload`. + pub signature: Witness, + + /// The payload that's being signed over. + pub payload: T, +} + +impl + Capsule, DID: Did> Verifiable for Envelope { + fn verifier(&self) -> &DID { + &self.payload.verifier() + } +} + +impl + Into + Clone, DID: Did> Envelope { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> { + Self::try_sign_generic::(signer, DagCborCodec, payload) + } + + pub fn try_sign_generic>( + signer: &DID::Signer, + codec: C, + payload: T, + ) -> Result, SignError> + where + Ipld: Encode, + { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .map_err(SignError::PayloadEncodingError)?; + + let sig = signer + .try_sign(&buffer) + .map_err(SignError::SignatureError)?; + + Ok(Envelope { + signature: Witness::Signature(sig), + payload, + }) + } + + pub fn validate_signature(&self) -> Result<(), ValidateError> { + // FIXME need varsig + let codec = DagCborCodec; + + let mut encoded = vec![]; + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); + ipld.encode(codec, &mut encoded) + .map_err(ValidateError::PayloadEncodingError)?; + + match &self.signature { + Witness::Signature(sig) => self + .verifier() + .verify(&encoded, &sig) + .map_err(ValidateError::VerifyError), + } + } +} + +impl + Capsule + Into, DID: Did> From> for Ipld { + fn from(Envelope { signature, payload }: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); + BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), ipld)]).into() + } +} + +/// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. +#[derive(Debug, Error)] +pub enum SignError { + /// Unable to encode the payload. + #[error("Unable to encode payload")] + PayloadEncodingError(#[from] libipld_core::error::Error), + + /// Error while signing. + #[error("Signature error: {0}")] + SignatureError(#[from] signature::Error), +} + +/// Errors that can occur when validating a [`signature::Envelope`][Envelope]. +#[derive(Debug, Error)] +pub enum ValidateError { + /// Unable to encode the payload. + #[error("Unable to encode payload")] + PayloadEncodingError(#[from] libipld_core::error::Error), + + /// Error while verifying the signature. + #[error("Signature verification failed: {0}")] + VerifyError(#[from] signature::Error), +} diff --git a/src/signature/witness.rs b/src/signature/witness.rs new file mode 100644 index 00000000..e5d87a76 --- /dev/null +++ b/src/signature/witness.rs @@ -0,0 +1,27 @@ +//! Signatures and related cryptographic witnesses. + +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use signature::SignatureEncoding; + +/// Asymmetric cryptographic witnesses. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Witness { + /// A single cryptographic signature. + Signature(S), + // FIXME TODO + // Batch { + // signature: S, + // root: Vec, + // merkle_proof: Vec, + // }, +} + +impl From> for Ipld { + fn from(w: Witness) -> Self { + match w { + Witness::Signature(sig) => sig.to_vec().into(), + } + } +} diff --git a/src/task.rs b/src/task.rs index 60a0fbb2..50d51596 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,5 +1,9 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. +mod id; + +pub use id::Id; + use crate::{ability::arguments, did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ @@ -67,30 +71,6 @@ impl From for Cid { } } -/// The unique identifier for a [`Task`]. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Id { - /// The CID of the [`Task`]. - /// - /// This acts as a unique identifier for the task. - pub cid: Cid, -} - -impl TryFrom for Id { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From for Ipld { - fn from(id: Id) -> Self { - id.cid.into() - } -} - impl From for Id { fn from(task: Task) -> Id { Id { diff --git a/src/task/id.rs b/src/task/id.rs new file mode 100644 index 00000000..6e1ec854 --- /dev/null +++ b/src/task/id.rs @@ -0,0 +1,27 @@ +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::Debug; + +/// The unique identifier for a [`Task`][super::Task]. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Id { + /// The CID of the [`Task`][super::Task]. + /// + /// This acts as a unique identifier for the task. + pub cid: Cid, +} + +impl TryFrom for Id { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(id: Id) -> Self { + id.cid.into() + } +} diff --git a/src/time.rs b/src/time.rs index a0f7a355..fa3b622a 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,224 +1,11 @@ //! Time utilities. +//! +//! The [`Timestamp`] struct is the main type for representing time in a UCAN token. -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; -use thiserror::Error; -use web_time::{Duration, SystemTime, UNIX_EPOCH}; +mod error; +mod js; +mod timestamp; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -/// Get the current time in seconds since [`UNIX_EPOCH`]. -pub fn now() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() -} - -/// All timestamps that this library can handle. -/// -/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. - JsSafe(JsTime), - - /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`]. - Postel(SystemTime), -} - -impl From for Timestamp { - fn from(js_time: JsTime) -> Self { - Timestamp::JsSafe(js_time) - } -} - -impl From for Timestamp { - fn from(sys_time: SystemTime) -> Self { - Timestamp::Postel(sys_time) - } -} - -impl From for Ipld { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.into(), - Timestamp::Postel(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } - } -} - -impl Serialize for Timestamp { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Timestamp::JsSafe(js_time) => js_time.serialize(serializer), - Timestamp::Postel(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for Timestamp { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if let Ok(secs) = u64::deserialize(deserializer) { - match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { - None => return Err(serde::de::Error::custom("time out of range for SystemTime")), - Some(sys_time) => match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), - }, - } - } else { - Err(serde::de::Error::custom("not a Timestamp")) - } - } -} - -impl From for SystemTime { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.time, - Timestamp::Postel(sys_time) => sys_time, - } - } -} - -impl TryFrom for Timestamp { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -/// A JavaScript-wrapper for [`Timestamp`]. -/// -/// Per the UCAN spec, timestamps MUST respect [IEEE-754] -/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. -/// -/// This range can represent millions of years into the future, -/// and is thus sufficient for "nearly" all auth use cases. -/// -/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct JsTime { - time: SystemTime, -} - -impl From for SystemTime { - fn from(js_time: JsTime) -> Self { - js_time.time - } -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl JsTime { - /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] - pub fn from_date(date_time: js_sys::Date) -> Result { - let millis = date_time.get_time() as u64; - let secs: u64 = (millis / 1000) as u64; - let duration = Duration::new(secs, 0); // Just round off the nanos - JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) - } - - /// Lower the [`JsTime`] to a [`js_sys::Date`] - pub fn to_date(&self) -> js_sys::Date { - js_sys::Date::new(&JsValue::from( - self.time - .duration_since(UNIX_EPOCH) - .expect("time should be in range since it's getting a JS Date") - .as_millis(), - )) - } -} - -impl JsTime { - /// Create a [`JsTime`] from a [`SystemTime`]. - /// - /// # Arguments - /// - /// * `time` — The time to convert - /// - /// # Errors - /// - /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch - pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -impl From for Ipld { - fn from(js_time: JsTime) -> Self { - js_time - .time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into() - } -} - -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) - } -} - -/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). - pub tried: SystemTime, -} - -impl fmt::Display for OutOfRangeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) - } -} - -// FIXME move to time.rs -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] -pub enum TimeBoundError { - #[error("The UCAN delegation has expired")] - Expired, - - #[error("The UCAN delegation is not yet valid")] - NotYetValid, -} +pub use error::{OutOfRangeError, TimeBoundError}; +pub use js::JsTime; +pub use timestamp::Timestamp; diff --git a/src/time/error.rs b/src/time/error.rs new file mode 100644 index 00000000..6b640b87 --- /dev/null +++ b/src/time/error.rs @@ -0,0 +1,24 @@ +//! Temporal errors. + +use thiserror::Error; +use web_time::SystemTime; + +/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch +#[derive(Debug, Clone, PartialEq, Eq, Error)] +#[error("Time out of JsTime (2⁵³) range: {:?}", tried)] +pub struct OutOfRangeError { + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). + pub tried: SystemTime, +} + +/// An error expressing when a time is not within the bounds of a UCAN. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum TimeBoundError { + /// The UCAN delegation has expired + #[error("Expired")] + Expired, + + /// Not yet valid + #[error("Not yet valid")] + NotYetValid, +} diff --git a/src/time/js.rs b/src/time/js.rs new file mode 100644 index 00000000..f1bb52ce --- /dev/null +++ b/src/time/js.rs @@ -0,0 +1,109 @@ +//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. + +use super::OutOfRangeError; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; + +/// A JavaScript-wrapper for [`Timestamp`][super::Timestamp]. +/// +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct JsTime { + pub time: SystemTime, +} + +impl JsTime { + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`JsTime`]. + pub fn now() -> JsTime { + Self::new(SystemTime::now()) + .expect("the current time to be somtime in the 3rd millenium CE") + } + + /// Create a [`JsTime`] from a [`SystemTime`]. + /// + /// # Arguments + /// + /// * `time` — The time to convert + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl JsTime { + /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) + } + + /// Lower the [`JsTime`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) + } +} + +impl From for SystemTime { + fn from(js_time: JsTime) -> Self { + js_time.time + } +} + +impl From for Ipld { + fn from(js_time: JsTime) -> Self { + js_time + .time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into() + } +} + +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs new file mode 100644 index 00000000..c94e2bc3 --- /dev/null +++ b/src/time/timestamp.rs @@ -0,0 +1,102 @@ +use super::JsTime; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; + +/// All timestamps that this library can handle. +/// +/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). +/// While this library only allows creation of [`JsTime`]s, it will parse the broader +/// [`SystemTime`] range to be liberal in what it accepts. Large numbers are only a problem in +/// langauges that lack 64-bit integers (like JavaScript). +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Timestamp { + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. + JsSafe(JsTime), + + /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), + /// received timestamps may be parsed as regular [`SystemTime`]. + Postel(SystemTime), +} + +impl Timestamp { + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. + /// + /// This will always return the [`JsSafe`][Timestamp::JsSafe] variant. + pub fn now() -> Timestamp { + Timestamp::JsSafe(JsTime::now()) + } +} + +impl From for Timestamp { + fn from(js_time: JsTime) -> Self { + Timestamp::JsSafe(js_time) + } +} + +impl From for Timestamp { + fn from(sys_time: SystemTime) -> Self { + Timestamp::Postel(sys_time) + } +} + +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.into(), + Timestamp::Postel(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Timestamp::JsSafe(js_time) => js_time.serialize(serializer), + Timestamp::Postel(sys_time) => sys_time.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if let Ok(secs) = u64::deserialize(deserializer) { + match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { + None => return Err(serde::de::Error::custom("time out of range for SystemTime")), + Some(sys_time) => match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + }, + } + } else { + Err(serde::de::Error::custom("not a Timestamp")) + } + } +} + +impl From for SystemTime { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.time, + Timestamp::Postel(sys_time) => sys_time, + } + } +} + +impl TryFrom for Timestamp { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/url.rs b/src/url.rs index 4e7a50a4..a55255ed 100644 --- a/src/url.rs +++ b/src/url.rs @@ -52,11 +52,14 @@ impl TryFrom for Newtype { } } +/// Possible errors when trying to convert from [`Ipld`]. #[derive(Debug, Error)] pub enum FromIpldError { + /// Not an IPLD string. #[error("Not an IPLD string")] NotAString, + /// Failed to parse the URL. #[error(transparent)] UrlParseError(#[from] url::ParseError), } From d1f05f37440a932f97371955adba7a2c77c72b2a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 10:21:21 -0800 Subject: [PATCH 148/234] Clean up did:key --- src/delegation.rs | 13 ++- src/delegation/agent.rs | 6 +- src/delegation/condition/max_number.rs | 2 +- src/delegation/condition/min_number.rs | 2 +- src/did.rs | 140 +--------------------- src/did/key.rs | 135 +--------------------- src/did/key/signature.rs | 66 +++++++++++ src/did/key/verifier.rs | 104 +++++++++++++++++ src/did/newtype.rs | 112 ++++++++++++++++++ src/did/preset.rs | 20 ++++ src/did/traits.rs | 13 +++ src/did/verifiable.rs | 5 - src/invocation.rs | 4 +- src/invocation/agent.rs | 12 +- src/invocation/payload.rs | 2 +- src/ipld.rs | 2 + src/ipld/enriched.rs | 117 ++++++++++--------- src/{ => ipld}/number.rs | 20 +++- src/lib.rs | 1 - src/receipt.rs | 2 +- src/signature/envelope.rs | 42 +++++++ src/signature/witness.rs | 3 +- src/time.rs | 2 - src/time/js.rs | 109 ----------------- src/time/timestamp.rs | 154 +++++++++++++++---------- 25 files changed, 568 insertions(+), 520 deletions(-) create mode 100644 src/did/key/signature.rs create mode 100644 src/did/key/verifier.rs create mode 100644 src/did/newtype.rs create mode 100644 src/did/preset.rs create mode 100644 src/did/traits.rs delete mode 100644 src/did/verifiable.rs rename src/{ => ipld}/number.rs (56%) delete mode 100644 src/time/js.rs diff --git a/src/delegation.rs b/src/delegation.rs index eea99dd2..b91ff05f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,49 +29,60 @@ use web_time::SystemTime; /// /// # Examples /// FIXME +/// FIXME wrap in struct to make the docs & error messages better? pub type Delegation = signature::Envelope, DID>; -pub type Preset = Delegation; +pub type Preset = Delegation; // FIXME checkable -> provable? impl Delegation { + /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.payload.issuer } + /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &DID { &self.payload.subject } + /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { &self.payload.audience } + /// Retrive the `ability_builder` of a [`Delegation`] pub fn ability_builder(&self) -> &B { &self.payload.ability_builder } + /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { &self.payload.conditions } + /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { &self.payload.metadata } + /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { &self.payload.nonce } + /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { self.payload.not_before.as_ref() } + /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { &self.payload.expiration } + /// Retrive the `signature` of a [`Delegation`] pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { self.payload.check_time(now) } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 23019ab0..5df85df3 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,5 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; @@ -41,8 +41,8 @@ impl< ability_builder: B, new_conditions: Vec, metadata: BTreeMap, - expiration: JsTime, - not_before: Option, + expiration: Timestamp, + not_before: Option, now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 9e2609fd..41cc6eb0 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -1,6 +1,6 @@ //! A max number [`Condition`]. use super::traits::Condition; -use crate::{ability::arguments, number::Number}; +use crate::{ability::arguments, ipld::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index f6e1e1d6..996c5f81 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -1,6 +1,6 @@ //! A min number [`Condition`]. use super::traits::Condition; -use crate::{ability::arguments, number::Number}; +use crate::{ability::arguments, ipld::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/did.rs b/src/did.rs index 7b2fe96d..65a00d29 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,141 +2,11 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -mod verifiable; +mod newtype; +mod traits; pub mod key; +pub mod preset; -pub use verifiable::Verifiable; - -use did_url::DID; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::{fmt, string::ToString}; -use thiserror::Error; - -pub trait Did: - PartialEq + TryFrom + Into + signature::Verifier -{ - type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; - type Signer: signature::Signer + fmt::Debug; -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Preset { - Key(key::Verifier), - // Dns(did_url::DID), -} - -impl signature::Verifier for Preset { - fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { - match self { - Preset::Key(verifier) => verifier.verify(message, signature), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -/// A [Decentralized Identifier (DID)][wiki] -/// -/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::did; -/// # -/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); -/// assert_eq!(did.0.method(), "example"); -/// ``` -/// -/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -pub struct Newtype(pub DID); - -impl Serialize for Newtype { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - String::from(self.clone()).serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Newtype { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let string = String::deserialize(deserializer)?; - Newtype::try_from(string).map_err(serde::de::Error::custom) - } -} - -impl From for String { - fn from(did: Newtype) -> Self { - did.0.to_string() - } -} - -impl TryFrom for Newtype { - type Error = >::Error; - - fn try_from(string: String) -> Result { - DID::parse(&string).map(Newtype) - } -} - -impl fmt::Display for Newtype { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - -impl From for Ipld { - fn from(did: Newtype) -> Self { - did.into() - } -} - -impl TryFrom for Newtype { - type Error = FromIpldError; - - fn try_from(ipld: Ipld) -> Result { - match ipld { - Ipld::String(string) => { - Newtype::try_from(string).map_err(FromIpldError::StructuralError) - } - other => Err(FromIpldError::NotAnIpldString(other)), - } - } -} - -/// Errors that can occur when converting to or from a [`Newtype`] -#[derive(Debug, Clone, PartialEq, Error)] -pub enum FromIpldError { - /// Strutural errors in the [`Newtype`] - #[error(transparent)] - StructuralError(#[from] did_url::Error), - - /// The [`Ipld`] was not a string - #[error("Not an IPLD String")] - NotAnIpldString(Ipld), -} - -impl Serialize for FromIpldError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for FromIpldError { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let ipld = Ipld::deserialize(deserializer)?; - Ok(FromIpldError::NotAnIpldString(ipld)) - } -} +pub use newtype::{FromIpldError, Newtype}; +pub use traits::{Did, Verifiable}; diff --git a/src/did/key.rs b/src/did/key.rs index 07e0f281..068c3fc3 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -1,132 +1,9 @@ -//! Support for the `did:key` scheme +//! Support for the [`did:key`](https://w3c-ccg.github.io/did-method-key/) DID method. -pub mod traits; - -use signature; - -#[cfg(feature = "eddsa")] -use ed25519_dalek; - -#[cfg(feature = "es256")] -use p256; - -#[cfg(feature = "es256k")] -use k256; - -#[cfg(feature = "es384")] -use p384; - -#[cfg(feature = "es512")] -use crate::crypto::p521; - -#[cfg(feature = "es512")] -use ::p521 as ext_p521; - -#[cfg(feature = "rs256")] -use crate::crypto::rs256; - -#[cfg(feature = "rs512")] -use crate::crypto::rs512; - -#[cfg(feature = "bls")] -use blst; - -#[cfg(feature = "bls")] -use crate::crypto::bls12381; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Verifier { - #[cfg(feature = "eddsa")] - Ed25519(ed25519_dalek::VerifyingKey), - - #[cfg(feature = "es256k")] - Sedcp256k1(k256::ecdsa::VerifyingKey), - - #[cfg(feature = "es256")] - P256(p256::ecdsa::VerifyingKey), +mod signature; +mod verifier; - #[cfg(feature = "es384")] - P384(p384::ecdsa::VerifyingKey), - - #[cfg(feature = "es512")] - P521(p521::VerifyingKey), - - #[cfg(feature = "rs256")] - Rs256(rs256::VerifyingKey), - - #[cfg(feature = "rs512")] - Rs512(rs512::VerifyingKey), - - #[cfg(feature = "bls")] - BlsMinPk(blst::min_pk::PublicKey), - - #[cfg(feature = "bls")] - BlsMinSig(blst::min_sig::PublicKey), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Signature { - #[cfg(feature = "eddsa")] - Ed25519(ed25519_dalek::Signature), - - #[cfg(feature = "es256k")] - Sedcp256k1(k256::ecdsa::Signature), - - #[cfg(feature = "es256")] - P256(p256::ecdsa::Signature), - - #[cfg(feature = "es384")] - P384(p384::ecdsa::Signature), - - #[cfg(feature = "es512")] - P521(ext_p521::ecdsa::Signature), - - #[cfg(feature = "rs256")] - Rs256(rs256::Signature), - - #[cfg(feature = "rs512")] - Rs512(rs512::Signature), - - #[cfg(feature = "bls")] - BlsMinPk(bls12381::min_pk::Signature), - - #[cfg(feature = "bls")] - BlsMinSig(bls12381::min_sig::Signature), -} +pub mod traits; -impl signature::Verifier for Verifier { - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - match (self, signature) { - (Verifier::Ed25519(vk), Signature::Ed25519(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Sedcp256k1(vk), Signature::Sedcp256k1(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P256(vk), Signature::P256(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P384(vk), Signature::P384(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P521(vk), Signature::P521(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Rs256(vk), Signature::Rs256(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Rs512(vk), Signature::Rs512(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (_, _) => Err(signature::Error::from_source( - "invalid signature type for verifier", - )), - } - } -} +pub use signature::Signature; +pub use verifier::Verifier; diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs new file mode 100644 index 00000000..429aadd3 --- /dev/null +++ b/src/did/key/signature.rs @@ -0,0 +1,66 @@ +use enum_as_inner::EnumAsInner; +// FIXME use serde::{Deserialize, Serialize}; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + +/// Signature types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Signature { + /// `EdDSA` signature. + #[cfg(feature = "eddsa")] + EdDSA(ed25519_dalek::Signature), + + /// `ES256K` (`secp256k1`) signature. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::Signature), + + /// `P-256` signature. + #[cfg(feature = "es256")] + P256(p256::ecdsa::Signature), + + /// `P-384` signature. + #[cfg(feature = "es384")] + P384(p384::ecdsa::Signature), + + /// `P-521` signature. + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::Signature), + + /// `RS256` signature. + #[cfg(feature = "rs256")] + Rs256(rs256::Signature), + + /// `RS512` signature. + #[cfg(feature = "rs512")] + Rs512(rs512::Signature), + + /// `BLS 12-381` signature for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(bls12381::min_pk::Signature), + + /// `BLS 12-381` signature for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(bls12381::min_sig::Signature), +} diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs new file mode 100644 index 00000000..e26aa881 --- /dev/null +++ b/src/did/key/verifier.rs @@ -0,0 +1,104 @@ +use super::Signature; +use enum_as_inner::EnumAsInner; +// FIXME use serde::{Deserialize, Serialize}; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +/// Verifiers (public/verifying keys) for `did:key`. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Verifier { + /// `EdDSA` verifying key. + #[cfg(feature = "eddsa")] + EdDSA(ed25519_dalek::VerifyingKey), + + /// `ES256K` (`secp256k1`) verifying key. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::VerifyingKey), + + /// `P-256` verifying key. + #[cfg(feature = "es256")] + P256(p256::ecdsa::VerifyingKey), + + /// `P-384` verifying key. + #[cfg(feature = "es384")] + P384(p384::ecdsa::VerifyingKey), + + /// `P-521` verifying key. + #[cfg(feature = "es512")] + P521(p521::VerifyingKey), + + /// `RS256` verifying key. + #[cfg(feature = "rs256")] + Rs256(rs256::VerifyingKey), + + /// `RS512` verifying key. + #[cfg(feature = "rs512")] + Rs512(rs512::VerifyingKey), + + /// `BLS 12-381` verifying key for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::PublicKey), + + /// `BLS 12-381` verifying key for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::PublicKey), +} + +impl signature::Verifier for Verifier { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match (self, signature) { + (Verifier::EdDSA(vk), Signature::EdDSA(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Es256k(vk), Signature::Es256k(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P256(vk), Signature::P256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P384(vk), Signature::P384(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P521(vk), Signature::P521(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs256(vk), Signature::Rs256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs512(vk), Signature::Rs512(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (_, _) => Err(signature::Error::from_source( + "invalid signature type for verifier", + )), + } + } +} diff --git a/src/did/newtype.rs b/src/did/newtype.rs new file mode 100644 index 00000000..2d29ea1b --- /dev/null +++ b/src/did/newtype.rs @@ -0,0 +1,112 @@ +use did_url::DID; +use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::{fmt, string::ToString}; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// A [Decentralized Identifier (DID)][wiki] +/// +/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::did; +/// # +/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); +/// assert_eq!(did.0.method(), "example"); +/// ``` +/// +/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub struct Newtype(pub DID); + +impl Serialize for Newtype { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + String::from(self.clone()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Newtype { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + Newtype::try_from(string).map_err(serde::de::Error::custom) + } +} + +impl From for String { + fn from(did: Newtype) -> Self { + did.0.to_string() + } +} + +impl TryFrom for Newtype { + type Error = >::Error; + + fn try_from(string: String) -> Result { + DID::parse(&string).map(Newtype) + } +} + +impl fmt::Display for Newtype { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl From for Ipld { + fn from(did: Newtype) -> Self { + did.into() + } +} + +impl TryFrom for Newtype { + type Error = FromIpldError; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::String(string) => { + Newtype::try_from(string).map_err(FromIpldError::StructuralError) + } + other => Err(FromIpldError::NotAnIpldString(other)), + } + } +} + +/// Errors that can occur when converting to or from a [`Newtype`] +#[derive(Debug, Clone, EnumAsInner, PartialEq, Error)] +pub enum FromIpldError { + /// Strutural errors in the [`Newtype`] + #[error(transparent)] + StructuralError(#[from] did_url::Error), + + /// The [`Ipld`] was not a string + #[error("Not an IPLD String")] + NotAnIpldString(Ipld), +} + +impl Serialize for FromIpldError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for FromIpldError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ipld = Ipld::deserialize(deserializer)?; + Ok(FromIpldError::NotAnIpldString(ipld)) + } +} diff --git a/src/did/preset.rs b/src/did/preset.rs new file mode 100644 index 00000000..0808afd8 --- /dev/null +++ b/src/did/preset.rs @@ -0,0 +1,20 @@ +use super::key; +use enum_as_inner::EnumAsInner; + +/// The set of [`Did`] types that ship with this library ("presets"). +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +pub enum Verifier { + /// `did:key` DIDs. + Key(key::Verifier), + // Dns(did_url::DID), +} + +// FIXME serialize with did:key etc + +impl signature::Verifier for Verifier { + fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { + match self { + Verifier::Key(verifier) => verifier.verify(message, signature), + } + } +} diff --git a/src/did/traits.rs b/src/did/traits.rs new file mode 100644 index 00000000..12931628 --- /dev/null +++ b/src/did/traits.rs @@ -0,0 +1,13 @@ +use super::Newtype; +use std::fmt; + +pub trait Did: + PartialEq + TryFrom + Into + signature::Verifier +{ + type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; + type Signer: signature::Signer + fmt::Debug; +} + +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} diff --git a/src/did/verifiable.rs b/src/did/verifiable.rs deleted file mode 100644 index 26adb53c..00000000 --- a/src/did/verifiable.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::Did; - -pub trait Verifiable { - fn verifier<'a>(&'a self) -> &'a DID; -} diff --git a/src/invocation.rs b/src/invocation.rs index 3eff3a76..c13d1933 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -20,8 +20,8 @@ use crate::{ability, did, did::Did, signature}; pub type Invocation = signature::Envelope, DID>; // FIXME use presnet ability, too -pub type Preset = Invocation; -pub type PresetPromised = Invocation; +pub type Preset = Invocation; +pub type PresetPromised = Invocation; impl Invocation { pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0cd992c1..1a161313 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -7,7 +7,7 @@ use crate::{ nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, signature::Witness, - time::JsTime, + time::Timestamp, }; use libipld_cbor::DagCborCodec; use libipld_core::{ @@ -80,8 +80,8 @@ where ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? metadata: BTreeMap, cause: Option, - expiration: Option, - not_before: Option, + expiration: Option, + not_before: Option, now: &SystemTime, // FIXME err type ) -> Result, ()> { @@ -103,8 +103,8 @@ where metadata, nonce: Nonce::generate_12(&mut seed), cause, - expiration: expiration.map(Into::into), - not_before: not_before.map(Into::into), + expiration, + not_before, }; Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) @@ -189,7 +189,7 @@ where subject: &DID, cause: Option, cid: Cid, - now: JsTime, + now: Timestamp, // FIXME return type ) -> Result, ()> where diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 252ca24e..40d3fc49 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -104,7 +104,7 @@ impl From> nonce: payload.nonce, not_before: payload.not_before, - expiration: SystemTime::now().into(), // FIXME + expiration: Timestamp::postel(SystemTime::now()), // FIXME } } } diff --git a/src/ipld.rs b/src/ipld.rs index 131c47d8..95aeca60 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -8,10 +8,12 @@ mod enriched; mod newtype; +mod number; mod promised; pub mod cid; pub use enriched::Enriched; pub use newtype::Newtype; +pub use number::Number; pub use promised::Promised; diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 1c8d8fb0..bea703fc 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,6 +1,7 @@ //! A generalized version of [`Ipld`][libipld_core::ipld::Ipld] //! that can contain non-IPLD leaves. +use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,7 +12,7 @@ use std::collections::BTreeMap; /// This is helpful especially when building (mutually) recursive /// data strutcures that are reducable to [`Ipld`], such as /// [`ipld::Promised`][crate::ipld::Promised]. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Enriched { /// Lifted [`Ipld::Null`] Null, @@ -41,33 +42,6 @@ pub enum Enriched { Map(BTreeMap), } -/// A post-order [`Ipld`] iterator -#[derive(Clone, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] -#[allow(clippy::module_name_repetitions)] -pub struct PostOrderIpldIter<'a, T> { - inbound: Vec>, - outbound: Vec>, -} - -// FIXME not sure if &'a worth it because nbow I'm cloning everywhere -#[derive(Clone, Debug, PartialEq)] -pub enum Item<'a, T> { - Node(&'a Enriched), - Inner(&'a T), -} - -impl<'a, T> PostOrderIpldIter<'a, T> { - /// Initialize a new [`PostOrderIpldIter`] - #[must_use] - pub fn new(enriched: &'a Enriched) -> Self { - PostOrderIpldIter { - inbound: vec![Item::Node(enriched)], - outbound: vec![], - } - } -} - impl<'a, T: Clone> IntoIterator for &'a Enriched { type Item = Item<'a, T>; type IntoIter = PostOrderIpldIter<'a, T>; @@ -111,35 +85,6 @@ impl<'a, T: Clone> From<&'a Enriched> for PostOrderIpldIter<'a, T> { PostOrderIpldIter::new(enriched) } } - -impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { - type Item = Item<'a, T>; - - fn next(&mut self) -> Option { - loop { - match self.inbound.pop() { - None => return self.outbound.pop(), - Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { - self.outbound.push(map.clone()); - - for node in btree.values() { - self.inbound.push(Item::Inner(node)); - } - } - - Some(ref list @ Item::Node(Enriched::List(ref vector))) => { - self.outbound.push(list.clone()); - - for node in vector { - self.inbound.push(Item::Inner(node)); - } - } - Some(node) => self.outbound.push(node), - } - } - } -} - impl> From for Enriched { fn from(ipld: Ipld) -> Self { match ipld { @@ -196,3 +141,61 @@ impl> TryFrom> for Ipld { } } } + +/*************************** +| POST ORDER IPLD ITERATOR | +***************************/ + +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a, T> { + inbound: Vec>, + outbound: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Item<'a, T> { + Node(&'a Enriched), + Inner(&'a T), +} + +impl<'a, T> PostOrderIpldIter<'a, T> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(enriched: &'a Enriched) -> Self { + PostOrderIpldIter { + inbound: vec![Item::Node(enriched)], + outbound: vec![], + } + } +} + +impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { + type Item = Item<'a, T>; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { + self.outbound.push(map.clone()); + + for node in btree.values() { + self.inbound.push(Item::Inner(node)); + } + } + + Some(ref list @ Item::Node(Enriched::List(ref vector))) => { + self.outbound.push(list.clone()); + + for node in vector { + self.inbound.push(Item::Inner(node)); + } + } + Some(node) => self.outbound.push(node), + } + } + } +} diff --git a/src/number.rs b/src/ipld/number.rs similarity index 56% rename from src/number.rs rename to src/ipld/number.rs index bfa72fa5..bfb5084c 100644 --- a/src/number.rs +++ b/src/ipld/number.rs @@ -1,10 +1,16 @@ //! Helpers for working with [`Ipld`] numerics. +use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; /// The union of [`Ipld`] numeric types -#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] +/// +/// This is helpful when comparing different numeric types, such as +/// bounds checking in [`Condition`]s. +/// +/// [`Condition`]: crate::delegation::Condition +#[derive(Debug, Clone, PartialEq, PartialOrd, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number @@ -27,3 +33,15 @@ impl TryFrom for Number { ipld_serde::from_ipld(ipld) } } + +impl From for Number { + fn from(i: i128) -> Number { + Number::Integer(i) + } +} + +impl From for Number { + fn from(f: f64) -> Number { + Number::Float(f) + } +} diff --git a/src/lib.rs b/src/lib.rs index c9d44ab7..45d1c929 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,6 @@ pub mod did; pub mod invocation; pub mod ipld; pub mod nonce; -pub mod number; pub mod proof; pub mod reader; pub mod receipt; diff --git a/src/receipt.rs b/src/receipt.rs index 8d81e06f..cb1563ca 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -21,4 +21,4 @@ pub type Receipt = signature::Envelope, DID>; /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt; +pub type Preset = Receipt; diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs index d371aa9c..cd14f420 100644 --- a/src/signature/envelope.rs +++ b/src/signature/envelope.rs @@ -33,10 +33,39 @@ impl + Capsule, DID: Did> Verifiable for Envelope + Into + Clone, DID: Did> Envelope { + /// Attempt to sign some payload with a given signer. + /// + /// # Arguments + /// + /// * `signer` - The signer to use to sign the payload. + /// * `payload` - The payload to sign. + /// + /// # Errors + /// + /// * [`SignError`] - the payload can't be encoded or the signature fails. + /// + /// # Example + /// + /// FIXME pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> { Self::try_sign_generic::(signer, DagCborCodec, payload) } + /// Attempt to sign some payload with a given signer and specific codec. + /// + /// # Arguments + /// + /// * `signer` - The signer to use to sign the payload. + /// * `codec` - The codec to use to encode the payload. + /// * `payload` - The payload to sign. + /// + /// # Errors + /// + /// * [`SignError`] - the payload can't be encoded or the signature fails. + /// + /// # Example + /// + /// FIXME pub fn try_sign_generic>( signer: &DID::Signer, codec: C, @@ -61,6 +90,19 @@ impl + Into + Clone, DID: Did> Envelope Result<(), ValidateError> { // FIXME need varsig let codec = DagCborCodec; diff --git a/src/signature/witness.rs b/src/signature/witness.rs index e5d87a76..705eae7b 100644 --- a/src/signature/witness.rs +++ b/src/signature/witness.rs @@ -1,11 +1,12 @@ //! Signatures and related cryptographic witnesses. +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use signature::SignatureEncoding; /// Asymmetric cryptographic witnesses. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Witness { /// A single cryptographic signature. diff --git a/src/time.rs b/src/time.rs index fa3b622a..b4e167bf 100644 --- a/src/time.rs +++ b/src/time.rs @@ -3,9 +3,7 @@ //! The [`Timestamp`] struct is the main type for representing time in a UCAN token. mod error; -mod js; mod timestamp; pub use error::{OutOfRangeError, TimeBoundError}; -pub use js::JsTime; pub use timestamp::Timestamp; diff --git a/src/time/js.rs b/src/time/js.rs deleted file mode 100644 index f1bb52ce..00000000 --- a/src/time/js.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. - -use super::OutOfRangeError; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use web_time::{Duration, SystemTime, UNIX_EPOCH}; - -/// A JavaScript-wrapper for [`Timestamp`][super::Timestamp]. -/// -/// Per the UCAN spec, timestamps MUST respect [IEEE-754] -/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. -/// -/// This range can represent millions of years into the future, -/// and is thus sufficient for "nearly" all auth use cases. -/// -/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct JsTime { - pub time: SystemTime, -} - -impl JsTime { - /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`JsTime`]. - pub fn now() -> JsTime { - Self::new(SystemTime::now()) - .expect("the current time to be somtime in the 3rd millenium CE") - } - - /// Create a [`JsTime`] from a [`SystemTime`]. - /// - /// # Arguments - /// - /// * `time` — The time to convert - /// - /// # Errors - /// - /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch - pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl JsTime { - /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] - pub fn from_date(date_time: js_sys::Date) -> Result { - let millis = date_time.get_time() as u64; - let secs: u64 = (millis / 1000) as u64; - let duration = Duration::new(secs, 0); // Just round off the nanos - JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) - } - - /// Lower the [`JsTime`] to a [`js_sys::Date`] - pub fn to_date(&self) -> js_sys::Date { - js_sys::Date::new(&JsValue::from( - self.time - .duration_since(UNIX_EPOCH) - .expect("time should be in range since it's getting a JS Date") - .as_millis(), - )) - } -} - -impl From for SystemTime { - fn from(js_time: JsTime) -> Self { - js_time.time - } -} - -impl From for Ipld { - fn from(js_time: JsTime) -> Self { - js_time - .time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into() - } -} - -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) - } -} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index c94e2bc3..6e649e74 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -1,55 +1,110 @@ -use super::JsTime; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. + +use super::OutOfRangeError; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use web_time::{Duration, SystemTime, UNIX_EPOCH}; -/// All timestamps that this library can handle. +/// A [`Timestamp`][super::Timestamp] with safe JavaScript interop. /// -/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -/// While this library only allows creation of [`JsTime`]s, it will parse the broader -/// [`SystemTime`] range to be liberal in what it accepts. Large numbers are only a problem in -/// langauges that lack 64-bit integers (like JavaScript). -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. - JsSafe(JsTime), - - /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`]. - Postel(SystemTime), +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for +/// JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// This type internally deserializes permissively from any [`SystemTime`], +/// but checks that any time created is in the 53-bit bound when created via +/// the public API. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Timestamp { + time: SystemTime, } impl Timestamp { - /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. + /// Create a [`Timestamp`] from a [`SystemTime`]. /// - /// This will always return the [`JsSafe`][Timestamp::JsSafe] variant. + /// # Arguments + /// + /// * `time` — The time to convert + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + Err(OutOfRangeError { tried: time }) + } else { + Ok(Timestamp { time }) + } + } + + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. pub fn now() -> Timestamp { - Timestamp::JsSafe(JsTime::now()) + Self::new(SystemTime::now()) + .expect("the current time to be somtime in the 3rd millenium CE") + } + + /// Convert a [`Timestamp`] to a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + pub fn to_unix(&self) -> u64 { + self.time + .duration_since(UNIX_EPOCH) + .expect("System time to be after the Unix epoch") + .as_secs() + } + + /// An intentionally permissive variant of `new` for + /// deseriazation. See the note on the struct. + pub(crate) fn postel(time: SystemTime) -> Self { + Timestamp { time } } } -impl From for Timestamp { - fn from(js_time: JsTime) -> Self { - Timestamp::JsSafe(js_time) +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Timestamp { + /// Lift a [`js_sys::Date`] into a Rust [`Timestamp`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + Timestamp::new(UNIX_EPOCH + duration).map_err(Into::into) + } + + /// Lower the [`Timestamp`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) + } +} + +impl TryFrom for Timestamp { + type Error = OutOfRangeError; + + fn try_from(sys_time: SystemTime) -> Result { + Timestamp::new(sys_time) } } -impl From for Timestamp { - fn from(sys_time: SystemTime) -> Self { - Timestamp::Postel(sys_time) +impl From for SystemTime { + fn from(js_time: Timestamp) -> Self { + js_time.time } } impl From for Ipld { fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.into(), - Timestamp::Postel(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } + timestamp.to_unix().into() } } @@ -58,10 +113,7 @@ impl Serialize for Timestamp { where S: Serializer, { - match self { - Timestamp::JsSafe(js_time) => js_time.serialize(serializer), - Timestamp::Postel(sys_time) => sys_time.serialize(serializer), - } + self.to_unix().serialize(serializer) } } @@ -70,33 +122,7 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(secs) = u64::deserialize(deserializer) { - match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { - None => return Err(serde::de::Error::custom("time out of range for SystemTime")), - Some(sys_time) => match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), - }, - } - } else { - Err(serde::de::Error::custom("not a Timestamp")) - } - } -} - -impl From for SystemTime { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.time, - Timestamp::Postel(sys_time) => sys_time, - } - } -} - -impl TryFrom for Timestamp { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + let seconds = u64::deserialize(deserializer)?; + Ok(Timestamp::postel(UNIX_EPOCH + Duration::from_secs(seconds))) } } From e9a77c24ae377fabe9234363ec1b65dc5bad1b8b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 11:03:20 -0800 Subject: [PATCH 149/234] Save before some newtyping --- src/capability.rs | 405 --------------------------------- src/crypto.rs | 22 +- src/crypto/bls12381/error.rs | 11 +- src/crypto/domain_separator.rs | 4 + src/crypto/eddsa.rs | 36 --- src/crypto/es256.rs | 24 -- src/crypto/es256k.rs | 25 -- src/crypto/es384.rs | 24 -- src/crypto/es512.rs | 38 +++- src/crypto/ps256.rs | 27 --- src/crypto/rs256.rs | 5 +- src/crypto/rs512.rs | 5 +- src/did/key/traits.rs | 4 +- src/did/key/verifier.rs | 4 +- 14 files changed, 61 insertions(+), 573 deletions(-) delete mode 100644 src/capability.rs delete mode 100644 src/crypto/eddsa.rs delete mode 100644 src/crypto/es256.rs delete mode 100644 src/crypto/es256k.rs delete mode 100644 src/crypto/es384.rs delete mode 100644 src/crypto/ps256.rs diff --git a/src/capability.rs b/src/capability.rs deleted file mode 100644 index de4377ad..00000000 --- a/src/capability.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! Capabilities, and traits for deserializing them - -use std::collections::BTreeMap; - -use serde::{ - de::{DeserializeSeed, IgnoredAny, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use url::Url; - -use crate::semantics::{ - ability::{Ability, TopAbility}, - caveat::{Caveat, EmptyCaveat}, - resource::Resource, -}; - -/// The default capability handler, when deserializing a UCAN -pub type DefaultCapabilityParser = PluginCapability; - -/// A capability -#[derive(Debug, Clone)] -pub struct Capability { - /// The resource - resource: Box, - /// The ability - ability: Box, - /// The caveat - caveat: Box, -} - -impl Capability { - /// Creates a new capability - pub fn new(resource: R, ability: A, caveat: C) -> Self - where - R: Resource, - A: Ability, - C: Caveat, - { - Self { - resource: Box::new(resource), - ability: Box::new(ability), - caveat: Box::new(caveat), - } - } - - /// Creates a new capability by cloning the resource, ability, and caveat as trait objects - pub fn clone_box(resource: &dyn Resource, ability: &dyn Ability, caveat: &dyn Caveat) -> Self { - Self { - resource: dyn_clone::clone_box(resource), - ability: dyn_clone::clone_box(ability), - caveat: dyn_clone::clone_box(caveat), - } - } - - /// Returns the resource - pub fn resource(&self) -> &dyn Resource { - &*self.resource - } - - /// Returns the ability - pub fn ability(&self) -> &dyn Ability { - &*self.ability - } - - /// Returns the caveat - pub fn caveat(&self) -> &dyn Caveat { - &*self.caveat - } - - /// Returns true if self is subsumed by other - pub fn is_subsumed_by(&self, other: &Capability) -> bool { - if !self.resource.is_valid_attenuation(&*other.resource) { - return false; - } - - if !(other.ability.is::() || self.ability.is_valid_attenuation(&*other.ability)) - { - return false; - } - - other.caveat.is::() || self.caveat.is_valid_attenuation(&*other.caveat) - } -} - -/// A collection of capabilities -#[derive(Clone, Debug)] -pub struct Capabilities { - inner: Vec, - _marker: std::marker::PhantomData C>, -} - -impl Default for Capabilities { - fn default() -> Self { - Self { - inner: Default::default(), - _marker: Default::default(), - } - } -} - -impl Capabilities { - /// Creates a new collection of capabilities from a vector - pub fn new(inner: Vec) -> Self { - Self { - inner, - _marker: Default::default(), - } - } - - /// Pushes a capability to the collection - pub fn push(&mut self, capability: Capability) { - self.inner.push(capability); - } - - /// Extends the collection with the capabilities from a slice of capabilities - pub fn extend_from_slice(&mut self, capabilities: &[Capability]) { - self.inner.extend_from_slice(capabilities); - } - - /// Returns an iterator over the capabilities - pub fn iter(&self) -> impl Iterator { - self.inner.iter() - } -} - -/// Handles deserializing capabilities -pub trait CapabilityParser: Clone { - /// Tries to deserialize a capability from a resource_uri, ability, and a deserilizer for the caveat - fn try_handle( - resource_uri: &Url, - ability: &str, - caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, anyhow::Error> - where - Self: Sized; -} - -/// A capability handler that deserializes using the registered plugins -#[derive(Clone, Debug)] -pub struct PluginCapability {} - -impl CapabilityParser for PluginCapability { - fn try_handle( - resource_uri: &Url, - ability: &str, - caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, anyhow::Error> { - let resource_scheme = resource_uri.scheme(); - - for plugin in crate::plugins::plugins().filter(|p| p.scheme() == resource_scheme) { - let Some(resource) = plugin.try_handle_resource(resource_uri)? else { - continue; - }; - - let Some(ability) = plugin.try_handle_ability(&resource, ability)? else { - continue; - }; - - let Some(caveat) = plugin.try_handle_caveat(&resource, &ability, caveat_deserializer)? - else { - continue; - }; - - return Ok(Some(Capability { - resource, - ability, - caveat, - })); - } - - Ok(None) - } -} - -impl Serialize for Capabilities -where - Cap: CapabilityParser, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut capabilities: BTreeMap>> = - Default::default(); - - for capability in self.iter() { - let resource_uri = capability.resource().to_string(); - let ability_key = capability.ability().to_string(); - let caveat = capability.caveat(); - - capabilities - .entry(resource_uri) - .or_default() - .entry(ability_key) - .or_default() - .push(caveat); - } - - capabilities.serialize(serializer) - } -} - -impl<'de, C> Deserialize<'de> for Capabilities -where - C: CapabilityParser, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CapabilitiesVisitor { - _marker: std::marker::PhantomData C>, - } - - impl<'de, C> Visitor<'de> for CapabilitiesVisitor - where - C: CapabilityParser, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "a map of capabilities") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut capabilities = Vec::new(); - - while let Some(resource_key) = map.next_key::()? { - let resource_uri = - Url::parse(&resource_key).map_err(serde::de::Error::custom)?; - - map.next_value_seed(Abilities:: { - resource_uri, - capabilities: &mut capabilities, - _marker: Default::default(), - })?; - } - - Ok(capabilities) - } - } - - let caps = deserializer.deserialize_map(CapabilitiesVisitor:: { - _marker: Default::default(), - })?; - - Ok(Self::new(caps)) - } -} - -struct Abilities<'a, C> { - resource_uri: Url, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, -} - -impl<'de, 'a, C> DeserializeSeed<'de> for Abilities<'a, C> -where - C: CapabilityParser, -{ - type Value = (); - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct AbilitiesVisitor<'a, C> { - resource_uri: Url, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, - } - - impl<'de, 'a, C> Visitor<'de> for AbilitiesVisitor<'a, C> - where - C: CapabilityParser, - { - type Value = (); - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "a map of abilities for {}", self.resource_uri) - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - while let Some(ability_key) = map.next_key::()? { - map.next_value_seed(Caveats:: { - resource_uri: self.resource_uri.clone(), - ability_key: ability_key.clone(), - capabilities: self.capabilities, - _marker: Default::default(), - })?; - } - - Ok(()) - } - } - - deserializer.deserialize_map(AbilitiesVisitor:: { - resource_uri: self.resource_uri, - capabilities: self.capabilities, - _marker: Default::default(), - }) - } -} - -struct Caveats<'a, C> { - resource_uri: Url, - ability_key: String, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, -} - -impl<'de, 'a, C> DeserializeSeed<'de> for Caveats<'a, C> -where - C: CapabilityParser, -{ - type Value = (); - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CaveatsVisitor<'a, C> { - resource_uri: Url, - ability_key: String, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, - } - - impl<'de, 'a, C> Visitor<'de> for CaveatsVisitor<'a, C> - where - C: CapabilityParser, - { - type Value = (); - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - formatter, - "a map of caveats for {} : {}", - self.resource_uri, self.ability_key - ) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - while let Some(element) = seq.next_element_seed(CaveatSeed:: { - resource_uri: self.resource_uri.clone(), - ability_key: self.ability_key.clone(), - _marker: Default::default(), - })? { - if let Some(capability) = element { - self.capabilities.push(capability); - } - } - - Ok(()) - } - } - - deserializer.deserialize_seq(CaveatsVisitor:: { - resource_uri: self.resource_uri, - ability_key: self.ability_key, - capabilities: self.capabilities, - _marker: Default::default(), - }) - } -} - -struct CaveatSeed { - resource_uri: Url, - ability_key: String, - _marker: std::marker::PhantomData Cap>, -} - -impl<'de, Cap> DeserializeSeed<'de> for CaveatSeed -where - Cap: CapabilityParser, -{ - type Value = Option; - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut deserializer = >::erase(deserializer); - - let Some(capability) = - Cap::try_handle(&self.resource_uri, &self.ability_key, &mut deserializer) - .map_err(serde::de::Error::custom)? - else { - erased_serde::deserialize::(&mut deserializer) - .map_err(serde::de::Error::custom)?; - return Ok(None); - }; - - Ok(Some(capability)) - } -} diff --git a/src/crypto.rs b/src/crypto.rs index 5f961c47..086753be 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,31 +1,15 @@ //! Cryptographic signature utilities -pub mod domain_separator; +mod domain_separator; + +pub use domain_separator::DomainSeparator; #[cfg(feature = "bls")] pub mod bls12381; -#[cfg(feature = "es512")] -pub mod p521; - -#[cfg(feature = "eddsa")] -pub mod eddsa; - -#[cfg(feature = "es256")] -pub mod es256; - -#[cfg(feature = "es256k")] -pub mod es256k; - -#[cfg(feature = "es384")] -pub mod es384; - #[cfg(feature = "es512")] pub mod es512; -#[cfg(feature = "ps256")] -pub mod ps256; - #[cfg(feature = "rs256")] pub mod rs256; diff --git a/src/crypto/bls12381/error.rs b/src/crypto/bls12381/error.rs index 7af72c19..8475a875 100644 --- a/src/crypto/bls12381/error.rs +++ b/src/crypto/bls12381/error.rs @@ -1,26 +1,35 @@ use blst::BLST_ERROR; +use enum_as_inner::EnumAsInner; use thiserror::Error; -#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +/// Errors that can occur during BLS verification. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error, EnumAsInner)] pub enum VerificationError { + /// Signature mismatch. #[error("signature mismatch")] VerifyMsgFail, + /// Bad encoding. #[error("bad encoding")] BadEncoding, + /// Point not on curve. #[error("point not on curve")] PointNotOnCurve, + /// Point not in group. #[error("bad point not in group")] PointNotInGroup, + /// Aggregate type mismatch. #[error("aggregate type mismatch")] AggrTypeMismatch, + /// Public key is infinity. #[error("public key is infinity")] PkIsInfinity, + /// Bad scalar. #[error("bad scalar")] BadScalar, } diff --git a/src/crypto/domain_separator.rs b/src/crypto/domain_separator.rs index 718aabc2..fbe325f3 100644 --- a/src/crypto/domain_separator.rs +++ b/src/crypto/domain_separator.rs @@ -1,3 +1,7 @@ +//! Domain separation utilities. + +/// Static domain separator for the DID method. pub trait DomainSeparator { + /// The domain separator bytes; const DST: &'static [u8]; } diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs deleted file mode 100644 index 0b8ef8c7..00000000 --- a/src/crypto/eddsa.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! EdDSA signature support - -#[cfg(feature = "eddsa-verifier")] -use anyhow::anyhow; -#[cfg(feature = "eddsa-verifier")] -use signature::Verifier; - -// use multibase::Base; - -// impl SignerDid for ed25519_dalek::SigningKey { -// fn did(&self) -> Result { -// let mut buf = unsigned_varint::encode::u128_buffer(); -// let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); -// -// Ok(format!( -// "did:key:{}", -// multibase::encode( -// Base::Base58Btc, -// [multicodec, self.verifying_key().to_bytes().as_ref()].concat() -// ) -// )) -// } -// } - -// /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate -// #[cfg(feature = "eddsa-verifier")] -// pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = ed25519_dalek::VerifyingKey::try_from(key) -// .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; -// -// let signature = ed25519_dalek::Signature::try_from(signature) -// .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs deleted file mode 100644 index 390b3074..00000000 --- a/src/crypto/es256.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES256 signature support - -#[cfg(feature = "es256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256-verifier")] -use signature::Verifier; - -// use super::JWSSignature; -// -// impl JWSSignature for p256::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES256"; -// } -// -// /// A verifier for PS256 signatures -// #[cfg(feature = "es256-verifier")] -// pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; -// -// let signature = -// p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs deleted file mode 100644 index 1000f553..00000000 --- a/src/crypto/es256k.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! ES256K signature support - -#[cfg(feature = "es256k-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256k-verifier")] -use signature::Verifier; - -// use super::JWSSignature; -// -// impl JWSSignature for k256::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES256K"; -// } - -// A verifier for ES256k signatures -// #[cfg(feature = "es256k-verifier")] -// pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = -// k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; -// -// let signature = k256::ecdsa::Signature::try_from(signature) -// .map_err(|_| anyhow!("invalid secp256k1 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs deleted file mode 100644 index 25709142..00000000 --- a/src/crypto/es384.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES384 signature support - -#[cfg(feature = "es384-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es384-verifier")] -use signature::Verifier; - -//use super::JWSSignature; -// -//impl JWSSignature for p384::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES384"; -//} -// -///// A verifier for ES384 signatures -//#[cfg(feature = "es384-verifier")] -//pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; -// -// let signature = -// p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -//} diff --git a/src/crypto/es512.rs b/src/crypto/es512.rs index 0d4d4284..18a31e25 100644 --- a/src/crypto/es512.rs +++ b/src/crypto/es512.rs @@ -1,7 +1,33 @@ -//! ES512 signature support +//! ES512 signature support (P-512) -// use super::JWSSignature; -// -// impl JWSSignature for ecdsa::Signature { -// const ALGORITHM: &'static str = "ES512"; -// } +use p521; +use signature::Verifier; +use std::fmt; + +/// The verifying/public key for ES512. +#[derive(Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); + +impl fmt::Debug for VerifyingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VerifyingKey").finish() + } +} + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_encoded_point(true) == other.0.to_encoded_point(true) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify( + &self, + msg: &[u8], + signature: &p521::ecdsa::Signature, + ) -> Result<(), signature::Error> { + self.0.verify(msg, &signature) + } +} diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs deleted file mode 100644 index 9895df2c..00000000 --- a/src/crypto/ps256.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! PS256 signature support - -// #[cfg(feature = "ps256-verifier")] -// use anyhow::anyhow; -// #[cfg(feature = "ps256-verifier")] -// use signature::Verifier; -// -// use super::JWSSignature; -// -// impl JWSSignature for rsa::pss::Signature { -// const ALGORITHM: &'static str = "PS256"; -// } -// -// /// A verifier for RS256 signatures -// #[cfg(feature = "ps256-verifier")] -// pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) -// .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; -// -// let key = rsa::pss::VerifyingKey::::new(key); -// -// let signature = rsa::pss::Signature::try_from(signature) -// .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index f08729ee..dae3a982 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -1,8 +1,9 @@ -//! RS256 signature support +//! RS256 signature support (2048-bit RSA PKCS #1 v1.5). use rsa; use signature::{SignatureEncoding, Signer, Verifier}; +/// The verifying/public key for RS256. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); @@ -20,6 +21,7 @@ impl Verifier for VerifyingKey { } } +/// The signing/secret key for RS256. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); @@ -29,6 +31,7 @@ impl Signer for SigningKey { } } +/// The signature for RS256. #[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] pub struct Signature(pub rsa::pkcs1v15::Signature); diff --git a/src/crypto/rs512.rs b/src/crypto/rs512.rs index d3af4635..32a739a7 100644 --- a/src/crypto/rs512.rs +++ b/src/crypto/rs512.rs @@ -1,8 +1,9 @@ -//! RS512 signature support +//! RS512 signature support (4096-bit RSA PKCS #1 v1.5). use rsa; use signature::{SignatureEncoding, Signer, Verifier}; +/// The verifying/public key for RS512. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); @@ -20,6 +21,7 @@ impl Verifier for VerifyingKey { } } +/// The signing/secret key for RS512. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); @@ -29,6 +31,7 @@ impl Signer for SigningKey { } } +/// The signature for RS512. #[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] pub struct Signature(pub rsa::pkcs1v15::Signature); diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs index b4548e3e..59c3f304 100644 --- a/src/did/key/traits.rs +++ b/src/did/key/traits.rs @@ -1,4 +1,4 @@ -use crate::crypto::{bls12381, p521, rs256, rs512}; +use crate::crypto::{bls12381, es512, rs256, rs512}; use ::p521 as ext_p521; use ed25519_dalek; use k256; @@ -44,7 +44,7 @@ impl DidKey for p384::ecdsa::VerifyingKey { type Signature = p384::ecdsa::Signature; } -impl DidKey for p521::VerifyingKey { +impl DidKey for es512::VerifyingKey { const BASE58_PREFIX: &'static str = "2J9"; type Signer = ext_p521::ecdsa::SigningKey; diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index e26aa881..9e17cad8 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -15,7 +15,7 @@ use k256; use p384; #[cfg(feature = "es512")] -use crate::crypto::p521; +use crate::crypto::es512; #[cfg(feature = "rs256")] use crate::crypto::rs256; @@ -47,7 +47,7 @@ pub enum Verifier { /// `P-521` verifying key. #[cfg(feature = "es512")] - P521(p521::VerifyingKey), + P521(es512::VerifyingKey), /// `RS256` verifying key. #[cfg(feature = "rs256")] From 83f8e6d127b909d9ffd1da86299b9982f038ba5c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 12:27:44 -0800 Subject: [PATCH 150/234] Newtype invocation --- src/invocation.rs | 68 +++++++++++++++++++++++++++++++++++---- src/invocation/agent.rs | 18 +++++------ src/invocation/payload.rs | 58 +++++++++++++++++---------------- src/receipt.rs | 15 ++++++++- src/receipt/payload.rs | 16 ++++----- src/receipt/responds.rs | 3 +- 6 files changed, 125 insertions(+), 53 deletions(-) diff --git a/src/invocation.rs b/src/invocation.rs index c13d1933..326272c1 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -8,7 +8,8 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, did::Did, signature}; +use crate::{ability, did, did::Did, signature, time::Timestamp}; +use libipld_core::{cid::Cid, ipld::Ipld}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -17,19 +18,74 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID>; +#[derive(Debug, Clone, PartialEq)] +pub struct Invocation(pub signature::Envelope, DID>); // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; +impl Invocation { + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } + + pub fn audience(&self) -> &Option { + &self.0.payload.audience + } + + pub fn issuer(&self) -> &DID { + &self.0.payload.issuer + } + + pub fn subject(&self) -> &DID { + &self.0.payload.subject + } + + pub fn ability(&self) -> &T { + &self.0.payload.ability + } + + pub fn proofs(&self) -> &Vec { + &self.0.payload.proofs + } + + pub fn issued_at(&self) -> &Option { + &self.0.payload.issued_at + } + + pub fn try_sign( + signer: &DID::Signer, + payload: Payload, + ) -> Result, signature::SignError> { + let envelope = signature::Envelope::try_sign(signer, payload)?; + Ok(Invocation(envelope)) + } +} + +impl did::Verifiable for Invocation { + fn verifier(&self) -> &DID { + &self.0.verifier() + } +} + impl Invocation { pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { - let mut payload = self.payload; + let mut payload = self.0.payload; payload.ability = f(payload.ability); - Self { + Invocation(signature::Envelope { payload, - signature: self.signature, - } + signature: self.0.signature, + }) + } +} + +impl From> for Ipld { + fn from(invocation: Invocation) -> Self { + invocation.0.into() } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1a161313..48001f96 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -81,7 +81,7 @@ where metadata: BTreeMap, cause: Option, expiration: Option, - not_before: Option, + issued_at: Option, now: &SystemTime, // FIXME err type ) -> Result, ()> { @@ -104,7 +104,7 @@ where nonce: Nonce::generate_12(&mut seed), cause, expiration, - not_before, + issued_at, }; Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) @@ -134,7 +134,7 @@ where ); let mut encoded = vec![]; - Ipld::from(promised.payload.clone()) + Ipld::from(promised.payload().clone()) // FIXME use the varsig headre to get the codec .encode(DagCborCodec, &mut encoded) .expect("FIXME"); @@ -143,13 +143,13 @@ where .verifier() .verify( &encoded, - &match promised.signature { + &match promised.signature() { Witness::Signature(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; - let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability.clone()) { + let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store @@ -165,19 +165,19 @@ where let proof_payloads = self .delegation_store - .get_many(&promised.payload.proofs) + .get_many(&promised.proofs()) .map_err(|_| ())? .into_iter() .map(|d| d.payload.clone()) .collect(); - let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); + let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) .map_err(|_| ())?; - if promised.payload.audience != Some(self.did.clone()) { + if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); } @@ -222,7 +222,7 @@ where metadata: BTreeMap::new(), nonce: Nonce::generate_12(&mut vec![]), expiration: None, - not_before: None, + issued_at: None, }; let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 40d3fc49..f1aa0f8c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -40,7 +40,7 @@ pub struct Payload { pub metadata: BTreeMap, pub nonce: Nonce, - pub not_before: Option, + pub issued_at: Option, pub expiration: Option, // FIXME this field may not make sense } @@ -64,8 +64,8 @@ impl Payload { cause: self.cause, metadata: self.metadata, nonce: self.nonce, - not_before: self.not_before, - expiration: None, + issued_at: self.issued_at, + expiration: self.expiration, } } @@ -91,20 +91,22 @@ impl Capsule for Payload { impl From> for delegation::Payload { - fn from(payload: Payload) -> Self { + fn from(inv_payload: Payload) -> Self { delegation::Payload { - issuer: payload.issuer.clone(), - subject: payload.subject.clone(), - audience: payload.audience.unwrap_or(payload.subject), + issuer: inv_payload.issuer.clone(), + subject: inv_payload.subject.clone(), + audience: inv_payload.audience.unwrap_or(inv_payload.subject), - ability_builder: T::Builder::from(payload.ability), + ability_builder: T::Builder::from(inv_payload.ability), conditions: vec![], - metadata: payload.metadata, - nonce: payload.nonce, + metadata: inv_payload.metadata, + nonce: inv_payload.nonce, - not_before: payload.not_before, - expiration: Timestamp::postel(SystemTime::now()), // FIXME + not_before: None, + expiration: inv_payload + .expiration + .unwrap_or(Timestamp::postel(SystemTime::now())), } } } @@ -123,16 +125,16 @@ impl, DID: Did> From> for arguments::N ("nonce".into(), payload.nonce.into()), ]); - if let Some(audience) = payload.audience { - args.insert("aud".into(), audience.into().to_string().into()); + if let Some(aud) = payload.audience { + args.insert("aud".into(), aud.into().to_string().into()); } - if let Some(not_before) = payload.not_before { - args.insert("nbf".into(), not_before.into()); + if let Some(iat) = payload.issued_at { + args.insert("iat".into(), iat.into()); } - if let Some(expiration) = payload.expiration { - args.insert("exp".into(), expiration.into()); + if let Some(exp) = payload.expiration { + args.insert("exp".into(), exp.into()); } args @@ -152,7 +154,7 @@ where if self.audience.is_some() { field_count += 1 }; - if self.not_before.is_some() { + if self.issued_at.is_some() { field_count += 1 }; if self.expiration.is_some() { @@ -176,8 +178,8 @@ where state.serialize_field("aud", aud)?; } - if let Some(nbf) = &self.not_before { - state.serialize_field("nbf", nbf)?; + if let Some(iat) = &self.issued_at { + state.serialize_field("iat", iat)?; } if let Some(exp) = &self.expiration { @@ -198,7 +200,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); const FIELDS: &'static [&'static str] = &[ - "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "nbf", "exp", + "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "iat", "exp", ]; impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Visitor<'de> @@ -220,7 +222,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser let mut nonce = None; let mut cause = None; let mut metadata = None; - let mut not_before = None; + let mut issued_at = None; let mut expiration = None; while let Some(key) = map.next_key()? { @@ -279,11 +281,11 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser } metadata = Some(map.next_value()?); } - "nbf" => { - if not_before.is_some() { - return Err(de::Error::duplicate_field("nbf")); + "issued_at" => { + if issued_at.is_some() { + return Err(de::Error::duplicate_field("iat")); } - not_before = map.next_value()?; + issued_at = map.next_value()?; } "exp" => { if expiration.is_some() { @@ -316,7 +318,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser audience, ability, cause, - not_before, + issued_at, expiration, }) } diff --git a/src/receipt.rs b/src/receipt.rs index cb1563ca..8b1d3bf5 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -17,8 +17,21 @@ pub use store::Store; use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID>; +#[derive(Clone, Debug, PartialEq)] +pub struct Receipt(pub signature::Envelope, DID>); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt; + +impl Receipt { + /// Returns the [`Payload`] of the [`Receipt`]. + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + /// Returns the [`signature::Envelope`] of the [`Receipt`]. + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } +} diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 773b884f..e8445318 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -50,7 +50,7 @@ pub struct Payload { /// requested to be queued next. /// /// [`Invocation`]: crate::invocation::Invocation - pub next: Vec, // FIXME rename here or in spec? + pub next: Vec, /// An optional proof chain authorizing a different [`Did`] to /// be the receipt `iss` than the audience (or subject) of the @@ -194,13 +194,13 @@ where } Ok(Payload { - issuer: issuer.ok_or_else(|| de::Error::missing_field("iss"))?, - ran: ran.ok_or_else(|| de::Error::missing_field("ran"))?, - out: out.ok_or_else(|| de::Error::missing_field("out"))?, - next: next.ok_or_else(|| de::Error::missing_field("next"))?, - proofs: proofs.ok_or_else(|| de::Error::missing_field("prf"))?, - metadata: metadata.ok_or_else(|| de::Error::missing_field("meta"))?, - nonce: nonce.ok_or_else(|| de::Error::missing_field("nonce"))?, + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + ran: ran.ok_or(de::Error::missing_field("ran"))?, + out: out.ok_or(de::Error::missing_field("out"))?, + next: next.ok_or(de::Error::missing_field("next"))?, + proofs: proofs.ok_or(de::Error::missing_field("prf"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, issued_at, }) } diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 723d9e7d..6f7ca552 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,5 @@ use crate::{nonce::Nonce, task, task::Task}; +use std::fmt; /// Describe the relationship between an ability and the [`Receipt`]s. /// @@ -8,7 +9,7 @@ use crate::{nonce::Nonce, task, task::Task}; /// [`Receipt`]: crate::receipt::Receipt pub trait Responds { /// The successful return type for running `Self`. - type Success; + type Success: Clone + fmt::Debug + PartialEq; /// Convert an Ability (`Self`) into a [`Task`]. /// From 233397ea57c40ffa8f9e62d40bbd857e3bd408ef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 13:02:41 -0800 Subject: [PATCH 151/234] Delegation newtype --- src/delegation.rs | 73 +++++++++++++++++++++++++++++---------- src/delegation/agent.rs | 4 +-- src/delegation/payload.rs | 10 +++--- src/delegation/store.rs | 30 ++++++++-------- src/invocation.rs | 12 ++++++- src/invocation/agent.rs | 6 ++-- src/invocation/payload.rs | 18 ++++++++-- src/signature/envelope.rs | 13 +++++-- src/time/timestamp.rs | 2 +- 9 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/delegation.rs b/src/delegation.rs index b91ff05f..9d71ce31 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -14,7 +14,7 @@ use crate::{ ability, did::{self, Did}, nonce::Nonce, - proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + proof::{parents::CheckParents, same::CheckSame}, signature, time::{TimeBoundError, Timestamp}, }; @@ -30,69 +30,104 @@ use web_time::SystemTime; /// # Examples /// FIXME /// FIXME wrap in struct to make the docs & error messages better? -pub type Delegation = signature::Envelope, DID>; +#[derive(Clone, Debug, PartialEq)] +pub struct Delegation(pub signature::Envelope, DID>); pub type Preset = Delegation; // FIXME checkable -> provable? -impl Delegation { +impl Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { - &self.payload.issuer + &self.0.payload.issuer } /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &DID { - &self.payload.subject + &self.0.payload.subject } /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { - &self.payload.audience + &self.0.payload.audience } /// Retrive the `ability_builder` of a [`Delegation`] pub fn ability_builder(&self) -> &B { - &self.payload.ability_builder + &self.0.payload.ability_builder + } + + pub fn map_ability_builder(self, f: F) -> Delegation + where + F: FnOnce(B) -> T, + { + Delegation(signature::Envelope { + payload: self.0.payload.map_ability(f), + signature: self.0.signature, + }) } /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { - &self.payload.conditions + &self.0.payload.conditions } /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { - &self.payload.metadata + &self.0.payload.metadata } /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { - &self.payload.nonce + &self.0.payload.nonce } /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { - self.payload.not_before.as_ref() + self.0.payload.not_before.as_ref() } /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { - &self.payload.expiration + &self.0.payload.expiration } - /// Retrive the `signature` of a [`Delegation`] pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - self.payload.check_time(now) + self.0.payload.check_time(now) + } + + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } + + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> + where + Payload: Clone, + { + self.0.validate_signature() + } + + pub fn try_sign( + signer: &DID::Signer, + payload: Payload, + ) -> Result + where + Payload: Clone, + { + signature::Envelope::try_sign(signer, payload).map(Delegation) } } -impl CheckSame for Delegation { - type Error = ::Error; +impl CheckSame for Delegation { + type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { - self.payload.check_same(&proof.payload) + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + self.0.payload.check_same(&proof.payload()) } } @@ -101,6 +136,6 @@ impl CheckParents for Delegation::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.payload.check_parent(&proof.payload) + self.payload().check_parent(&proof.payload()) } } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 5df85df3..7db39707 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -43,7 +43,7 @@ impl< metadata: BTreeMap, expiration: Timestamp, not_before: Option, - now: &SystemTime, + now: SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -71,7 +71,7 @@ impl< .ok_or(DelegateError::ProofsNotFound)? .first() .1 - .payload; + .payload(); let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0970bcb8..9eafbe71 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -122,12 +122,14 @@ impl Payload { } pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - if SystemTime::from(self.expiration.clone()) < now { + let ts_now = &Timestamp::postel(now); + + if &self.expiration < ts_now { return Err(TimeBoundError::Expired); } - if let Some(nbf) = self.not_before.clone() { - if SystemTime::from(nbf) > now { + if let Some(ref nbf) = self.not_before { + if nbf > ts_now { return Err(TimeBoundError::NotYetValid); } } @@ -371,7 +373,7 @@ impl>, C: Condition, DID: Did { pub fn check( &self, - proofs: Vec>, + proofs: Vec<&Payload>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 184e2f96..233492e2 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -34,7 +34,7 @@ pub trait Store { subject: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result)>>, Self::Error>; fn can_delegate( @@ -43,7 +43,7 @@ pub trait Store { audience: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result { self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) @@ -138,16 +138,14 @@ where fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index - .entry(delegation.payload.subject.clone()) + .entry(delegation.subject().clone()) .or_default() - .entry(delegation.payload.audience.clone()) + .entry(delegation.audience().clone()) .or_default() .insert(cid); - let hierarchy: Delegation = Delegation { - signature: delegation.signature, - payload: delegation.payload.map_ability(Into::into), - }; + let hierarchy: Delegation = + delegation.map_ability_builder(Into::into); self.ucans.insert(cid.clone(), hierarchy); Ok(()) @@ -164,7 +162,7 @@ where subject: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), @@ -188,30 +186,30 @@ where return ControlFlow::Continue(()); } - if d.payload.check_time(*now).is_err() { + if d.check_time(now).is_err() { return ControlFlow::Continue(()); } - target_aud = &d.payload.audience; + target_aud = &d.audience(); - if args.check(&d.payload.ability_builder).is_ok() { - args = &d.payload.ability_builder; + if args.check(&d.ability_builder()).is_ok() { + args = &d.ability_builder(); } else { return ControlFlow::Continue(()); } for condition in &conditions { - if !condition.validate(&d.payload.ability_builder.clone().into()) { + if !condition.validate(&d.ability_builder().clone().into()) { return ControlFlow::Continue(()); } } chain.push((*cid, d)); - if &d.payload.issuer == subject { + if d.issuer() == subject { status = Status::Complete; } else { - target_aud = &d.payload.issuer; + target_aud = &d.issuer(); } ControlFlow::Break(()) diff --git a/src/invocation.rs b/src/invocation.rs index 326272c1..509b6ae5 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -8,8 +8,14 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, did::Did, signature, time::Timestamp}; +use crate::{ + ability, did, + did::Did, + signature, + time::{TimeBoundError, Timestamp}, +}; use libipld_core::{cid::Cid, ipld::Ipld}; +use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -58,6 +64,10 @@ impl Invocation { &self.0.payload.issued_at } + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + self.0.payload.check_time(now) + } + pub fn try_sign( signer: &DID::Signer, payload: Payload, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 48001f96..2d19d317 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -82,7 +82,7 @@ where cause: Option, expiration: Option, issued_at: Option, - now: &SystemTime, + now: SystemTime, // FIXME err type ) -> Result, ()> { let proofs = self @@ -168,7 +168,7 @@ where .get_many(&promised.proofs()) .map_err(|_| ())? .into_iter() - .map(|d| d.payload.clone()) + .map(|d| d.payload()) .collect(); let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); @@ -205,7 +205,7 @@ where self.did, &ability.clone().into(), vec![], - &now.into(), + now.into(), ) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f1aa0f8c..6f00e12b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,9 +9,8 @@ use crate::{ did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - time::Timestamp, + time::{TimeBoundError, Timestamp}, }; -// use anyhow; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{ de::{self, MapAccess, Visitor}, @@ -69,9 +68,22 @@ impl Payload { } } + // FIXME err type + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + let ts_now = &Timestamp::postel(now); + + if let Some(ref exp) = self.expiration { + if exp < ts_now { + panic!("FIXME") + } + } + + Ok(()) + } + pub fn check( self, - proofs: Vec::Hierarchy, C, DID>>, + proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs index cd14f420..75d41ec5 100644 --- a/src/signature/envelope.rs +++ b/src/signature/envelope.rs @@ -32,7 +32,7 @@ impl + Capsule, DID: Did> Verifiable for Envelope + Into + Clone, DID: Did> Envelope { +impl + Into, DID: Did> Envelope { /// Attempt to sign some payload with a given signer. /// /// # Arguments @@ -47,7 +47,10 @@ impl + Into + Clone, DID: Did> Envelope Result, SignError> { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> + where + T: Clone, + { Self::try_sign_generic::(signer, DagCborCodec, payload) } @@ -72,6 +75,7 @@ impl + Into + Clone, DID: Did> Envelope Result, SignError> where + T: Clone, Ipld: Encode, { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); @@ -103,7 +107,10 @@ impl + Into + Clone, DID: Did> Envelope Result<(), ValidateError> { + pub fn validate_signature(&self) -> Result<(), ValidateError> + where + T: Clone, + { // FIXME need varsig let codec = DagCborCodec; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 6e649e74..2d608a98 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -19,7 +19,7 @@ use web_time::{Duration, SystemTime, UNIX_EPOCH}; /// the public API. /// /// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct Timestamp { time: SystemTime, From c2dcb1d303d95aec21e5bbfbb4086e60d11bd1f4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 13:17:44 -0800 Subject: [PATCH 152/234] Better docs --- src/delegation/payload.rs | 6 ++--- src/invocation.rs | 51 +++++++++++++++++++++-------------- src/invocation/payload.rs | 57 ++++++++++++++++++++------------------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 9eafbe71..4dea88f2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -368,15 +368,15 @@ impl From> for Ipld { } } -impl>, C: Condition, DID: Did + Clone> - Payload -{ +impl>, C: Condition, DID: Did> Payload { pub fn check( &self, proofs: Vec<&Payload>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where + T: Clone, + DID: Clone, T::Hierarchy: Clone + Into>, { let start: Acc = Acc { diff --git a/src/invocation.rs b/src/invocation.rs index 509b6ae5..4465474a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,17 +22,17 @@ use web_time::SystemTime; /// # Promises /// /// For a version that can include [`Promise`][promise::Promise]s, -/// wrap your `T` in [`invocation::Promised`](Promised) to get -/// `Invocation>`. +/// wrap your `A` in [`invocation::Promised`](Promised) to get +/// `Invocation>`. #[derive(Debug, Clone, PartialEq)] -pub struct Invocation(pub signature::Envelope, DID>); +pub struct Invocation(pub signature::Envelope, DID>); // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; -impl Invocation { - pub fn payload(&self) -> &Payload { +impl Invocation { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -52,10 +52,20 @@ impl Invocation { &self.0.payload.subject } - pub fn ability(&self) -> &T { + pub fn ability(&self) -> &A { &self.0.payload.ability } + pub fn map_ability(self, f: F) -> Invocation + where + F: FnOnce(A) -> Z, + { + Invocation(signature::Envelope { + payload: self.0.payload.map_ability(f), + signature: self.0.signature, + }) + } + pub fn proofs(&self) -> &Vec { &self.0.payload.proofs } @@ -64,35 +74,36 @@ impl Invocation { &self.0.payload.issued_at } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + pub fn expiration(&self) -> &Option { + &self.0.payload.expiration + } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> + where + A: Clone, + { self.0.payload.check_time(now) } pub fn try_sign( signer: &DID::Signer, - payload: Payload, - ) -> Result, signature::SignError> { + payload: Payload, + ) -> Result, signature::SignError> + where + Payload: Clone, + { let envelope = signature::Envelope::try_sign(signer, payload)?; Ok(Invocation(envelope)) } } -impl did::Verifiable for Invocation { +impl did::Verifiable for Invocation { fn verifier(&self) -> &DID { &self.0.verifier() } } -impl Invocation { - pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { - let mut payload = self.0.payload; - payload.ability = f(payload.ability); - Invocation(signature::Envelope { - payload, - signature: self.0.signature, - }) - } -} +impl Invocation {} impl From> for Ipld { fn from(invocation: Invocation) -> Self { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6f00e12b..5d2aba6c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -27,12 +27,12 @@ impl Verifiable for Payload { } #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: T, + pub ability: A, pub proofs: Vec, pub cause: Option, @@ -49,10 +49,10 @@ pub struct Payload { // // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type -impl Payload { - pub fn map_ability(self, f: F) -> Payload +impl Payload { + pub fn map_ability(self, f: F) -> Payload where - F: FnOnce(T) -> U, + F: FnOnce(A) -> Z, { Payload { issuer: self.issuer, @@ -83,33 +83,34 @@ impl Payload { pub fn check( self, - proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, + proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - T: Delegable, - T::Builder: Clone + Checkable + Prove + Into>, - ::Hierarchy: Clone + Into>, + A: Delegable, + A::Builder: Clone + Into>, + ::Hierarchy: Clone + Into>, + DID: Clone, { - let builder_payload: delegation::Payload = self.into(); + let builder_payload: delegation::Payload = self.into(); builder_payload.check(proofs, now) } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> - for delegation::Payload +impl From> + for delegation::Payload { - fn from(inv_payload: Payload) -> Self { + fn from(inv_payload: Payload) -> Self { delegation::Payload { - issuer: inv_payload.issuer.clone(), + issuer: inv_payload.issuer, subject: inv_payload.subject.clone(), audience: inv_payload.audience.unwrap_or(inv_payload.subject), - ability_builder: T::Builder::from(inv_payload.ability), + ability_builder: A::Builder::from(inv_payload.ability), conditions: vec![], metadata: inv_payload.metadata, @@ -123,8 +124,8 @@ impl From> } } -impl, DID: Did> From> for arguments::Named { - fn from(payload: Payload) -> Self { +impl, DID: Did> From> for arguments::Named { + fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ ("iss".into(), payload.issuer.into().to_string().into()), ("sub".into(), payload.subject.into().to_string().into()), @@ -153,9 +154,9 @@ impl, DID: Did> From> for arguments::N } } -impl Serialize for Payload +impl Serialize for Payload where - T: ToCommand + Into + Serialize, + A: ToCommand + Into + Serialize, DID: Did + Serialize, { fn serialize(&self, serializer: S) -> Result @@ -202,14 +203,14 @@ where } } -impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> - for Payload +impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> + for Payload { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, { - struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); + struct InvocationPayloadVisitor(std::marker::PhantomData<(A, DID)>); const FIELDS: &'static [&'static str] = &[ "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "iat", "exp", @@ -347,10 +348,10 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised, DID>; +pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } From a358ffb9aa56a42cfa7f471b9e46b467c9eb81c5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 16:32:46 -0800 Subject: [PATCH 153/234] Break up a few modules --- src/ability/crud.rs | 2 +- src/ability/crud/create.rs | 4 +- src/ability/crud/destroy.rs | 4 +- src/ability/crud/read.rs | 4 +- src/ability/crud/update.rs | 4 +- src/ability/msg.rs | 2 +- src/ability/msg/receive.rs | 4 +- src/ability/msg/send.rs | 4 +- src/ability/preset.rs | 2 +- src/ability/ucan/revoke.rs | 42 +++-- src/ability/wasm/run.rs | 4 +- src/delegation.rs | 19 +- src/delegation/agent.rs | 6 + src/delegation/condition.rs | 2 + src/delegation/delegable.rs | 10 +- src/delegation/error.rs | 24 --- src/delegation/payload.rs | 54 +++--- src/delegation/store.rs | 236 +------------------------ src/delegation/store/memory.rs | 185 +++++++++++++++++++ src/delegation/store/traits.rs | 57 ++++++ src/invocation.rs | 43 +++-- src/invocation/agent.rs | 11 +- src/invocation/payload.rs | 95 +++++++--- src/invocation/promise.rs | 6 +- src/invocation/promise/any.rs | 8 + src/invocation/promise/err.rs | 6 + src/invocation/promise/ok.rs | 6 + src/invocation/promise/resolvable.rs | 22 +++ src/invocation/promise/resolves.rs | 8 + src/invocation/promise/store.rs | 7 + src/invocation/promise/store/memory.rs | 46 +++++ src/invocation/promise/store/traits.rs | 14 ++ src/invocation/resolvable.rs | 19 -- src/invocation/store.rs | 78 +------- src/time.rs | 2 +- src/time/error.rs | 9 +- 36 files changed, 599 insertions(+), 450 deletions(-) delete mode 100644 src/delegation/error.rs create mode 100644 src/delegation/store/memory.rs create mode 100644 src/delegation/store/traits.rs create mode 100644 src/invocation/promise/resolvable.rs create mode 100644 src/invocation/promise/store.rs create mode 100644 src/invocation/promise/store/memory.rs create mode 100644 src/invocation/promise/store/traits.rs delete mode 100644 src/invocation/resolvable.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c080ae6d..98c8804e 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -54,7 +54,7 @@ pub use parents::*; use crate::{ ability::arguments, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 622cb06a..e62494ac 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -276,7 +276,7 @@ impl From for Builder { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index ec487632..f47200e0 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -226,7 +226,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 6c2ce833..99ee9022 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -4,7 +4,7 @@ use super::any as crud; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -249,7 +249,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index fdb81752..0dc4f992 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -290,7 +290,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 01e1ee0a..7f7e33ab 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -11,7 +11,7 @@ pub use receive::Receive; use crate::{ ability::arguments, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 1c152c9f..36381e04 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -3,7 +3,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; @@ -132,7 +132,7 @@ impl From for arguments::Named { } } -impl Resolvable for Receive { +impl promise::Resolvable for Receive { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index d5974af7..96d3299b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -3,7 +3,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, }; @@ -127,7 +127,7 @@ impl Delegable for Ready { type Builder = Builder; } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 48ebb30b..50822bb7 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -2,7 +2,7 @@ use super::{crud, msg, wasm}; use crate::{ ability::{arguments, command::ParseAbility}, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index ac3aacb8..a0d61502 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -1,32 +1,28 @@ -//! UCAN [Revocations](https://github.com/ucan-wg/revocation) +//! This is an ability for revoking [`Delegation`][crate::delegation::Delegation]s by their [`Cid`]. +//! +//! For more, see the [UCAN Revocation spec](https://github.com/ucan-wg/revocation). use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{parentless::NoParents, same::CheckSame}, + invocation::promise, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; -/// An ability for revoking previously issued UCANs by [`Cid`] +/// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - // FIXME check spec +pub struct Ready { /// The UCAN to revoke - pub ucan: Arg, + pub ucan: Cid, } -impl Command for Generic { +impl Command for Ready { const COMMAND: &'static str = "ucan/revoke"; } -/// The fully resolved variant: ready to execute. -pub type Ready = Generic; - -impl NoParents for Builder {} - impl Delegable for Ready { type Builder = Builder; } @@ -39,7 +35,7 @@ impl From for Builder { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(promised: Self::Promised) -> Result { @@ -51,13 +47,18 @@ impl Resolvable for Ready { } /// A variant with some fields waiting to be set. -pub type Builder = Generic>; +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct Builder { + pub ucan: Option, +} + +impl NoParents for Builder {} impl CheckSame for Builder { - type Error = (); // FIXME + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.ucan.check_same(&proof.ucan).map_err(|_| ()) + self.ucan.check_same(&proof.ucan) } } @@ -89,8 +90,11 @@ impl From for arguments::Named { } } -/// A variant where arguments may be [`Promise`]s. -pub type Promised = Generic>; +/// A variant where arguments may be [`Promise`][crate::invocation::promise]s. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Promised { + pub ucan: promise::Resolves, +} impl From for Promised { fn from(r: Ready) -> Promised { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 6ee7d6df..ecf4be6a 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -4,7 +4,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -35,7 +35,7 @@ impl Delegable for Ready { type Builder = Builder; } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(promised: Self::Promised) -> Result { diff --git a/src/delegation.rs b/src/delegation.rs index 9d71ce31..d0b951ad 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,5 +1,18 @@ +//! An [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. +//! +//! ## Data +//! +//! - [`Delegation`] is the top-level, signed data struture. +//! - [`Payload`] is the fields unique to an invocation. +//! - [`Preset`] is an [`Delegation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). +//! - [`Condition`]s are syntactically-driven validation rules for [`Delegation`]s. +//! +//! ## Stateful Helpers +//! +//! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. +//! - [`store`] is an interface for caching [`Delegation`]s. + pub mod condition; -pub mod error; pub mod store; mod agent; @@ -8,7 +21,7 @@ mod payload; pub use agent::Agent; pub use delegable::Delegable; -pub use payload::Payload; +pub use payload::{Payload, ValidationError}; use crate::{ ability, @@ -29,10 +42,10 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -/// FIXME wrap in struct to make the docs & error messages better? #[derive(Clone, Debug, PartialEq)] pub struct Delegation(pub signature::Envelope, DID>); +/// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation; // FIXME checkable -> provable? diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 7db39707..a9a152ce 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,9 +5,15 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; +/// A stateful agent capable of delegatint to others, and being delegated to. +/// +/// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { + /// The [`Did`][Did] of the agent. pub did: &'a DID, + + /// The attached [`deleagtion::Store`][super::store::Store]. pub store: &'a mut S, signer: &'a ::Signer, diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 79746ff1..f9594bbc 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,3 +1,5 @@ +//! Conditions for syntactic validation of abilities in [`Delegation`][super::Delegation]s. + mod contains_all; mod contains_any; mod contains_key; diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 925aee08..19806dd5 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,7 +1,13 @@ use crate::proof::checkable::Checkable; +/// A trait for types that can be delegated. +/// +/// Since [`Delegation`]s may omit fields (until [`Invocation`]), +/// this trait helps associate the delegatable variant to the invocable one. +/// +/// [`Delegation`]: crate::delegation::Delegation +/// [`Invocation`]: crate::invocation::Invocation pub trait Delegable: Sized { - /// A delegation with some arguments filled - /// FIXME add more text + /// A delegation with some arguments filled. type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/error.rs b/src/delegation/error.rs deleted file mode 100644 index 21bafe1e..00000000 --- a/src/delegation/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -// FIXME rename this is not for the sign envelope -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EnvelopeError { - InvalidSubject, - MisalignedIssAud, - Expired, - NotYetValid, -} - -// FIXME Error, etc -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DelegationError { - Envelope(EnvelopeError), - - FailedCondition, // FIXME add context? - - SemanticError(Semantic), -} - -impl From for DelegationError { - fn from(err: EnvelopeError) -> Self { - DelegationError::Envelope(err) - } -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4dea88f2..4143da78 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,7 +1,4 @@ -use super::{ - condition::Condition, - error::{DelegationError, EnvelopeError}, -}; +use super::condition::Condition; use crate::{ ability::{ arguments, @@ -25,6 +22,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; impl Verifiable for Payload { @@ -373,9 +371,10 @@ impl>, C: Condition, DID: Did> Payloa &self, proofs: Vec<&Payload>, now: &SystemTime, - ) -> Result<(), DelegationError<::Error>> + ) -> Result<(), ValidationError<::Error, C>> where T: Clone, + C: fmt::Debug + Clone, DID: Clone, T::Hierarchy: Clone + Into>, { @@ -426,25 +425,26 @@ impl Acc { proof: &Payload, args: &arguments::Named, now: &SystemTime, - ) -> Result::Error>> + ) -> Result::Error, C>> where + C: fmt::Debug + Clone, H: Prove + Clone + Into>, { if self.issuer != proof.audience { - return Err(EnvelopeError::InvalidSubject.into()); + return Err(ValidationError::InvalidSubject.into()); } if self.subject != proof.subject { - return Err(EnvelopeError::MisalignedIssAud.into()); + return Err(ValidationError::MisalignedIssAud.into()); } if SystemTime::from(proof.expiration.clone()) > *now { - return Err(EnvelopeError::Expired.into()); + return Err(ValidationError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > *now { - return Err(EnvelopeError::NotYetValid.into()); + return Err(ValidationError::NotYetValid.into()); } } @@ -456,22 +456,34 @@ impl Acc { // Plz let me know if I got this wrong. // —@expede if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { - return Err(DelegationError::FailedCondition); + return Err(ValidationError::FailedCondition(c.clone())); } } self.hierarchy .check(&proof.ability_builder.clone()) - .map_err(DelegationError::SemanticError) + .map_err(ValidationError::AbilityError) } } -// use crate::proof::{parentful::Parentful, parentless::Parentless}; -// -// impl>, C, DID: Did> Checkable for Payload { -// type Hierarchy = Parentless>; -// } -// -// impl>, C, DID: Did> Checkable for Payload { -// type Hierarchy = Parentful>; -// } +/// Delegation validation errors. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ValidationError { + #[error("The subject of the delegation is invalid")] + InvalidSubject, + + #[error("The issuer and audience of the delegation are misaligned")] + MisalignedIssAud, + + #[error("The delegation has expired")] + Expired, + + #[error("The delegation is not yet valid")] + NotYetValid, + + #[error("The delegation failed a condition: {0:?}")] + FailedCondition(C), // FIXME add context? + + #[error(transparent)] + AbilityError(AbilityError), +} diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 233492e2..680d5f99 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,233 +1,7 @@ -use super::{condition::Condition, Delegation}; -use crate::{ - ability::arguments, - did::Did, - proof::{checkable::Checkable, prove::Prove}, -}; -use libipld_core::{cid::Cid, ipld::Ipld}; -use nonempty::NonEmpty; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; -use web_time::SystemTime; +//! Storage interface for [`Delegation`][super::Delegation]s. -// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { - type Error; +mod memory; +mod traits; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; - - // FIXME add a variant that calculated the CID from the capsulre header? - // FIXME that means changing the name to insert_by_cid or similar - // FIXME rename put - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; - - // FIXME validate invocation - // sore invocation - // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; - - fn get_chain( - &self, - audience: &DID, - subject: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result)>>, Self::Error>; - - fn can_delegate( - &self, - issuer: &DID, - audience: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result { - self.get_chain(audience, issuer, builder, conditions, now) - .map(|chain| chain.is_some()) - } - - fn get_many( - &self, - cids: &[Cid], - ) -> Result>, Self::Error> { - cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; - acc.push(d); - Ok(acc) - }) - } -} - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// A simple in-memory store for delegations. -/// -/// The store is laid out as follows: -/// -/// `{Subject => {Audience => {Cid => Delegation}}}` -/// -/// ```mermaid -/// flowchart LR -/// subgraph Subjects -/// direction TB -/// -/// Akiko -/// Boris -/// Carol -/// -/// subgraph aud[Boris's Audiences] -/// direction TB -/// -/// Denzel -/// Erin -/// Frida -/// Georgia -/// Hugo -/// -/// subgraph cid[Frida's CIDs] -/// direction LR -/// -/// CID1 --> Delegation1 -/// CID2 --> Delegation2 -/// CID3 --> Delegation3 -/// end -/// end -/// end -/// -/// Akiko ~~~ Hugo -/// Carol ~~~ Hugo -/// Boris --> Frida --> CID2 -/// -/// Boris -.-> Denzel -/// Boris -.-> Erin -/// Boris -.-> Georgia -/// Boris -.-> Hugo -/// -/// Frida -.-> CID1 -/// Frida -.-> CID3 -/// -/// style Boris stroke:orange; -/// style Frida stroke:orange; -/// style CID2 stroke:orange; -/// style Delegation2 stroke:orange; -/// -/// linkStyle 5 stroke:orange; -/// linkStyle 6 stroke:orange; -/// linkStyle 1 stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, - index: BTreeMap>>, - revocations: BTreeSet, -} - -// FIXME check that UCAN is valid -impl Store - for MemoryStore -where - B::Hierarchy: Into> + Clone, -{ - type Error = (); // FIXME misisng - - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(()) - } - - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { - self.index - .entry(delegation.subject().clone()) - .or_default() - .entry(delegation.audience().clone()) - .or_default() - .insert(cid); - - let hierarchy: Delegation = - delegation.map_ability_builder(Into::into); - - self.ucans.insert(cid.clone(), hierarchy); - Ok(()) - } - - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { - self.revocations.insert(cid); - Ok(()) - } - - fn get_chain( - &self, - aud: &DID, - subject: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result)>>, Self::Error> { - match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { - None => Ok(None), - Some(delegation_subtree) => { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } - - let mut status = Status::Looking; - let mut target_aud = aud; - let mut args = &B::Hierarchy::from(builder.clone()); - let mut chain = vec![]; - - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(cid) { - return ControlFlow::Continue(()); - } - - if d.check_time(now).is_err() { - return ControlFlow::Continue(()); - } - - target_aud = &d.audience(); - - if args.check(&d.ability_builder()).is_ok() { - args = &d.ability_builder(); - } else { - return ControlFlow::Continue(()); - } - - for condition in &conditions { - if !condition.validate(&d.ability_builder().clone().into()) { - return ControlFlow::Continue(()); - } - } - - chain.push((*cid, d)); - - if d.issuer() == subject { - status = Status::Complete; - } else { - target_aud = &d.issuer(); - } - - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }); - - if found.is_continue() { - status = Status::NoPath; - } - } - - match status { - Status::Complete => Ok(NonEmpty::from_vec(chain)), - _ => Ok(None), - } - } - } - } -} +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs new file mode 100644 index 00000000..27dd6e93 --- /dev/null +++ b/src/delegation/store/memory.rs @@ -0,0 +1,185 @@ +use super::Store; +use crate::{ + ability::arguments, + delegation::{condition::Condition, Delegation}, + did::Did, + proof::{checkable::Checkable, prove::Prove}, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use nonempty::NonEmpty; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; +use web_time::SystemTime; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// A simple in-memory store for delegations. +/// +/// The store is laid out as follows: +/// +/// `{Subject => {Audience => {Cid => Delegation}}}` +/// +/// ```mermaid +/// flowchart LR +/// subgraph Subjects +/// direction TB +/// +/// Akiko +/// Boris +/// Carol +/// +/// subgraph aud[Boris's Audiences] +/// direction TB +/// +/// Denzel +/// Erin +/// Frida +/// Georgia +/// Hugo +/// +/// subgraph cid[Frida's CIDs] +/// direction LR +/// +/// CID1 --> Delegation1 +/// CID2 --> Delegation2 +/// CID3 --> Delegation3 +/// end +/// end +/// end +/// +/// Akiko ~~~ Hugo +/// Carol ~~~ Hugo +/// Boris --> Frida --> CID2 +/// +/// Boris -.-> Denzel +/// Boris -.-> Erin +/// Boris -.-> Georgia +/// Boris -.-> Hugo +/// +/// Frida -.-> CID1 +/// Frida -.-> CID3 +/// +/// style Boris stroke:orange; +/// style Frida stroke:orange; +/// style CID2 stroke:orange; +/// style Delegation2 stroke:orange; +/// +/// linkStyle 5 stroke:orange; +/// linkStyle 6 stroke:orange; +/// linkStyle 1 stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, + revocations: BTreeSet, +} + +// FIXME check that UCAN is valid +impl Store + for MemoryStore +where + B::Hierarchy: Into> + Clone, +{ + type Error = (); // FIXME misisng + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(()) + } + + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + self.index + .entry(delegation.subject().clone()) + .or_default() + .entry(delegation.audience().clone()) + .or_default() + .insert(cid); + + let hierarchy: Delegation = + delegation.map_ability_builder(Into::into); + + self.ucans.insert(cid.clone(), hierarchy); + Ok(()) + } + + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { + self.revocations.insert(cid); + Ok(()) + } + + fn get_chain( + &self, + aud: &DID, + subject: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result)>>, Self::Error> { + match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + None => Ok(None), + Some(delegation_subtree) => { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } + + let mut status = Status::Looking; + let mut target_aud = aud; + let mut args = &B::Hierarchy::from(builder.clone()); + let mut chain = vec![]; + + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if let Some(d) = self.ucans.get(cid) { + if self.revocations.contains(cid) { + return ControlFlow::Continue(()); + } + + if d.check_time(now).is_err() { + return ControlFlow::Continue(()); + } + + target_aud = &d.audience(); + + if args.check(&d.ability_builder()).is_ok() { + args = &d.ability_builder(); + } else { + return ControlFlow::Continue(()); + } + + for condition in &conditions { + if !condition.validate(&d.ability_builder().clone().into()) { + return ControlFlow::Continue(()); + } + } + + chain.push((*cid, d)); + + if d.issuer() == subject { + status = Status::Complete; + } else { + target_aud = &d.issuer(); + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; + } + } + + match status { + Status::Complete => Ok(NonEmpty::from_vec(chain)), + _ => Ok(None), + } + } + } + } +} diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs new file mode 100644 index 00000000..c1129344 --- /dev/null +++ b/src/delegation/store/traits.rs @@ -0,0 +1,57 @@ +use crate::{ + delegation::{condition::Condition, Delegation}, + did::Did, + proof::checkable::Checkable, +}; +use libipld_core::cid::Cid; +use nonempty::NonEmpty; +use web_time::SystemTime; + +// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? +pub trait Store { + type Error; + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + + // FIXME add a variant that calculated the CID from the capsulre header? + // FIXME that means changing the name to insert_by_cid or similar + // FIXME rename put + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + + // FIXME validate invocation + // sore invocation + // just... move to invocation + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; + + fn get_chain( + &self, + audience: &DID, + subject: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result)>>, Self::Error>; + + fn can_delegate( + &self, + issuer: &DID, + audience: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result { + self.get_chain(audience, issuer, builder, conditions, now) + .map(|chain| chain.is_some()) + } + + fn get_many( + &self, + cids: &[Cid], + ) -> Result>, Self::Error> { + cids.iter().try_fold(vec![], |mut acc, cid| { + let d: &Delegation = self.get(cid)?; + acc.push(d); + Ok(acc) + }) + } +} diff --git a/src/invocation.rs b/src/invocation.rs index 4465474a..6aa7846b 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,37 +1,60 @@ +//! An [`Invocation`] is a request to use an [`Ability`][crate::ability]. +//! +//! ## Data +//! +//! - [`Invocation`] is the top-level, signed data struture. +//! - [`Payload`] is the fields unique to an invocation. +//! - [`Preset`] is an [`Invocation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). +//! - [`promise`]s are a mechanism to chain invocations together. +//! +//! ## Stateful Helpers +//! +//! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. +//! - [`store`] is an interface for caching [`Invocation`]s. + +mod agent; mod payload; -mod resolvable; -pub mod agent; pub mod promise; pub mod store; +pub use agent::Agent; pub use payload::{Payload, Promised}; -pub use resolvable::Resolvable; use crate::{ ability, did, did::Did, signature, - time::{TimeBoundError, Timestamp}, + time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. /// -/// # Promises +/// Invocations are the actual "doing" in the UCAN lifecycle. +/// Unlike [`Delegation`][crate::Delegation]s, which live for some period of time and +/// can be used multiple times, [`Invocation`]s are unique and single-use. /// -/// For a version that can include [`Promise`][promise::Promise]s, -/// wrap your `A` in [`invocation::Promised`](Promised) to get -/// `Invocation>`. +/// # Expiration +/// +/// `Invocations` include an optional expiration field which behaves like a timeout: +/// "if this isn't run by a the expiration time, I'm going to assume that it didn't happen." +/// This is a best practice in message-passing distributed systems because the network is +/// [unreliable](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). #[derive(Debug, Clone, PartialEq)] pub struct Invocation(pub signature::Envelope, DID>); -// FIXME use presnet ability, too +/// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Invocation; + pub type PresetPromised = Invocation; impl Invocation { + pub fn new(payload: Payload, signature: signature::Witness) -> Self { + Invocation(signature::Envelope { payload, signature }) + } + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -78,7 +101,7 @@ impl Invocation { &self.0.payload.expiration } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> where A: Clone, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 2d19d317..0250296f 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,4 +1,4 @@ -use super::{payload::Payload, store::Store, Invocation, Resolvable}; +use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, delegation, @@ -16,7 +16,7 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use std::{collections::BTreeMap, marker::PhantomData}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; use web_time::SystemTime; #[derive(Debug)] @@ -51,7 +51,6 @@ impl< > Agent<'a, T, C, DID, S, P, D> where T::Promised: Clone, - // Payload<::Hierarchy, DID>: Clone, // FIXME delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( @@ -117,6 +116,7 @@ where // FIXME return type ) -> Result>, ()> where + C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, T::Builder: Clone + Checkable + Prove + Into>, { @@ -237,8 +237,3 @@ pub enum Recipient { You(T), Other(T), } - -// impl Agent { -// FIXME err = () -// FIXME move to revocation agent wit own traits? -// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5d2aba6c..e2c1824f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,15 +1,15 @@ -use super::resolvable::Resolvable; +use super::promise::Resolvable; use crate::{ ability::{ arguments, command::{ParseAbility, ToCommand}, }, capsule::Capsule, - delegation::{self, condition::Condition, error::DelegationError, Delegable}, + delegation::{self, condition::Condition, Delegable, ValidationError}, did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - time::{TimeBoundError, Timestamp}, + time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{ @@ -20,35 +20,81 @@ use serde::{ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; -impl Verifiable for Payload { - fn verifier(&self) -> &DID { - &self.issuer - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Payload { - pub issuer: DID, + /// The subject of the [`Invocation`]. + /// + /// This is typically also the `audience`, hence the [`audence`] + /// field is optional. + /// + /// This role *must* have issued the earlier (root) + /// delegation in the chain. This makes the chains + /// self-certifying. + /// + /// The semantics of the delegation are established + /// by the subject. + /// + /// [`Invocation`]: super::Invocation pub subject: DID, + + /// The issuer of the [`Invocation`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Invocation`]. + /// + /// [`Invocation`]: super::Invocation + pub issuer: DID, + + /// The agent being delegated to. + /// + /// Note that if this is the same as the [`subject`], + /// this field may be omitted. pub audience: Option, + /// The [Ability] being invoked. + /// + /// The specific shape and semantics of this ability + /// are established by the [`subject`] and the `A` type. + /// + /// [Ability]: crate::ability pub ability: A, + /// [`Cid`] links to the proofs that authorize this [`Invocation`]. + /// + /// These must be given in order starting from one where the [`issuer`] + /// of this invocation matches the [`audience`] of that [`Delegation`] proof. + /// + /// [`Invocation`]: super::Invocation + /// [`Delegation`]: crate::delegation::Delegation pub proofs: Vec, + + /// An optional [`Cid`] of the [`Receipt`] that requested this be invoked. + /// + /// This is helpful for provenance of calls. + /// + /// [`Receipt`]: crate::receipt::Receipt pub cause: Option, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce pub nonce: Nonce, + /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] + /// was created. pub issued_at: Option, - pub expiration: Option, // FIXME this field may not make sense + + /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] + /// should no longer be executed. + /// + /// One way of thinking about this is as a `timeout`. It also guards against + /// certain types of denial-of-service attacks. + pub expiration: Option, } -// FIXME cleanup traits -// one idea, because they keep comingup together: put hierarchy and builder on the same -// trair (as associated tyeps) to klet us skip the ::bulder::hierarchy indirection. -// -// This probably means putting the delegation T back to the upper level and bieng explicit about -// the T::Builder in the type impl Payload { pub fn map_ability(self, f: F) -> Payload where @@ -68,24 +114,23 @@ impl Payload { } } - // FIXME err type - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { let ts_now = &Timestamp::postel(now); if let Some(ref exp) = self.expiration { if exp < ts_now { - panic!("FIXME") + return Err(Expired); } } Ok(()) } - pub fn check( + pub fn check( self, proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + ) -> Result<(), ValidationError<<::Hierarchy as Prove>::Error, C>> where A: Delegable, A::Builder: Clone + Into>, @@ -345,6 +390,12 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser } } +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index aaa9ad83..592aac94 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,18 +1,22 @@ -//! [UCAN Promise](https://github.com/ucan-wg/promise) +//! [UCAN Promise](https://github.com/ucan-wg/promise)s: selectors, wrappers, and traits. // FIXME put entire module behind feature flag mod any; mod err; mod ok; +mod resolvable; mod resolves; +pub mod store; // FIXME pub mod js; pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; +pub use resolvable::Resolvable; pub use resolves::Resolves; +pub use store::Store; use serde::{Deserialize, Serialize}; diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index e37d1e87..a8ecbd78 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -7,6 +7,14 @@ use serde::{ }; use std::fmt; +/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. +/// +/// Unlike [`Resolves`][super::Resolves]: +/// +/// 1. The branches may be of different types +/// 2. The underlying value is _left wrapped_ in `{"ok": T}` or `{"err": T}` capsules +/// +/// FIXME example #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(untagged)] pub enum PromiseAny { diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index 623808ec..e30ce53e 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -3,6 +3,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +/// A promise that only selects the `{"err": error}` branch of a result. +/// +/// On resolution, the value is unwrapped from the `{"err": error}`, +/// leaving just the `error` (much like [`Result::unwrap_err`]). +/// +/// FIXME exmaple #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum PromiseErr { diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 2662344e..56f50e0a 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -3,6 +3,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +/// A promise that only selects the `{"ok": value}` branch of a result. +/// +/// On resolution, the value is unwrapped from the `{"ok": value}`, +/// leaving just the `value` (much like [`Result::unwrap`]). +/// +/// FIXME exmaple #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum PromiseOk { diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs new file mode 100644 index 00000000..d039585e --- /dev/null +++ b/src/invocation/promise/resolvable.rs @@ -0,0 +1,22 @@ +use crate::{ability::arguments, delegation::Delegable}; +use libipld_core::ipld::Ipld; + +// FIXME rename "Unresolved" +// FIXME better name + +/// A trait for [`Delegable`]s that can be deferred (by promises). +/// +/// FIXME exmaples +pub trait Resolvable: Delegable { + /// The promise type that resolves to `Self`. + /// + /// Note that this may be a more complex type than the promise selector + /// variants. One example is [letting any leaf][PromiseIpld] of an [`Ipld`] graph + /// be a promise. + /// + /// [PromiseIpld]: crate::ipld::Promised + type Promised: Into + Into>; + + /// Attempt to resolve the [`Self::Promised`]. + fn try_resolve(promised: Self::Promised) -> Result; +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 026308ee..d998485e 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -3,6 +3,14 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. +/// +/// Unlike [`PromiseAny`][super::PromiseAny]: +/// +/// 1. Both branches of this promise resolve to the same type +/// 2. The underlying value is unwrapped from the `{"ok": T}` or `{"err": T}` capsules +/// +/// FIXME example #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum Resolves { diff --git a/src/invocation/promise/store.rs b/src/invocation/promise/store.rs new file mode 100644 index 00000000..2cd345c2 --- /dev/null +++ b/src/invocation/promise/store.rs @@ -0,0 +1,7 @@ +//! Storage of resolved and unresolved promises. + +mod memory; +mod traits; + +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs new file mode 100644 index 00000000..ae960dfc --- /dev/null +++ b/src/invocation/promise/store/memory.rs @@ -0,0 +1,46 @@ +use super::Store; +use crate::{did::Did, invocation::promise::Resolvable}; +use libipld_core::cid::Cid; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::Infallible, +}; + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + pub index: BTreeMap>, +} + +impl Store for MemoryStore { + type PromiseIndexError = Infallible; + + fn put( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError> { + self.index + .insert(invocation, BTreeSet::from_iter(waiting_on)); + + Ok(()) + } + + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError> { + Ok(match waiting_on.pop() { + None => BTreeSet::new(), + Some(first) => waiting_on + .iter() + .try_fold(BTreeSet::from_iter([first]), |acc, cid| { + let next = self.index.get(cid).ok_or(())?; + + let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); + if reduced.is_empty() { + return Err(()); + } + + Ok(reduced) + }) + .unwrap_or_default(), + }) + } +} diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs new file mode 100644 index 00000000..200ac656 --- /dev/null +++ b/src/invocation/promise/store/traits.rs @@ -0,0 +1,14 @@ +use crate::{did::Did, invocation::promise::Resolvable}; +use libipld_core::cid::Cid; +use std::collections::BTreeSet; + +pub trait Store { + type PromiseIndexError; + + // NOTE put_waiting + fn put(&mut self, waiting_on: Vec, invocation: Cid) + -> Result<(), Self::PromiseIndexError>; + + // NOTE get waiting + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError>; +} diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs deleted file mode 100644 index 0f4d12dc..00000000 --- a/src/invocation/resolvable.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{ability::arguments, delegation::Delegable}; -use libipld_core::ipld::Ipld; - -pub trait Resolvable: Delegable { - // FIXME rename "Unresolved" - type Promised: Into + Into>; - - // FIXME indeed needed to get teh right err type - fn try_resolve(promised: Self::Promised) -> Result; - - // FIXME better name - // NOTE this takes anything taht doesn't resolve and returns None on those fields - // FIXME no, jsut use Into and NOTE THIS IN THE DOCS - // fn resolve_to_builder(&self) -> Self::Builder; -} - -// impl Delegable for Ipld { -// type Builder = Option; -// } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index e168836e..a270db1d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,13 +1,14 @@ +//! Storage for [`Invocation`]s. + use super::Invocation; -use crate::{did::Did, invocation::Resolvable}; +use crate::did::Did; use libipld_core::cid::Cid; -use std::collections::{BTreeMap, BTreeSet}; -use thiserror::Error; +use std::{collections::BTreeMap, convert::Infallible}; pub trait Store { type Error; - fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: Cid) -> Result>, Self::Error>; fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; @@ -21,15 +22,11 @@ pub struct MemoryStore { store: BTreeMap>, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - impl Store for MemoryStore { - type Error = NotFound; + type Error = Infallible; - fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error> { - self.store.get(&cid).ok_or(NotFound) + fn get(&self, cid: Cid) -> Result>, Self::Error> { + Ok(self.store.get(&cid)) } fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { @@ -37,62 +34,3 @@ impl Store for MemoryStore { Ok(()) } } - -//////// - -pub trait PromiseIndex { - type PromiseIndexError; - - fn put_waiting( - &mut self, - waiting_on: Vec, - invocation: Cid, - ) -> Result<(), Self::PromiseIndexError>; - - fn get_waiting( - &self, - waiting_on: &mut Vec, - ) -> Result, Self::PromiseIndexError>; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryPromiseIndex { - pub index: BTreeMap>, -} - -impl PromiseIndex for MemoryPromiseIndex { - type PromiseIndexError = NotFound; - - fn put_waiting( - &mut self, - waiting_on: Vec, - invocation: Cid, - ) -> Result<(), Self::PromiseIndexError> { - self.index - .insert(invocation, BTreeSet::from_iter(waiting_on)); - - Ok(()) - } - - fn get_waiting( - &self, - waiting_on: &mut Vec, - ) -> Result, Self::PromiseIndexError> { - Ok(match waiting_on.pop() { - None => BTreeSet::new(), - Some(first) => waiting_on - .iter() - .try_fold(BTreeSet::from_iter([first]), |acc, cid| { - let next = self.index.get(cid).ok_or(())?; - - let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); - if reduced.is_empty() { - return Err(()); - } - - Ok(reduced) - }) - .unwrap_or_default(), - }) - } -} diff --git a/src/time.rs b/src/time.rs index b4e167bf..3c37cb1d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -5,5 +5,5 @@ mod error; mod timestamp; -pub use error::{OutOfRangeError, TimeBoundError}; +pub use error::*; pub use timestamp::Timestamp; diff --git a/src/time/error.rs b/src/time/error.rs index 6b640b87..e9c3149e 100644 --- a/src/time/error.rs +++ b/src/time/error.rs @@ -14,11 +14,16 @@ pub struct OutOfRangeError { /// An error expressing when a time is not within the bounds of a UCAN. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] pub enum TimeBoundError { - /// The UCAN delegation has expired + /// The UCAN has expired. #[error("Expired")] Expired, - /// Not yet valid + /// The UCAN is not yet valid, but will be in the future. #[error("Not yet valid")] NotYetValid, } + +/// The UCAN has expired. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +#[error("Expired")] +pub struct Expired; From 6b98b491aea7b8bed06ee7cd108a7d4ec9d9b2a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 16:42:24 -0800 Subject: [PATCH 154/234] Move signature under crypto --- src/crypto.rs | 4 ++++ src/{ => crypto}/nonce.rs | 5 ++--- src/{ => crypto}/signature.rs | 0 src/{ => crypto}/signature/envelope.rs | 0 src/{ => crypto}/signature/witness.rs | 0 src/delegation.rs | 3 +-- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 2 +- src/invocation.rs | 6 +++--- src/invocation/agent.rs | 3 +-- src/invocation/payload.rs | 2 +- src/lib.rs | 2 -- src/receipt.rs | 2 +- src/receipt/payload.rs | 2 +- src/receipt/responds.rs | 2 +- src/task.rs | 2 +- 16 files changed, 18 insertions(+), 19 deletions(-) rename src/{ => crypto}/nonce.rs (98%) rename src/{ => crypto}/signature.rs (100%) rename src/{ => crypto}/signature/envelope.rs (100%) rename src/{ => crypto}/signature/witness.rs (100%) diff --git a/src/crypto.rs b/src/crypto.rs index 086753be..cb236014 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,8 +1,12 @@ //! Cryptographic signature utilities mod domain_separator; +mod nonce; + +pub mod signature; pub use domain_separator::DomainSeparator; +pub use nonce::*; #[cfg(feature = "bls")] pub mod bls12381; diff --git a/src/nonce.rs b/src/crypto/nonce.rs similarity index 98% rename from src/nonce.rs rename to src/crypto/nonce.rs index a6a81a85..63ef9b08 100644 --- a/src/nonce.rs +++ b/src/crypto/nonce.rs @@ -70,7 +70,6 @@ impl From> for Nonce { } impl Nonce { - // NOTE salt = domain-separator /// Generate a 96-bit, 12-byte nonce. /// This is the minimum nonce size typically recommended. /// @@ -81,7 +80,7 @@ impl Nonce { /// # Example /// /// ```rust - /// # use ucan::nonce::Nonce; + /// # use ucan::crypto::Nonce; /// # use ucan::did::Did; /// # /// let mut salt = "did:example:123".as_bytes().to_vec(); @@ -118,7 +117,7 @@ impl Nonce { /// # Example /// /// ```rust - /// # use ucan::nonce::Nonce; + /// # use ucan::crypto::Nonce; /// # use ucan::did::Did; /// # /// let mut salt = "did:example:123".as_bytes().to_vec(); diff --git a/src/signature.rs b/src/crypto/signature.rs similarity index 100% rename from src/signature.rs rename to src/crypto/signature.rs diff --git a/src/signature/envelope.rs b/src/crypto/signature/envelope.rs similarity index 100% rename from src/signature/envelope.rs rename to src/crypto/signature/envelope.rs diff --git a/src/signature/witness.rs b/src/crypto/signature/witness.rs similarity index 100% rename from src/signature/witness.rs rename to src/crypto/signature/witness.rs diff --git a/src/delegation.rs b/src/delegation.rs index d0b951ad..690dbf49 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -25,10 +25,9 @@ pub use payload::{Payload, ValidationError}; use crate::{ ability, + crypto::{signature, Nonce}, did::{self, Did}, - nonce::Nonce, proof::{parents::CheckParents, same::CheckSame}, - signature, time::{TimeBoundError, Timestamp}, }; use condition::Condition; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index a9a152ce..258cc1b5 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,5 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::Timestamp}; +use crate::{crypto::Nonce, did::Did, proof::checkable::Checkable, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4143da78..3d9b6c70 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,8 +5,8 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, + crypto::Nonce, did::{Did, Verifiable}, - nonce::Nonce, proof::{ checkable::Checkable, parents::CheckParents, diff --git a/src/invocation.rs b/src/invocation.rs index 6aa7846b..846e2555 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,9 +22,9 @@ pub use agent::Agent; pub use payload::{Payload, Promised}; use crate::{ - ability, did, - did::Did, - signature, + ability, + crypto::signature, + did::{self, Did}, time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0250296f..8b8aba07 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,12 +1,11 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, + crypto::{signature::Witness, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, - nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::Witness, time::Timestamp, }; use libipld_cbor::DagCborCodec; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index e2c1824f..aae8991a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -5,9 +5,9 @@ use crate::{ command::{ParseAbility, ToCommand}, }, capsule::Capsule, + crypto::Nonce, delegation::{self, condition::Condition, Delegable, ValidationError}, did::{Did, Verifiable}, - nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, time::{Expired, Timestamp}, }; diff --git a/src/lib.rs b/src/lib.rs index 45d1c929..719f2fdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,11 +22,9 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod nonce; pub mod proof; pub mod reader; pub mod receipt; -pub mod signature; pub mod task; pub mod time; pub mod url; diff --git a/src/receipt.rs b/src/receipt.rs index 8b1d3bf5..e7b70ebb 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,7 +14,7 @@ pub use payload::Payload; pub use responds::Responds; pub use store::Store; -use crate::{ability, did, signature}; +use crate::{ability, crypto::signature, did}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index e8445318..725e2b8d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -6,8 +6,8 @@ use super::responds::Responds; use crate::{ ability::arguments, capsule::Capsule, + crypto::Nonce, did::{Did, Verifiable}, - nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 6f7ca552..24db9f43 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,4 @@ -use crate::{nonce::Nonce, task, task::Task}; +use crate::{crypto::Nonce, task, task::Task}; use std::fmt; /// Describe the relationship between an ability and the [`Receipt`]s. diff --git a/src/task.rs b/src/task.rs index 50d51596..e6b4f1d2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -4,7 +4,7 @@ mod id; pub use id::Id; -use crate::{ability::arguments, did, nonce::Nonce}; +use crate::{ability::arguments, crypto::Nonce, did}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, From c41e0b87a50fb9b0ad6c027327b7c609c6a928ec Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 18:15:14 -0800 Subject: [PATCH 155/234] Porting IPLD proptest helpers from inlien-ipld --- Cargo.toml | 8 ++- src/ability/arguments/named.rs | 15 ++++++ src/crypto/nonce.rs | 19 +++++++ src/ipld/cid.rs | 30 ++++++++++- src/ipld/newtype.rs | 40 ++++++++++++++ src/lib.rs | 3 ++ src/task.rs | 29 +++++++++-- src/test_utils.rs | 95 ++++++++++++++++++++++++++++++++++ src/time/timestamp.rs | 13 +++++ src/url.rs | 20 ++++++- 10 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 src/test_utils.rs diff --git a/Cargo.toml b/Cargo.toml index 936493f5..3848c016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" # Interplanetary Stack +libipld = { version = "0.16", optional = true } libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multihash = { version = "0.18" } @@ -106,10 +107,13 @@ default = [ "eddsa", "bls", - "ability-preset" + "ability-preset", + + # FIXME temp while developing + "test_utils", ] -test_utils = ["proptest"] +test_utils = ["dep:proptest", "dep:libipld"] eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 2e467503..7c726800 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -10,6 +10,9 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Reflect}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// Named arguments /// /// Being such a common pattern, but with so few trait implementations, @@ -328,3 +331,15 @@ pub enum NamedError { #[error("arguments::Named field {0}: value doesn't match")] FieldValueMismatch(String), } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Named { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop::collection::btree_map(".*", T::arbitrary_with(t_args), 0..256) + .prop_map(Named) + .boxed() + } +} diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index 63ef9b08..fc83d952 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -16,6 +16,9 @@ use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// Known [`Nonce`] types #[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { @@ -194,6 +197,22 @@ impl TryFrom for Nonce { } } +#[cfg(feature = "test_utils")] +impl Arbitrary for Nonce { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + any::<[u8; 12]>().prop_map(Nonce::Nonce12), + any::<[u8; 16]>().prop_map(Nonce::Nonce16), + any::>().prop_map(Nonce::Custom) + ] + .boxed() + } +} + +// FIXME move module? #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index a5cab51d..5715b5c8 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,7 +1,7 @@ //! Utilities for [`Cid`]s use crate::ipld; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld, multihash::MultihashGeneric}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -11,6 +11,15 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_derive::TryFromJsValue; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::test_utils::SomeCodec; + +#[cfg(feature = "test_utils")] +use crate::test_utils::SomeMultihash; + /// A newtype wrapper around a [`Cid`] /// /// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. @@ -111,3 +120,22 @@ impl TryFrom<&Ipld> for Newtype { #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] #[error("Not a CID: {0:?}")] pub struct NotACid(pub ipld::Newtype); + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Very much faking it + any::<([u8; 32], SomeMultihash, SomeCodec)>() + .prop_map(|(hash_bytes, hasher, codec)| { + let multihash = MultihashGeneric::wrap(hasher.0.into(), &hash_bytes.as_slice()) + .expect("Sha2_256 should always successfully encode a hash"); + + let cid = Cid::new_v1(codec.0.into(), multihash); + Newtype { cid } + }) + .boxed() + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 72c3110c..fea821f4 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -8,6 +8,12 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Uint8Array}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use super::cid; + // FIXME push into the submodules /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// @@ -217,3 +223,37 @@ impl TryFrom for Newtype { Err(()) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let leaf = prop_oneof![ + Just(Ipld::Null), + any::().prop_map(Ipld::Bool), + any::>().prop_map(Ipld::Bytes), + any::().prop_map(move |i| { + Ipld::Integer((i % (2 ^ 53)).into()) // NOTE Because DAG-JSON + }), + any::().prop_map(Ipld::Float), + ".*".prop_map(Ipld::String), + any::().prop_map(|newtype_cid| { Ipld::Link(newtype_cid.cid) }) + ]; + + let coll = leaf.clone().prop_recursive(16, 1024, 128, |inner| { + prop_oneof![ + prop::collection::vec(inner.clone(), 0..128).prop_map(Ipld::List), + prop::collection::btree_map(".*", inner, 0..128).prop_map(Ipld::Map), + ] + }); + + prop_oneof![ + 1 => leaf, + 9 => coll + ] + .prop_map(Newtype) + .boxed() + } +} diff --git a/src/lib.rs b/src/lib.rs index 719f2fdb..bb7edaf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,9 @@ pub mod task; pub mod time; pub mod url; +#[cfg(feature = "test_utils")] +pub mod test_utils; + pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; diff --git a/src/task.rs b/src/task.rs index e6b4f1d2..3c31e711 100644 --- a/src/task.rs +++ b/src/task.rs @@ -11,13 +11,14 @@ use libipld_core::{ codec::Encode, error::SerdeError, ipld::Ipld, - multihash::MultihashGeneric, + multihash::{Code, MultihashGeneric}, serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -const SHA2_256: u64 = 0x12; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; /// The fields required to uniquely identify a [`Task`], potentially across multiple executors. /// @@ -65,7 +66,7 @@ impl From for Cid { CidGeneric::new_v1( DagCborCodec.into(), - MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), ) } @@ -78,3 +79,25 @@ impl From for Id { } } } + +// #[cfg(feature = "test_utils")] +// impl Arbitrary for Task { +// type Parameters = (); +// type Strategy = BoxedStrategy; +// +// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { +// ( +// any::(), +// any::(), +// any::(), +// any::>(), +// ) +// .prop_map(|(sub, nonce, cmd, args)| Task { +// sub, +// nonce, +// cmd, +// args, +// }) +// .boxed() +// } +// } diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 00000000..e3817b91 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,95 @@ +use libipld::{ + cid::multihash::{Code, MultihashDigest, MultihashGeneric}, + codec_impl::IpldCodec, +}; +use proptest::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct SomeCodec(pub IpldCodec); + +impl Arbitrary for SomeCodec { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + Just(IpldCodec::Raw), + Just(IpldCodec::DagCbor), + Just(IpldCodec::DagJson), + Just(IpldCodec::DagPb), + ] + .prop_map(SomeCodec) + .boxed() + } +} + +#[derive(Eq, Copy, Clone, Debug, PartialEq)] +pub struct SomeMultihash(pub Code); + +impl Default for SomeMultihash { + fn default() -> Self { + SomeMultihash(Code::Sha2_256) + } +} + +impl SomeMultihash { + pub fn new(multihash: Code) -> Self { + SomeMultihash(multihash) + } +} + +impl From for SomeMultihash { + fn from(multihash: Code) -> Self { + SomeMultihash(multihash) + } +} + +impl From for Code { + fn from(wrapper: SomeMultihash) -> Self { + wrapper.0 + } +} + +impl From for u64 { + fn from(wrapper: SomeMultihash) -> Self { + wrapper.0.into() + } +} + +impl TryFrom for SomeMultihash { + type Error = >::Error; + + fn try_from(code: u64) -> Result { + let inner = code.try_into()?; + Ok(SomeMultihash(inner)) + } +} + +impl MultihashDigest<64> for SomeMultihash { + fn digest(&self, input: &[u8]) -> MultihashGeneric<64> { + self.0.digest(input) + } + + fn wrap(&self, digest: &[u8]) -> Result, Self::Error> { + self.0.wrap(digest) + } +} + +impl Arbitrary for SomeMultihash { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Only the 256-bit variants for now + prop_oneof![ + Just(Code::Sha2_256), + Just(Code::Sha3_256), + Just(Code::Keccak256), + Just(Code::Blake2s256), + Just(Code::Blake2b256), + Just(Code::Blake3_256), + ] + .prop_map(SomeMultihash) + .boxed() + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 2d608a98..d8659c29 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -5,6 +5,9 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use web_time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A [`Timestamp`][super::Timestamp] with safe JavaScript interop. /// /// Per the UCAN spec, timestamps MUST respect [IEEE-754] @@ -126,3 +129,13 @@ impl<'de> Deserialize<'de> for Timestamp { Ok(Timestamp::postel(UNIX_EPOCH + Duration::from_secs(seconds))) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Timestamp { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::().prop_map(Timestamp::postel).boxed() + } +} diff --git a/src/url.rs b/src/url.rs index a55255ed..22c6915b 100644 --- a/src/url.rs +++ b/src/url.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -/// A wrapper around [`Url`] that has additional trait implementations +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +/// A wrapper around [`Url`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. /// @@ -63,3 +66,18 @@ pub enum FromIpldError { #[error(transparent)] UrlParseError(#[from] url::ParseError), } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let url_regex: &str = &"\\w+:(\\/?\\/?)[^\\s]+"; + url_regex + .prop_map(|s| { + Newtype(Url::parse(&s).expect("the regex generator to create valid URLs")) + }) + .boxed() + } +} From 58f530016167eae705095cee5aad3b84e5fcea28 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 23:46:07 -0800 Subject: [PATCH 156/234] More Arbitrary instances --- Cargo.toml | 5 +- src/ability/arguments/named.rs | 17 ++- src/did/key/verifier.rs | 194 ++++++++++++++++++++++++++++++++- src/did/newtype.rs | 44 ++++++++ src/did/traits.rs | 7 +- src/receipt/payload.rs | 7 +- src/task.rs | 42 +++---- src/task/id.rs | 20 ++++ 8 files changed, 301 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3848c016..54384153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,13 @@ ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } +p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } +rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } signature = { version = "2.1.0", features = ["alloc"] } # Encoding base64 = "0.21" +bs58 = "0.5" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 7c726800..5ccf97c1 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -333,13 +333,20 @@ pub enum NamedError { } #[cfg(feature = "test_utils")] -impl Arbitrary for Named { - type Parameters = T::Parameters; +impl Arbitrary for Named { + type Parameters = (); type Strategy = BoxedStrategy; - fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { - prop::collection::btree_map(".*", T::arbitrary_with(t_args), 0..256) - .prop_map(Named) + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..256) + .prop_map(|newtype_map| { + newtype_map + .into_iter() + .fold(Named::new(), |mut named, (k, v)| { + named.insert(k, v.0); + named + }) + }) .boxed() } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 9e17cad8..2d5d3fcb 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -1,6 +1,10 @@ use super::Signature; use enum_as_inner::EnumAsInner; -// FIXME use serde::{Deserialize, Serialize}; +use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; +use std::{fmt::Display, str::FromStr}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -102,3 +106,191 @@ impl signature::Verifier for Verifier { } } } + +impl Display for Verifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Verifier::EdDSA(ed25519_pk) => write!( + f, + "did:key:z6Mk{}", + bs58::encode(ed25519_pk.to_bytes()).into_string() + ), + Verifier::Es256k(secp256k1_pk) => write!( + f, + "did:key:zQ3s{}", + bs58::encode(secp256k1_pk.to_sec1_bytes()).into_string() + ), + Verifier::P256(p256_key) => { + write!( + f, + "did:key:zDn{}", + bs58::encode(p256_key.to_sec1_bytes()).into_string() + ) + } + Verifier::P384(p384_key) => write!( + f, + "did:key:z82{}", + bs58::encode(p384_key.to_sec1_bytes()).into_string() + ), + Verifier::P521(p521_key) => write!( + f, + "did:key:z2J9{}", + bs58::encode(p521_key.0.to_encoded_point(true).as_bytes()).into_string() + ), + Verifier::Rs256(rsa2048_key) => { + write!( + f, + "did:key:z4MX{}", + bs58::encode( + rsa2048_key + .0 + .to_pkcs1_der() + .expect("RSA key to encode") // FIXME? + .as_bytes() + ) + .into_string() + ) + } + Verifier::Rs512(rsa4096_key) => write!( + f, + "did:key:zgg{}", + bs58::encode( + rsa4096_key + .0 + .to_pkcs1_der() + .expect("RSA key to encode") // FIXME? + .as_bytes() + ) + .into_string() + ), + Verifier::BlsMinPk(bls_minpk_pk) => write!( + f, + "did:key:zUC7{}", + bs58::encode(bls_minpk_pk.serialize()).into_string() + ), + Verifier::BlsMinSig(bls_minsig_pk) => write!( + f, + "did:key:zUC7{}", + bs58::encode(bls_minsig_pk.serialize()).into_string() + ), + } + } +} + +impl FromStr for Verifier { + type Err = String; // FIXME + + // FIXME needs tests + fn from_str(s: &str) -> Result { + if s.len() < 32 { + // Smallest key size + return Err("invalid did:key".to_string()); + } + + if let ("did:key:z", more) = s.split_at(9) { + let bytes = more.as_bytes(); + match bytes.split_at(2) { + ([0xed, _], _) => { + let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::EdDSA(vk)); + } + ([0xe7, _], _) => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Es256k(vk)); + } + ([0x12, 0x00], key_bytes) => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::P256(vk)); + } + ([0x12, 0x01], key_bytes) => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::P384(vk)); + } + ([0x12, 0x05], key_bytes) => match key_bytes.len() { + 2048 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + } + 4096 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + } + _ => todo!(), + }, + ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 48 => { + let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + .map_err(|_| "Failed BLS MinPk deserialization")?; + + return Ok(Verifier::BlsMinPk(pk)); + } + 96 => { + let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + .map_err(|_| "Failed BLS MinSig deserialization")?; + + return Ok(Verifier::BlsMinSig(pk)); + } + _ => return Err("invalid did:key".to_string()), + }, + _ => { + return Err("invalid did:key".to_string()); + } + } + } else { + return Err("invalid did:key".to_string()); + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Verifier { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // NOTE these are just the test vectors from `did:key` v0.7 + prop_oneof![ + // did:key + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + + // secp256k1 + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ].prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")).boxed() + } +} diff --git a/src/did/newtype.rs b/src/did/newtype.rs index 2d29ea1b..1057247b 100644 --- a/src/did/newtype.rs +++ b/src/did/newtype.rs @@ -5,6 +5,9 @@ use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// @@ -110,3 +113,44 @@ impl<'de> Deserialize<'de> for FromIpldError { Ok(FromIpldError::NotAnIpldString(ipld)) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // NOTE these are just the test vectors from `did:key` v0.7 + prop_oneof![ + // did:key + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + + // secp256k1 + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ].prop_map(|s: &str| Newtype(did_url::DID::parse(s).expect("did:key spec test vectors to work"))).boxed() + } +} diff --git a/src/did/traits.rs b/src/did/traits.rs index 12931628..bd903ad4 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,9 +1,8 @@ -use super::Newtype; +// use super::Newtype; +use did_url::DID; use std::fmt; -pub trait Did: - PartialEq + TryFrom + Into + signature::Verifier -{ +pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 725e2b8d..142ce917 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -87,8 +87,11 @@ where where S: Serializer, { - let mut state = serializer.serialize_struct("receipt::Payload", 8)?; - state.serialize_field("iss", &self.issuer.clone().into())?; + let field_count = 7 + self.issued_at.is_some() as usize; + + let mut state = serializer.serialize_struct("receipt::Payload", field_count)?; + + state.serialize_field("iss", &self.issuer.clone().into().as_str())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; diff --git a/src/task.rs b/src/task.rs index 3c31e711..032378e7 100644 --- a/src/task.rs +++ b/src/task.rs @@ -80,24 +80,24 @@ impl From for Id { } } -// #[cfg(feature = "test_utils")] -// impl Arbitrary for Task { -// type Parameters = (); -// type Strategy = BoxedStrategy; -// -// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { -// ( -// any::(), -// any::(), -// any::(), -// any::>(), -// ) -// .prop_map(|(sub, nonce, cmd, args)| Task { -// sub, -// nonce, -// cmd, -// args, -// }) -// .boxed() -// } -// } +#[cfg(feature = "test_utils")] +impl Arbitrary for Task { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::>(), + any::(), + any::>(), + ) + .prop_map(|(sub, nonce, cmd, args)| Task { + sub, + nonce, + cmd, + args, + }) + .boxed() + } +} diff --git a/src/task/id.rs b/src/task/id.rs index 6e1ec854..0414e722 100644 --- a/src/task/id.rs +++ b/src/task/id.rs @@ -2,6 +2,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + /// The unique identifier for a [`Task`][super::Task]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] @@ -25,3 +31,17 @@ impl From for Ipld { id.cid.into() } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Id { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::() + .prop_map(|cid_newtype| Id { + cid: cid_newtype.cid, + }) + .boxed() + } +} From bf3e2ff25fb3717df42694361b7e6ca85b057749 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 18 Feb 2024 00:14:27 -0800 Subject: [PATCH 157/234] Add more arbitrary (receipt paylaod) --- src/delegation/payload.rs | 3 +-- src/invocation/payload.rs | 14 ++++------- src/receipt/payload.rs | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 3d9b6c70..7a7df4e7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -167,8 +167,7 @@ where where S: Serializer, { - let count_nbf = if self.not_before.is_some() { 1 } else { 0 }; - + let count_nbf = self.not_before.is_some() as usize; let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; state.serialize_field("iss", &self.issuer.clone().into().to_string())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index aae8991a..7044e40d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -208,16 +208,10 @@ where where S: Serializer, { - let mut field_count = 9; - if self.audience.is_some() { - field_count += 1 - }; - if self.issued_at.is_some() { - field_count += 1 - }; - if self.expiration.is_some() { - field_count += 1 - }; + let field_count = 9 + + self.audience.is_some() as usize + + self.issued_at.is_some() as usize + + self.expiration.is_some() as usize; let mut state = serializer.serialize_struct("invocation::Payload", field_count)?; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 142ce917..41ccea13 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -18,6 +18,15 @@ use serde::{ }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer @@ -233,3 +242,44 @@ where ipld_serde::from_ipld(ipld) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Payload +where + T::Success: Arbitrary + 'static, +{ + type Parameters = (::Parameters, DID::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_params, did_params): Self::Parameters) -> Self::Strategy { + ( + DID::arbitrary_with(did_params), + cid::Newtype::arbitrary(), + prop_oneof![ + T::Success::arbitrary_with(t_params).prop_map(Result::Ok), + arguments::Named::arbitrary().prop_map(Result::Err), + ], + prop::collection::vec(cid::Newtype::arbitrary(), 0..25), + prop::collection::vec(cid::Newtype::arbitrary(), 0..25), + prop::collection::hash_map(".*", ipld::Newtype::arbitrary(), 0..50), + Nonce::arbitrary(), + prop::option::of(Timestamp::arbitrary()), + ) + .prop_map( + |(issuer, ran, out, next, proofs, newtype_metadata, nonce, issued_at)| Payload { + issuer, + ran: ran.cid, + out, + next: next.into_iter().map(|nt| nt.cid).collect(), + proofs: proofs.into_iter().map(|nt| nt.cid).collect(), + metadata: newtype_metadata + .into_iter() + .map(|(k, v)| (k, v.0)) + .collect(), + nonce, + issued_at, + }, + ) + .boxed() + } +} From 501613200af3d9b39e9317719ef8438d11e90fd2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 18 Feb 2024 23:57:08 -0800 Subject: [PATCH 158/234] Varsig implementation --- src/ability/msg.rs | 2 +- src/crypto.rs | 1 + src/crypto/signature.rs | 2 - src/crypto/signature/envelope.rs | 108 +++++++++++++++-------- src/crypto/signature/witness.rs | 28 ------ src/crypto/varsig.rs | 4 + src/crypto/varsig/encoding.rs | 3 + src/crypto/varsig/encoding/preset.rs | 116 +++++++++++++++++++++++++ src/crypto/varsig/header.rs | 17 ++++ src/crypto/varsig/header/eddsa.rs | 43 +++++++++ src/crypto/varsig/header/es256.rs | 48 ++++++++++ src/crypto/varsig/header/es256k.rs | 48 ++++++++++ src/crypto/varsig/header/es512.rs | 48 ++++++++++ src/crypto/varsig/header/preset.rs | 76 ++++++++++++++++ src/crypto/varsig/header/rs256.rs | 49 +++++++++++ src/crypto/varsig/header/rs512.rs | 49 +++++++++++ src/crypto/varsig/header/traits.rs | 44 ++++++++++ src/delegation.rs | 73 ++++++++++++---- src/delegation/agent.rs | 44 +++++++--- src/delegation/payload.rs | 75 ++++++++++++++-- src/delegation/store/memory.rs | 44 +++++++--- src/delegation/store/traits.rs | 39 ++++++--- src/did/key/signature.rs | 53 ++++++++++- src/did/key/verifier.rs | 8 +- src/invocation.rs | 93 +++++++++++++------- src/invocation/agent.rs | 115 ++++++++++++++++-------- src/invocation/payload.rs | 66 ++++++++++++++ src/invocation/promise/any.rs | 25 ++++++ src/invocation/promise/err.rs | 21 +++++ src/invocation/promise/ok.rs | 21 +++++ src/invocation/promise/resolves.rs | 21 +++++ src/invocation/promise/store/memory.rs | 8 +- src/invocation/promise/store/traits.rs | 8 +- src/invocation/store.rs | 42 ++++++--- src/proof/parentful.rs | 4 + src/receipt.rs | 29 +++++-- src/receipt/payload.rs | 6 +- src/receipt/store/memory.rs | 20 +++-- src/receipt/store/traits.rs | 9 +- 39 files changed, 1279 insertions(+), 231 deletions(-) delete mode 100644 src/crypto/signature/witness.rs create mode 100644 src/crypto/varsig.rs create mode 100644 src/crypto/varsig/encoding.rs create mode 100644 src/crypto/varsig/encoding/preset.rs create mode 100644 src/crypto/varsig/header.rs create mode 100644 src/crypto/varsig/header/eddsa.rs create mode 100644 src/crypto/varsig/header/es256.rs create mode 100644 src/crypto/varsig/header/es256k.rs create mode 100644 src/crypto/varsig/header/es512.rs create mode 100644 src/crypto/varsig/header/preset.rs create mode 100644 src/crypto/varsig/header/rs256.rs create mode 100644 src/crypto/varsig/header/rs512.rs create mode 100644 src/crypto/varsig/header/traits.rs diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 7f7e33ab..1f56e40b 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -32,7 +32,7 @@ pub enum Builder { #[derive(Debug, Clone, PartialEq)] pub enum Promised { Send(send::Promised), - Receive(receive::Promised), // FIXME + Receive(receive::Promised), } impl Delegable for Ready { diff --git a/src/crypto.rs b/src/crypto.rs index cb236014..4bc82072 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,6 +4,7 @@ mod domain_separator; mod nonce; pub mod signature; +pub mod varsig; pub use domain_separator::DomainSeparator; pub use nonce::*; diff --git a/src/crypto/signature.rs b/src/crypto/signature.rs index ca66a2a4..41e90739 100644 --- a/src/crypto/signature.rs +++ b/src/crypto/signature.rs @@ -1,7 +1,5 @@ //! Signatures and cryptographic envelopes. mod envelope; -mod witness; pub use envelope::*; -pub use witness::Witness; diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 75d41ec5..2b1237f5 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,38 +1,65 @@ -use super::Witness; use crate::{ capsule::Capsule, + crypto::varsig, did::{Did, Verifiable}, }; use libipld_core::{ codec::{Codec, Encode}, error::Result, ipld::Ipld, - multihash::Code, }; +use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; -// FIXME #[cfg(feature = "dag-cbor")] -use libipld_cbor::DagCborCodec; -use signature::Signer; - /// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] -pub struct Envelope + Capsule, DID: Did> { +pub struct Envelope< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + /// The [Varsig][crate::crypto::varsig] header. + pub varsig_header: V, + /// The signture of the `payload`. - pub signature: Witness, + pub signature: DID::Signature, /// The payload that's being signed over. pub payload: T, + + _phantom: std::marker::PhantomData, } -impl + Capsule, DID: Did> Verifiable for Envelope { +impl< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Verifiable for Envelope +{ fn verifier(&self) -> &DID { &self.payload.verifier() } } -impl + Into, DID: Did> Envelope { +impl< + T: Capsule + Verifiable + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Envelope +{ + pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { + Envelope { + varsig_header, + signature, + payload, + _phantom: std::marker::PhantomData, + } + } + /// Attempt to sign some payload with a given signer. /// /// # Arguments @@ -47,11 +74,16 @@ impl + Into, DID: Did> Envelope { /// # Example /// /// FIXME - pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> + pub fn try_sign( + signer: &DID::Signer, + varsig_header: V, + payload: T, + ) -> Result, SignError> where T: Clone, + Ipld: Encode, { - Self::try_sign_generic::(signer, DagCborCodec, payload) + Self::try_sign_generic(signer, varsig_header, payload) } /// Attempt to sign some payload with a given signer and specific codec. @@ -69,28 +101,30 @@ impl + Into, DID: Did> Envelope { /// # Example /// /// FIXME - pub fn try_sign_generic>( + pub fn try_sign_generic( signer: &DID::Signer, - codec: C, + varsig_header: V, payload: T, - ) -> Result, SignError> + ) -> Result, SignError> where T: Clone, - Ipld: Encode, + Ipld: Encode, { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) + ipld.encode(*varsig_header.codec(), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let sig = signer + let signature = signer .try_sign(&buffer) .map_err(SignError::SignatureError)?; Ok(Envelope { - signature: Witness::Signature(sig), + varsig_header, + signature, payload, + _phantom: std::marker::PhantomData, }) } @@ -107,31 +141,37 @@ impl + Into, DID: Did> Envelope { /// # Exmaples /// /// FIXME - pub fn validate_signature(&self) -> Result<(), ValidateError> + pub fn validate_signature(&self, varsig_header: &V) -> Result<(), ValidateError> where T: Clone, + Ipld: Encode, { - // FIXME need varsig - let codec = DagCborCodec; - let mut encoded = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(codec, &mut encoded) + ipld.encode(*varsig_header.codec(), &mut encoded) .map_err(ValidateError::PayloadEncodingError)?; - match &self.signature { - Witness::Signature(sig) => self - .verifier() - .verify(&encoded, &sig) - .map_err(ValidateError::VerifyError), - } + self.verifier() + .verify(&encoded, &self.signature) + .map_err(ValidateError::VerifyError) } } -impl + Capsule + Into, DID: Did> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), ipld)]).into() +impl< + T: Verifiable + Capsule + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > From> for Ipld +{ + fn from(envelope: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); + let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); + + Ipld::Map(BTreeMap::from_iter([ + ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), + ("pld".into(), Ipld::List(vec![varsig_header, ipld])), + ])) } } diff --git a/src/crypto/signature/witness.rs b/src/crypto/signature/witness.rs deleted file mode 100644 index 705eae7b..00000000 --- a/src/crypto/signature/witness.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Signatures and related cryptographic witnesses. - -use enum_as_inner::EnumAsInner; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use signature::SignatureEncoding; - -/// Asymmetric cryptographic witnesses. -#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Witness { - /// A single cryptographic signature. - Signature(S), - // FIXME TODO - // Batch { - // signature: S, - // root: Vec, - // merkle_proof: Vec, - // }, -} - -impl From> for Ipld { - fn from(w: Witness) -> Self { - match w { - Witness::Signature(sig) => sig.to_vec().into(), - } - } -} diff --git a/src/crypto/varsig.rs b/src/crypto/varsig.rs new file mode 100644 index 00000000..9308f13e --- /dev/null +++ b/src/crypto/varsig.rs @@ -0,0 +1,4 @@ +pub mod encoding; +pub mod header; + +pub use header::Header; diff --git a/src/crypto/varsig/encoding.rs b/src/crypto/varsig/encoding.rs new file mode 100644 index 00000000..237cb0ef --- /dev/null +++ b/src/crypto/varsig/encoding.rs @@ -0,0 +1,3 @@ +mod preset; + +pub use preset::Preset; diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs new file mode 100644 index 00000000..cf7cf8ea --- /dev/null +++ b/src/crypto/varsig/encoding/preset.rs @@ -0,0 +1,116 @@ +use libipld_core::codec::Codec; + +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Preset { + Identity = 0x5f, + DagPb = 0x70, + DagCbor = 0x71, + DagJson = 0x0129, + Jwt = 0x6a77, // FIXME break out Jwt & EIP-191? + Eip191 = 0xe191, +} + +impl TryFrom for Preset { + type Error = libipld_core::error::UnsupportedCodec; + + fn try_from(value: u64) -> Result { + match value { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => Ok(Preset::MerkleBatchSig), + _ => Err(libipld_core::error::UnsupportedCodec(value)), + } + } +} + +impl From for u64 { + fn from(encoding: Preset) -> u64 { + encoding as u64 + } +} + +impl Codec for Preset {} + +// FIXME pub struct MerkleSig + +impl<'a> TryFrom<&'a [u8]> for Preset { + type Error = (); + + fn try_from(bytes: &'a [u8]) -> Result { + if let (encoding_info, &[]) = unsigned_varint::decode::u32(&bytes).map_err(|_| ())? { + return match encoding_info { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => { + // let merkle_proof = Vec::new(); + // Ok(Preset::MerkleBatchSig(merkle_proof)) + // } + _ => Err(()), + }; + }; + + Err(()) + } +} + +impl AsRef<[u8]> for Preset { + fn as_ref(&self) -> &[u8] { + match self { + Preset::Identity => &[0x5f], + Preset::DagPb => &[0x70], + Preset::DagCbor => &[0x71], + Preset::DagJson => &[0x01, 0x29], + Preset::Jwt => &[0x6a, 0x77], + Preset::Eip191 => &[0xe1, 0x91], + // Preset::Eip191(inner) => { + // let mut buffer = vec![0xe191]; + // buffer.extend(inner.as_ref()); + // buffer.as_ref() + // } // Preset::MerkleBatchSig(merkle_proof) => { + // let mut buffer = vec![0xe1]; + // buffer.extend(merkle_proof.as_ref()); + // buffer.as_ref() + // } + } + } +} + +impl From for u32 { + fn from(encoding: Preset) -> u32 { + match encoding { + Preset::Identity => 0x5f, + Preset::DagPb => 0x70, + Preset::DagCbor => 0x71, + Preset::DagJson => 0x0129, + Preset::Jwt => 0x6a77, + Preset::Eip191 => 0xe191, + // Preset::MerkleBatchSig(_) => 0xe1, + } + } +} + +impl TryFrom for Preset { + type Error = libipld_core::error::UnsupportedCodec; + + fn try_from(value: u32) -> Result { + match value { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => Ok(Preset::MerkleBatchSig), + _ => Err(libipld_core::error::UnsupportedCodec(value as u64)), + } + } +} diff --git a/src/crypto/varsig/header.rs b/src/crypto/varsig/header.rs new file mode 100644 index 00000000..17005ac6 --- /dev/null +++ b/src/crypto/varsig/header.rs @@ -0,0 +1,17 @@ +mod eddsa; +mod es256; +mod es256k; +mod es512; +mod preset; +mod rs256; +mod rs512; +mod traits; + +pub use eddsa::EdDsaHeader; +pub use es256::Es256Header; +pub use es256k::Es256kHeader; +pub use es512::Es512Header; +pub use preset::Preset; +pub use rs256::Rs256Header; +pub use rs512::Rs512Header; +pub use traits::Header; diff --git a/src/crypto/varsig/header/eddsa.rs b/src/crypto/varsig/header/eddsa.rs new file mode 100644 index 00000000..0ccffd0a --- /dev/null +++ b/src/crypto/varsig/header/eddsa.rs @@ -0,0 +1,43 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct EdDsaHeader { + pub codec: C, +} + +impl> TryFrom<&[u8]> for EdDsaHeader { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0xed, inner)) = unsigned_varint::decode::u8(&bytes) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&inner) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(EdDsaHeader { codec }); + } + } + + return Err(()); + } +} + +impl + Clone> From> for Vec { + fn from(ed: EdDsaHeader) -> Vec { + let mut tag_buf: [u8; 2] = Default::default(); + let tag: &[u8] = unsigned_varint::encode::u8(0xed, &mut tag_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc: &[u8] = unsigned_varint::encode::u32(ed.codec.into(), &mut enc_buf); + + [tag, enc].concat().into() + } +} + +impl + TryFrom> Header for EdDsaHeader { + type Signature = ed25519_dalek::Signature; + type Verifier = ed25519_dalek::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es256.rs b/src/crypto/varsig/header/es256.rs new file mode 100644 index 00000000..901d863d --- /dev/null +++ b/src/crypto/varsig/header/es256.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es256Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es256Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1200, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es256Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es256Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1200, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es256Header { + type Signature = p256::ecdsa::Signature; + type Verifier = p256::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es256k.rs b/src/crypto/varsig/header/es256k.rs new file mode 100644 index 00000000..9b01c09f --- /dev/null +++ b/src/crypto/varsig/header/es256k.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es256kHeader { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es256kHeader { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0xe7, inner)) = unsigned_varint::decode::u8(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner).map_err(|_| ()) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es256kHeader { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es256kHeader) -> Vec { + let mut tag_buf: [u8; 2] = Default::default(); + let tag = unsigned_varint::encode::u8(0xe7, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es256kHeader { + type Signature = k256::ecdsa::Signature; + type Verifier = k256::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es512.rs b/src/crypto/varsig/header/es512.rs new file mode 100644 index 00000000..00e4b1a1 --- /dev/null +++ b/src/crypto/varsig/header/es512.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es512Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es512Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1202, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es512Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es512Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1202, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es512Header { + type Signature = p521::ecdsa::Signature; + type Verifier = p521::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs new file mode 100644 index 00000000..4548682b --- /dev/null +++ b/src/crypto/varsig/header/preset.rs @@ -0,0 +1,76 @@ +use super::{eddsa, es256, es256k, es512, rs256, rs512, Header}; +use crate::{crypto::varsig::encoding, did::key}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Preset { + EdDsa(eddsa::EdDsaHeader), + Es256(es256::Es256Header), + Es256k(es256k::Es256kHeader), + Es512(es512::Es512Header), + Rs256(rs256::Rs256Header), + Rs512(rs512::Rs512Header), + // FIXME BLS? + // FIXME Es384 +} + +impl From for Vec { + fn from(preset: Preset) -> Vec { + match preset { + Preset::EdDsa(ed) => ed.into(), + Preset::Rs256(rs256) => rs256.into(), + Preset::Rs512(rs512) => rs512.into(), + Preset::Es256(es256) => es256.into(), + Preset::Es256k(es256k) => es256k.into(), + Preset::Es512(es512) => es512.into(), + } + } +} + +impl<'a> TryFrom<&'a [u8]> for Preset { + type Error = (); + + fn try_from(bytes: &'a [u8]) -> Result { + if let Ok(ed) = eddsa::EdDsaHeader::try_from(bytes) { + return Ok(Preset::EdDsa(ed)); + } + + if let Ok(rs256) = rs256::Rs256Header::::try_from(bytes) { + return Ok(Preset::Rs256(rs256)); + } + + if let Ok(rs512) = rs512::Rs512Header::::try_from(bytes) { + return Ok(Preset::Rs512(rs512)); + } + + if let Ok(es256) = es256::Es256Header::::try_from(bytes) { + return Ok(Preset::Es256(es256)); + } + + if let Ok(es256k) = es256k::Es256kHeader::::try_from(bytes) { + return Ok(Preset::Es256k(es256k)); + } + + if let Ok(es512) = es512::Es512Header::::try_from(bytes) { + return Ok(Preset::Es512(es512)); + } + + Err(()) + } +} + +impl Header for Preset { + type Signature = key::Signature; + type Verifier = key::Verifier; + + fn codec(&self) -> &encoding::Preset { + match self { + Preset::EdDsa(ed) => ed.codec(), + Preset::Rs256(rs256) => rs256.codec(), + Preset::Rs512(rs512) => rs512.codec(), + Preset::Es256(es256) => es256.codec(), + Preset::Es256k(es256k) => es256k.codec(), + Preset::Es512(es512) => es512.codec(), + // BLS? + } + } +} diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs new file mode 100644 index 00000000..92f03155 --- /dev/null +++ b/src/crypto/varsig/header/rs256.rs @@ -0,0 +1,49 @@ +use super::Header; +use crate::crypto::rs256::{Signature, VerifyingKey}; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Rs256Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Rs256Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Rs256Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(rs: Rs256Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Rs256Header { + type Signature = Signature; + type Verifier = VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/rs512.rs b/src/crypto/varsig/header/rs512.rs new file mode 100644 index 00000000..d1cb098a --- /dev/null +++ b/src/crypto/varsig/header/rs512.rs @@ -0,0 +1,49 @@ +use super::Header; +use crate::crypto::rs512::{Signature, VerifyingKey}; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Rs512Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Rs512Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Rs512Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(rs: Rs512Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Rs512Header { + type Signature = Signature; + type Verifier = VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs new file mode 100644 index 00000000..6a88f49d --- /dev/null +++ b/src/crypto/varsig/header/traits.rs @@ -0,0 +1,44 @@ +use libipld_core::codec::{Codec, Encode}; +use signature::Verifier; +use thiserror::Error; + +pub trait Header + Into>: + for<'a> TryFrom<&'a [u8]> + Into> +{ + type Signature: signature::SignatureEncoding; + type Verifier: signature::Verifier; + + fn codec(&self) -> &Enc; + + fn encode_payload, Buf: std::io::Write>( + &self, + payload: T, + buffer: &mut Buf, + ) -> Result<(), libipld_core::error::Error> { + payload.encode(Self::codec(self).clone(), buffer) + } + + fn try_verify<'a, T: Encode>( + &self, + verifier: &'a Self::Verifier, + signature: &'a Self::Signature, + payload: T, + ) -> Result<(), VerifyError> { + let mut buffer = vec![]; + self.encode_payload(payload, &mut buffer) + .map_err(VerifyError::CodecError)?; + + verifier + .verify(&buffer, signature) + .map_err(VerifyError::SignatureError) + } +} + +#[derive(Debug, Error)] +pub enum VerifyError { + #[error("Varsig codec error: {0}")] + CodecError(libipld_core::error::Error), + + #[error("varsig signature error: {0}")] + SignatureError(signature::Error), +} diff --git a/src/delegation.rs b/src/delegation.rs index 690dbf49..05c5033d 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -25,13 +25,16 @@ pub use payload::{Payload, ValidationError}; use crate::{ ability, - crypto::{signature, Nonce}, + crypto::{signature, varsig, Nonce}, did::{self, Did}, proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; -use libipld_core::ipld::Ipld; +use libipld_core::{ + codec::{Codec, Encode}, + ipld::Ipld, +}; use std::collections::BTreeMap; use web_time::SystemTime; @@ -42,14 +45,28 @@ use web_time::SystemTime; /// # Examples /// FIXME #[derive(Clone, Debug, PartialEq)] -pub struct Delegation(pub signature::Envelope, DID>); +pub struct Delegation< + D, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Delegation; +pub type Preset = Delegation< + ability::preset::Builder, + condition::Preset, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; // FIXME checkable -> provable? -impl Delegation { +impl, Enc: Codec + Into + TryFrom> + Delegation +{ /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.0.payload.issuer @@ -70,14 +87,15 @@ impl Delegation { &self.0.payload.ability_builder } - pub fn map_ability_builder(self, f: F) -> Delegation + pub fn map_ability_builder(self, f: F) -> Delegation where F: FnOnce(B) -> T, { - Delegation(signature::Envelope { - payload: self.0.payload.map_ability(f), - signature: self.0.signature, - }) + Delegation(signature::Envelope::new( + self.0.varsig_header, + self.0.signature, + self.0.payload.map_ability(f), + )) } /// Retrive the `condition` of a [`Delegation`] @@ -113,38 +131,59 @@ impl Delegation { &self.0.payload } - pub fn signature(&self) -> &signature::Witness { + pub fn varsig_header(&self) -> &V { + &self.0.varsig_header + } + + pub fn signature(&self) -> &DID::Signature { &self.0.signature } pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, + Ipld: Encode, { - self.0.validate_signature() + self.0.validate_signature(self.varsig_header()) } pub fn try_sign( signer: &DID::Signer, + varsig_header: V, payload: Payload, ) -> Result where + Ipld: Encode, Payload: Clone, { - signature::Envelope::try_sign(signer, payload).map(Delegation) + signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl CheckSame for Delegation { +impl< + B: CheckSame, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > CheckSame for Delegation +{ type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { self.0.payload.check_same(&proof.payload()) } } -impl CheckParents for Delegation { - type Parents = Delegation; +impl< + T: CheckParents, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > CheckParents for Delegation +{ + type Parents = Delegation; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 258cc1b5..160bc1bb 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,6 +1,15 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{crypto::Nonce, did::Did, proof::checkable::Checkable, time::Timestamp}; -use libipld_core::{cid::Cid, ipld::Ipld}; +use crate::{ + crypto::{varsig, Nonce}, + did::Did, + proof::checkable::Checkable, + time::Timestamp, +}; +use libipld_core::{ + cid::Cid, + codec::{Codec, Encode}, + ipld::Ipld, +}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -9,7 +18,15 @@ use web_time::SystemTime; /// /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] -pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { +pub struct Agent< + 'a, + B: Checkable, + C: Condition, + DID: Did, + S: Store, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { /// The [`Did`][Did] of the agent. pub did: &'a DID, @@ -17,7 +34,7 @@ pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(B, C)>, + _marker: PhantomData<(B, C, V, Enc)>, } // FIXME show example of multiple hierarchies of "all things accepted" @@ -28,8 +45,12 @@ impl< B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, - > Agent<'a, B, C, DID, S> + S: Store + Clone, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Agent<'a, B, C, DID, S, V, Enc> +where + Ipld: Encode, { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { @@ -50,7 +71,8 @@ impl< expiration: Timestamp, not_before: Option, now: SystemTime, - ) -> Result, DelegateError<>::Error>> { + varsig_header: V, + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -67,7 +89,7 @@ impl< conditions: new_conditions, }; - return Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")); + return Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")); } let to_delegate = &self @@ -94,14 +116,14 @@ impl< not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) + Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")) } pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<>::Error, DID>> { + delegation: Delegation, + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7a7df4e7..3ab8d1f2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -25,11 +25,11 @@ use std::{collections::BTreeMap, fmt, fmt::Debug}; use thiserror::Error; use web_time::SystemTime; -impl Verifiable for Payload { - fn verifier(&self) -> &DID { - &self.issuer - } -} +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; /// The payload portion of a [`Delegation`][super::Delegation]. /// @@ -140,6 +140,12 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + impl CheckSame for Payload { type Error = ::Error; @@ -481,8 +487,65 @@ pub enum ValidationError { NotYetValid, #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), // FIXME add context? + FailedCondition(C), #[error(transparent)] AbilityError(AbilityError), } + +#[cfg(feature = "test_utils")] +impl Arbitrary + for Payload +where + T::Strategy: 'static, + C::Strategy: 'static, + DID::Parameters: Clone, + C::Parameters: Clone, +{ + type Parameters = (T::Parameters, DID::Parameters, C::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, did_args, c_args): Self::Parameters) -> Self::Strategy { + ( + T::arbitrary_with(t_args), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args), + Nonce::arbitrary(), + Timestamp::arbitrary(), + Option::::arbitrary(), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.0)) + .collect::>() + }), + prop::collection::vec(C::arbitrary_with(c_args), 0..10), + ) + .prop_map( + |( + ability_builder, + issuer, + audience, + subject, + nonce, + expiration, + not_before, + metadata, + conditions, + )| { + Payload { + issuer, + subject, + audience, + ability_builder, + conditions, + metadata, + nonce, + expiration, + not_before, + } + }, + ) + .boxed() + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 27dd6e93..e87399af 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,11 +1,12 @@ use super::Store; use crate::{ ability::arguments, + crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -70,25 +71,43 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, +pub struct MemoryStore< + H, + C: Condition, + DID: Did + Ord, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + ucans: BTreeMap>, index: BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl Store - for MemoryStore +impl< + B: Checkable + Clone, + C: Condition + PartialEq, + DID: Did + Ord + Clone, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Store for MemoryStore where B::Hierarchy: Into> + Clone, { - type Error = (); // FIXME misisng + type DelegationStoreError = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + fn get( + &self, + cid: &Cid, + ) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) .or_default() @@ -96,14 +115,14 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = + let hierarchy: Delegation = delegation.map_ability_builder(Into::into); self.ucans.insert(cid.clone(), hierarchy); Ok(()) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { self.revocations.insert(cid); Ok(()) } @@ -115,7 +134,10 @@ where builder: &B, conditions: Vec, now: SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result< + Option)>>, + Self::DelegationStoreError, + > { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c1129344..93c6d525 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,27 +1,43 @@ use crate::{ + crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, proof::checkable::Checkable, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; +use std::fmt::Debug; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { - type Error; +pub trait Store< + B: Checkable, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> +{ + type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get( + &self, + cid: &Cid, + ) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar // FIXME rename put - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation // sore invocation // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; fn get_chain( &self, @@ -30,7 +46,10 @@ pub trait Store { builder: &B, conditions: Vec, now: SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result< + Option)>>, + Self::DelegationStoreError, + >; fn can_delegate( &self, @@ -39,7 +58,7 @@ pub trait Store { builder: &B, conditions: Vec, now: SystemTime, - ) -> Result { + ) -> Result { self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) } @@ -47,9 +66,9 @@ pub trait Store { fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::Error> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs index 429aadd3..7cbc715e 100644 --- a/src/did/key/signature.rs +++ b/src/did/key/signature.rs @@ -30,7 +30,7 @@ use crate::crypto::bls12381; pub enum Signature { /// `EdDSA` signature. #[cfg(feature = "eddsa")] - EdDSA(ed25519_dalek::Signature), + EdDsa(ed25519_dalek::Signature), /// `ES256K` (`secp256k1`) signature. #[cfg(feature = "es256k")] @@ -63,4 +63,55 @@ pub enum Signature { /// `BLS 12-381` signature for the "min sig" variant. #[cfg(feature = "bls")] BlsMinSig(bls12381::min_sig::Signature), + + /// An unknown signature type. + /// + /// This is primarily for parsing, where reification is delayed + /// until the DID method is known. + Unknown(Vec), +} + +impl signature::SignatureEncoding for Signature { + type Repr = Vec; +} + +impl From for Vec { + fn from(sig: Signature) -> Vec { + match sig { + #[cfg(feature = "eddsa")] + Signature::EdDsa(sig) => sig.to_vec(), + + #[cfg(feature = "es256k")] + Signature::Es256k(sig) => sig.to_vec(), + + #[cfg(feature = "es256")] + Signature::P256(sig) => sig.to_vec(), + + #[cfg(feature = "es384")] + Signature::P384(sig) => sig.to_vec(), + + #[cfg(feature = "es512")] + Signature::P521(sig) => sig.to_vec(), + + #[cfg(feature = "rs256")] + Signature::Rs256(sig) => <[u8; 256]>::from(sig).into(), + + #[cfg(feature = "rs512")] + Signature::Rs512(sig) => <[u8; 512]>::from(sig).into(), + + #[cfg(feature = "bls")] + Signature::BlsMinPk(sig) => <[u8; 96]>::from(sig).into(), + + #[cfg(feature = "bls")] + Signature::BlsMinSig(sig) => <[u8; 48]>::from(sig).into(), + + Signature::Unknown(vec) => vec, + } + } +} + +impl From<&[u8]> for Signature { + fn from(arr: &[u8]) -> Signature { + Signature::Unknown(arr.to_vec()) + } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 2d5d3fcb..743cff94 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -35,7 +35,7 @@ use blst; pub enum Verifier { /// `EdDSA` verifying key. #[cfg(feature = "eddsa")] - EdDSA(ed25519_dalek::VerifyingKey), + EdDsa(ed25519_dalek::VerifyingKey), /// `ES256K` (`secp256k1`) verifying key. #[cfg(feature = "es256k")] @@ -73,7 +73,7 @@ pub enum Verifier { impl signature::Verifier for Verifier { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { match (self, signature) { - (Verifier::EdDSA(vk), Signature::EdDSA(sig)) => { + (Verifier::EdDsa(vk), Signature::EdDsa(sig)) => { vk.verify(msg, sig).map_err(signature::Error::from_source) } (Verifier::Es256k(vk), Signature::Es256k(sig)) => { @@ -110,7 +110,7 @@ impl signature::Verifier for Verifier { impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Verifier::EdDSA(ed25519_pk) => write!( + Verifier::EdDsa(ed25519_pk) => write!( f, "did:key:z6Mk{}", bs58::encode(ed25519_pk.to_bytes()).into_string() @@ -194,7 +194,7 @@ impl FromStr for Verifier { let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) .map_err(|e| e.to_string())?; - return Ok(Verifier::EdDSA(vk)); + return Ok(Verifier::EdDsa(vk)); } ([0xe7, _], _) => { let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) diff --git a/src/invocation.rs b/src/invocation.rs index 846e2555..4b6a4c60 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -23,11 +23,15 @@ pub use payload::{Payload, Promised}; use crate::{ ability, - crypto::signature, + crypto::{signature, varsig}, did::{self, Did}, time::{Expired, Timestamp}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{ + cid::Cid, + codec::{Codec, Encode}, + ipld::Ipld, +}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. @@ -43,23 +47,46 @@ use web_time::SystemTime; /// This is a best practice in message-passing distributed systems because the network is /// [unreliable](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). #[derive(Debug, Clone, PartialEq)] -pub struct Invocation(pub signature::Envelope, DID>); +pub struct Invocation< + A, + DID: did::Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Invocation; - -pub type PresetPromised = Invocation; - -impl Invocation { - pub fn new(payload: Payload, signature: signature::Witness) -> Self { - Invocation(signature::Envelope { payload, signature }) +pub type Preset = Invocation< + ability::preset::Ready, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +pub type PresetPromised = Invocation< + ability::preset::Promised, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +impl, Enc: Codec + TryFrom + Into> + Invocation +where + Ipld: Encode, +{ + pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { + Invocation(signature::Envelope::new(varsig_header, signature, payload)) } pub fn payload(&self) -> &Payload { &self.0.payload } - pub fn signature(&self) -> &signature::Witness { + pub fn varsig_header(&self) -> &V { + &self.0.varsig_header + } + + pub fn signature(&self) -> &DID::Signature { &self.0.signature } @@ -79,57 +106,63 @@ impl Invocation { &self.0.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { - Invocation(signature::Envelope { - payload: self.0.payload.map_ability(f), - signature: self.0.signature, - }) + Invocation(signature::Envelope::new( + self.0.varsig_header, + self.0.signature, + self.0.payload.map_ability(f), + )) } pub fn proofs(&self) -> &Vec { - &self.0.payload.proofs + &self.payload().proofs } pub fn issued_at(&self) -> &Option { - &self.0.payload.issued_at + &self.payload().issued_at } pub fn expiration(&self) -> &Option { - &self.0.payload.expiration + &self.payload().expiration } - pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> - where - A: Clone, - { - self.0.payload.check_time(now) + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { + self.payload().check_time(now) } pub fn try_sign( signer: &DID::Signer, + varsig_header: V, payload: Payload, - ) -> Result, signature::SignError> + ) -> Result, signature::SignError> where Payload: Clone, { - let envelope = signature::Envelope::try_sign(signer, payload)?; + let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; Ok(Invocation(envelope)) } } -impl did::Verifiable for Invocation { +impl, Enc: Codec + TryFrom + Into> + did::Verifiable for Invocation +{ fn verifier(&self) -> &DID { &self.0.verifier() } } -impl Invocation {} +impl, Enc: Codec + TryFrom + Into> + Invocation +{ +} -impl From> for Ipld { - fn from(invocation: Invocation) -> Self { +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(invocation: Invocation) -> Self { invocation.0.into() } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 8b8aba07..b7dd7a79 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,21 +1,23 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, - crypto::{signature::Witness, Nonce}, + crypto::{varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, + invocation::promise, proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, - codec::Encode, + codec::{Codec, Encode}, ipld::Ipld, multihash::{Code, MultihashGeneric}, }; use std::{collections::BTreeMap, fmt, marker::PhantomData}; +use thiserror::Error; use web_time::SystemTime; #[derive(Debug)] @@ -24,9 +26,11 @@ pub struct Agent< T: Resolvable + Delegable, C: Condition, DID: Did, - S: Store, - P: Store, - D: delegation::store::Store, + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, > { pub did: &'a DID, @@ -36,7 +40,7 @@ pub struct Agent< pub resolved_promise_store: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, C)>, + marker: PhantomData<(T, C, V, Enc)>, } impl< @@ -44,12 +48,15 @@ impl< T: Resolvable + Delegable + Clone, C: Condition, DID: Did + Clone, - S: Store, - P: Store, - D: delegation::store::Store, - > Agent<'a, T, C, DID, S, P, D> + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, + Ipld: Encode, delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( @@ -81,8 +88,9 @@ where expiration: Option, issued_at: Option, now: SystemTime, + varsig_header: V, // FIXME err type - ) -> Result, ()> { + ) -> Result, ()> { let proofs = self .delegation_store .get_chain(self.did, subject, &ability.clone().into(), vec![], now) @@ -105,57 +113,56 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) + Ok(Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?) } pub fn receive( &mut self, - promised: Invocation, + promised: Invocation, now: &SystemTime, - // FIXME return type - ) -> Result>, ()> + ) -> Result>, ReceiveError> where C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, T::Builder: Clone + Checkable + Prove + Into>, + Invocation: Clone, + <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug, +

- - rs-ucan Logo - - -

ucan-key-support

- -

- - Crate Information - - - Code Coverage - - - Build Status - - - License - - - Docs - - - Discord - -

-