mirror of
https://github.com/arnaucube/miden-crypto.git
synced 2026-01-10 16:11:30 +01:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3909b01993 | ||
|
|
ee20a49953 | ||
|
|
0d75e3593b | ||
|
|
d74e746a7f | ||
|
|
689cc93ed1 | ||
|
|
7970d3a736 | ||
|
|
a734dace1e | ||
|
|
940cc04670 | ||
|
|
e82baa35bb | ||
|
|
876d1bf97a | ||
|
|
8adc0ab418 | ||
|
|
c2eb38c236 | ||
|
|
a924ac6b81 | ||
|
|
e214608c85 | ||
|
|
c44ccd9dec | ||
|
|
e34900c7d8 | ||
|
|
2b184cd4ca | ||
|
|
913384600d | ||
|
|
ae807a47ae | ||
|
|
f4a9d5b027 | ||
|
|
ee42d87121 | ||
|
|
b1cb2b6ec3 | ||
|
|
e4a9a2ac00 | ||
|
|
c5077b1683 | ||
|
|
2e74028fd4 | ||
|
|
8bf6ef890d | ||
|
|
e2aeb25e01 | ||
|
|
790846cc73 | ||
|
|
4cb6bed428 | ||
|
|
a12e62ff22 | ||
|
|
9aa4987858 | ||
|
|
70a0a1e970 | ||
|
|
025fbb66a9 | ||
|
|
5ee5e8554b | ||
|
|
ac3c6976bd | ||
|
|
374a10f340 | ||
|
|
ad0f472708 | ||
|
|
8bb893345b | ||
|
|
d92fae7f82 | ||
|
|
b171575776 | ||
|
|
dfdd5f722f | ||
|
|
9f63b50510 | ||
|
|
d6ab367d32 | ||
|
|
b06cfa3c03 | ||
|
|
8556c8fc43 | ||
|
|
78ac70120d | ||
|
|
ccde10af13 | ||
|
|
f967211b5a | ||
|
|
d58c717956 | ||
|
|
c0743adac9 | ||
|
|
f72add58cd | ||
|
|
63f97e5621 |
3
.config/nextest.toml
Normal file
3
.config/nextest.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[profile.default]
|
||||
failure-output = "immediate-final"
|
||||
fail-fast = false
|
||||
25
.github/workflows/build.yml
vendored
Normal file
25
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Runs build related jobs.
|
||||
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, next]
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
no-std:
|
||||
name: Build for no-std
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Build for no-std
|
||||
run: |
|
||||
rustup update --no-self-update ${{ matrix.toolchain }}
|
||||
rustup target add wasm32-unknown-unknown
|
||||
make build-no-std
|
||||
23
.github/workflows/changelog.yml
vendored
Normal file
23
.github/workflows/changelog.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Runs changelog related jobs.
|
||||
# CI job heavily inspired by: https://github.com/tarides/changelog-check-action
|
||||
|
||||
name: changelog
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for changes in changelog
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
NO_CHANGELOG_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') }}
|
||||
run: ./scripts/check-changelog.sh "${{ inputs.changelog }}"
|
||||
shell: bash
|
||||
31
.github/workflows/doc.yml
vendored
31
.github/workflows/doc.yml
vendored
@@ -1,31 +0,0 @@
|
||||
# Runs documentation related jobs.
|
||||
|
||||
name: doc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Verify the docs on ${{matrix.toolchain}}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{matrix.toolchain}}
|
||||
override: true
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
- name: cargo make - doc
|
||||
run: cargo make doc
|
||||
81
.github/workflows/lint.yml
vendored
81
.github/workflows/lint.yml
vendored
@@ -4,63 +4,50 @@ name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main, next]
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
name: clippy nightly on ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Clippy
|
||||
run: |
|
||||
rustup update --no-self-update nightly
|
||||
rustup +nightly component add clippy
|
||||
make clippy
|
||||
|
||||
rustfmt:
|
||||
name: rustfmt check nightly on ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Rustfmt
|
||||
run: |
|
||||
rustup update --no-self-update nightly
|
||||
rustup +nightly component add rustfmt
|
||||
make format-check
|
||||
|
||||
doc:
|
||||
name: doc stable on ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Build docs
|
||||
run: |
|
||||
rustup update --no-self-update
|
||||
make doc
|
||||
|
||||
version:
|
||||
name: check rust version consistency
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@main
|
||||
with:
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: check rust versions
|
||||
run: ./scripts/check-rust-version.sh
|
||||
|
||||
rustfmt:
|
||||
name: rustfmt ${{matrix.toolchain}} on ${{matrix.os}}
|
||||
runs-on: ${{matrix.os}}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [nightly]
|
||||
os: [ubuntu]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install minimal Rust with rustfmt
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{matrix.toolchain}}
|
||||
components: rustfmt
|
||||
override: true
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
- name: cargo make - format-check
|
||||
run: cargo make format-check
|
||||
|
||||
clippy:
|
||||
name: clippy ${{matrix.toolchain}} on ${{matrix.os}}
|
||||
runs-on: ${{matrix.os}}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable]
|
||||
os: [ubuntu]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install minimal Rust with clippy
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{matrix.toolchain}}
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
- name: cargo make - clippy
|
||||
run: cargo make clippy
|
||||
|
||||
32
.github/workflows/no-std.yml
vendored
32
.github/workflows/no-std.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# Runs no-std related jobs.
|
||||
|
||||
name: no-std
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
no-std:
|
||||
name: build ${{matrix.toolchain}} no-std for wasm32-unknown-unknown
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{matrix.toolchain}}
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
- name: cargo make - build-no-std
|
||||
run: cargo make build-no-std
|
||||
26
.github/workflows/test.yml
vendored
26
.github/workflows/test.yml
vendored
@@ -1,34 +1,28 @@
|
||||
# Runs testing related jobs
|
||||
# Runs test related jobs.
|
||||
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main, next]
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.features}}
|
||||
name: test ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.args}}
|
||||
runs-on: ${{matrix.os}}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly]
|
||||
os: [ubuntu]
|
||||
features: ["test", "test-no-default-features"]
|
||||
args: [default, no-std]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{matrix.toolchain}}
|
||||
override: true
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
- name: cargo make - test
|
||||
run: cargo make ${{matrix.features}}
|
||||
- uses: actions/checkout@main
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- name: Perform tests
|
||||
run: |
|
||||
rustup update --no-self-update ${{matrix.toolchain}}
|
||||
make test-${{matrix.args}}
|
||||
|
||||
@@ -1,43 +1,34 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: pretty-format-json
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: detect-private-key
|
||||
- repo: https://github.com/hackaugusto/pre-commit-cargo
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
# Allows cargo fmt to modify the source code prior to the commit
|
||||
- id: cargo
|
||||
name: Cargo fmt
|
||||
args: ["+stable", "fmt", "--all"]
|
||||
stages: [commit]
|
||||
# Requires code to be properly formatted prior to pushing upstream
|
||||
- id: cargo
|
||||
name: Cargo fmt --check
|
||||
args: ["+stable", "fmt", "--all", "--check"]
|
||||
stages: [push, manual]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets
|
||||
args: ["+stable", "check", "--all-targets"]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets --no-default-features
|
||||
args: ["+stable", "check", "--all-targets", "--no-default-features"]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets --features default,std,serde
|
||||
args: ["+stable", "check", "--all-targets", "--features", "default,std,serde"]
|
||||
# Unlike fmt, clippy will not be automatically applied
|
||||
- id: cargo
|
||||
name: Cargo clippy
|
||||
args: ["+nightly", "clippy", "--workspace", "--", "--deny", "clippy::all", "--deny", "warnings"]
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: pretty-format-json
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: detect-private-key
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: lint
|
||||
name: Make lint
|
||||
stages: [commit]
|
||||
language: rust
|
||||
entry: make lint
|
||||
- id: doc
|
||||
name: Make doc
|
||||
stages: [commit]
|
||||
language: rust
|
||||
entry: make doc
|
||||
- id: check
|
||||
name: Make check
|
||||
stages: [commit]
|
||||
language: rust
|
||||
entry: make check
|
||||
|
||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,74 +1,110 @@
|
||||
## 0.11.0 (2024-10-30)
|
||||
|
||||
- [BREAKING] Updated Winterfell dependency to v0.10 (#338).
|
||||
|
||||
## 0.11.0 (2024-10-17)
|
||||
|
||||
- [BREAKING]: renamed `Mmr::open()` into `Mmr::open_at()` and `Mmr::peaks()` into `Mmr::peaks_at()` (#234).
|
||||
- Added `Mmr::open()` and `Mmr::peaks()` which rely on `Mmr::open_at()` and `Mmr::peaks()` respectively (#234).
|
||||
- Standardized CI and Makefile across Miden repos (#323).
|
||||
- Added `Smt::compute_mutations()` and `Smt::apply_mutations()` for validation-checked insertions (#327).
|
||||
- Changed padding rule for RPO/RPX hash functions (#318).
|
||||
- [BREAKING] Changed return value of the `Mmr::verify()` and `MerklePath::verify()` from `bool` to `Result<>` (#335).
|
||||
- Added `is_empty()` functions to the `SimpleSmt` and `Smt` structures. Added `EMPTY_ROOT` constant to the `SparseMerkleTree` trait (#337).
|
||||
|
||||
## 0.10.3 (2024-09-25)
|
||||
|
||||
- Implement `get_size_hint` for `Smt` (#331).
|
||||
|
||||
## 0.10.2 (2024-09-25)
|
||||
|
||||
- Implement `get_size_hint` for `RpoDigest` and `RpxDigest` and expose constants for their serialized size (#330).
|
||||
|
||||
## 0.10.1 (2024-09-13)
|
||||
|
||||
- Added `Serializable` and `Deserializable` implementations for `PartialMmr` and `InOrderIndex` (#329).
|
||||
|
||||
## 0.10.0 (2024-08-06)
|
||||
|
||||
- Added more `RpoDigest` and `RpxDigest` conversions (#311).
|
||||
- [BREAKING] Migrated to Winterfell v0.9 (#315).
|
||||
- Fixed encoding of Falcon secret key (#319).
|
||||
|
||||
## 0.9.3 (2024-04-24)
|
||||
|
||||
- Added `RpxRandomCoin` struct (#307).
|
||||
|
||||
## 0.9.2 (2024-04-21)
|
||||
|
||||
* Implemented serialization for the `Smt` struct (#304).
|
||||
* Fixed a bug in Falcon signature generation (#305).
|
||||
- Implemented serialization for the `Smt` struct (#304).
|
||||
- Fixed a bug in Falcon signature generation (#305).
|
||||
|
||||
## 0.9.1 (2024-04-02)
|
||||
|
||||
* Added `num_leaves()` method to `SimpleSmt` (#302).
|
||||
- Added `num_leaves()` method to `SimpleSmt` (#302).
|
||||
|
||||
## 0.9.0 (2024-03-24)
|
||||
|
||||
* [BREAKING] Removed deprecated re-exports from liballoc/libstd (#290).
|
||||
* [BREAKING] Refactored RpoFalcon512 signature to work with pure Rust (#285).
|
||||
* [BREAKING] Added `RngCore` as supertrait for `FeltRng` (#299).
|
||||
- [BREAKING] Removed deprecated re-exports from liballoc/libstd (#290).
|
||||
- [BREAKING] Refactored RpoFalcon512 signature to work with pure Rust (#285).
|
||||
- [BREAKING] Added `RngCore` as supertrait for `FeltRng` (#299).
|
||||
|
||||
# 0.8.4 (2024-03-17)
|
||||
|
||||
* Re-added unintentionally removed re-exported liballoc macros (`vec` and `format` macros).
|
||||
- Re-added unintentionally removed re-exported liballoc macros (`vec` and `format` macros).
|
||||
|
||||
# 0.8.3 (2024-03-17)
|
||||
|
||||
* Re-added unintentionally removed re-exported liballoc macros (#292).
|
||||
- Re-added unintentionally removed re-exported liballoc macros (#292).
|
||||
|
||||
# 0.8.2 (2024-03-17)
|
||||
|
||||
* Updated `no-std` approach to be in sync with winterfell v0.8.3 release (#290).
|
||||
- Updated `no-std` approach to be in sync with winterfell v0.8.3 release (#290).
|
||||
|
||||
## 0.8.1 (2024-02-21)
|
||||
|
||||
* Fixed clippy warnings (#280)
|
||||
- Fixed clippy warnings (#280)
|
||||
|
||||
## 0.8.0 (2024-02-14)
|
||||
|
||||
* Implemented the `PartialMmr` data structure (#195).
|
||||
* Implemented RPX hash function (#201).
|
||||
* Added `FeltRng` and `RpoRandomCoin` (#237).
|
||||
* Accelerated RPO/RPX hash functions using AVX512 instructions (#234).
|
||||
* Added `inner_nodes()` method to `PartialMmr` (#238).
|
||||
* Improved `PartialMmr::apply_delta()` (#242).
|
||||
* Refactored `SimpleSmt` struct (#245).
|
||||
* Replaced `TieredSmt` struct with `Smt` struct (#254, #277).
|
||||
* Updated Winterfell dependency to v0.8 (#275).
|
||||
- Implemented the `PartialMmr` data structure (#195).
|
||||
- Implemented RPX hash function (#201).
|
||||
- Added `FeltRng` and `RpoRandomCoin` (#237).
|
||||
- Accelerated RPO/RPX hash functions using AVX512 instructions (#234).
|
||||
- Added `inner_nodes()` method to `PartialMmr` (#238).
|
||||
- Improved `PartialMmr::apply_delta()` (#242).
|
||||
- Refactored `SimpleSmt` struct (#245).
|
||||
- Replaced `TieredSmt` struct with `Smt` struct (#254, #277).
|
||||
- Updated Winterfell dependency to v0.8 (#275).
|
||||
|
||||
## 0.7.1 (2023-10-10)
|
||||
|
||||
* Fixed RPO Falcon signature build on Windows.
|
||||
- Fixed RPO Falcon signature build on Windows.
|
||||
|
||||
## 0.7.0 (2023-10-05)
|
||||
|
||||
* Replaced `MerklePathSet` with `PartialMerkleTree` (#165).
|
||||
* Implemented clearing of nodes in `TieredSmt` (#173).
|
||||
* Added ability to generate inclusion proofs for `TieredSmt` (#174).
|
||||
* Implemented Falcon DSA (#179).
|
||||
* Added conditional `serde`` support for various structs (#180).
|
||||
* Implemented benchmarking for `TieredSmt` (#182).
|
||||
* Added more leaf traversal methods for `MerkleStore` (#185).
|
||||
* Added SVE acceleration for RPO hash function (#189).
|
||||
- Replaced `MerklePathSet` with `PartialMerkleTree` (#165).
|
||||
- Implemented clearing of nodes in `TieredSmt` (#173).
|
||||
- Added ability to generate inclusion proofs for `TieredSmt` (#174).
|
||||
- Implemented Falcon DSA (#179).
|
||||
- Added conditional `serde`` support for various structs (#180).
|
||||
- Implemented benchmarking for `TieredSmt` (#182).
|
||||
- Added more leaf traversal methods for `MerkleStore` (#185).
|
||||
- Added SVE acceleration for RPO hash function (#189).
|
||||
|
||||
## 0.6.0 (2023-06-25)
|
||||
|
||||
* [BREAKING] Added support for recording capabilities for `MerkleStore` (#162).
|
||||
* [BREAKING] Refactored Merkle struct APIs to use `RpoDigest` instead of `Word` (#157).
|
||||
* Added initial implementation of `PartialMerkleTree` (#156).
|
||||
- [BREAKING] Added support for recording capabilities for `MerkleStore` (#162).
|
||||
- [BREAKING] Refactored Merkle struct APIs to use `RpoDigest` instead of `Word` (#157).
|
||||
- Added initial implementation of `PartialMerkleTree` (#156).
|
||||
|
||||
## 0.5.0 (2023-05-26)
|
||||
|
||||
* Implemented `TieredSmt` (#152, #153).
|
||||
* Implemented ability to extract a subset of a `MerkleStore` (#151).
|
||||
* Cleaned up `SimpleSmt` interface (#149).
|
||||
* Decoupled hashing and padding of peaks in `Mmr` (#148).
|
||||
* Added `inner_nodes()` to `MerkleStore` (#146).
|
||||
- Implemented `TieredSmt` (#152, #153).
|
||||
- Implemented ability to extract a subset of a `MerkleStore` (#151).
|
||||
- Cleaned up `SimpleSmt` interface (#149).
|
||||
- Decoupled hashing and padding of peaks in `Mmr` (#148).
|
||||
- Added `inner_nodes()` to `MerkleStore` (#146).
|
||||
|
||||
## 0.4.0 (2023-04-21)
|
||||
|
||||
@@ -116,6 +152,6 @@
|
||||
|
||||
- Initial release on crates.io containing the cryptographic primitives used in Miden VM and the Miden Rollup.
|
||||
- Hash module with the BLAKE3 and Rescue Prime Optimized hash functions.
|
||||
- BLAKE3 is implemented with 256-bit, 192-bit, or 160-bit output.
|
||||
- RPO is implemented with 256-bit output.
|
||||
- BLAKE3 is implemented with 256-bit, 192-bit, or 160-bit output.
|
||||
- RPO is implemented with 256-bit output.
|
||||
- Merkle module, with a set of data structures related to Merkle trees, implemented using the RPO hash function.
|
||||
|
||||
423
Cargo.lock
generated
423
Cargo.lock
generated
@@ -19,69 +19,70 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -100,15 +101,15 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.5.1"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52"
|
||||
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
@@ -132,6 +133,12 @@ version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@@ -140,13 +147,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.95"
|
||||
version = "1.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
|
||||
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -184,9 +191,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -194,9 +201,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -206,9 +213,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -218,27 +225,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -300,9 +307,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
@@ -332,25 +339,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.2"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
@@ -370,9 +377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -405,9 +412,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
@@ -417,15 +424,21 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
|
||||
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -443,18 +456,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.30"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -470,43 +483,43 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miden-crypto"
|
||||
version = "0.9.2"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"cc",
|
||||
@@ -532,9 +545,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41"
|
||||
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
@@ -546,20 +559,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -575,9 +587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.44"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
|
||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
@@ -586,11 +598,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
@@ -598,9 +609,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.18"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
@@ -608,21 +619,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
version = "11.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
@@ -633,39 +644,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf"
|
||||
checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
@@ -689,9 +703,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -757,9 +771,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -769,9 +783,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -780,21 +794,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.32"
|
||||
version = "0.38.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -811,9 +825,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -832,18 +846,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -852,11 +866,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.116"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
@@ -871,6 +886,12 @@ dependencies = [
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -879,9 +900,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -890,14 +911,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -924,21 +946,21 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
@@ -967,19 +989,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -992,9 +1015,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1002,9 +1025,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1015,51 +1038,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.69"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -1070,10 +1071,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -1087,57 +1097,57 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winter-crypto"
|
||||
version = "0.8.3"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aea508aa819e934c837f24bb706e69d890b9be2db82da39cde887e6f0a37246"
|
||||
checksum = "163da45f1d4d65cac361b8df4835a6daa95b3399154e16eb0305c178c6f6c1f4"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"sha3",
|
||||
@@ -1147,9 +1157,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winter-math"
|
||||
version = "0.8.4"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c36d2a04b4f79f2c8c6945aab6545b7310a0cd6ae47b9210750400df6775a04"
|
||||
checksum = "5a8ba832121679e79b004b0003018c85873956d742a39c348c247f680fe15e00"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winter-utils",
|
||||
@@ -1157,9 +1167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winter-rand-utils"
|
||||
version = "0.8.3"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b19ce50e688442052e957a69d72b8057d72ae8f03a7aea7c2538e11c76b2583"
|
||||
checksum = "4a7616d11fcc26552dada45c803a884ac97c253218835b83a2c63e1c2a988639"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"winter-utils",
|
||||
@@ -1167,6 +1177,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winter-utils"
|
||||
version = "0.8.4"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab6efccf6efa6fd0a80784f3894bc372ada67cc30d9c017fc907d4c0cdce86e7"
|
||||
checksum = "76b116c8ade0172506f8bda32dc674cf6b230adc8516e5138a0173ae69158a4f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "miden-crypto"
|
||||
version = "0.9.2"
|
||||
version = "0.12.0"
|
||||
description = "Miden Cryptographic primitives"
|
||||
authors = ["miden contributors"]
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/0xPolygonMiden/crypto"
|
||||
documentation = "https://docs.rs/miden-crypto/0.9.2"
|
||||
documentation = "https://docs.rs/miden-crypto/0.12.0"
|
||||
categories = ["cryptography", "no-std"]
|
||||
keywords = ["miden", "crypto", "hash", "merkle"]
|
||||
edition = "2021"
|
||||
rust-version = "1.75"
|
||||
rust-version = "1.82"
|
||||
|
||||
[[bin]]
|
||||
name = "miden-crypto"
|
||||
@@ -52,22 +52,22 @@ num = { version = "0.4", default-features = false, features = ["alloc", "libm"]
|
||||
num-complex = { version = "0.4", default-features = false }
|
||||
rand = { version = "0.8", default-features = false }
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
rand-utils = { version = "0.8", package = "winter-rand-utils", optional = true }
|
||||
rand-utils = { version = "0.10", package = "winter-rand-utils", optional = true }
|
||||
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
winter-crypto = { version = "0.8", default-features = false }
|
||||
winter-math = { version = "0.8", default-features = false }
|
||||
winter-utils = { version = "0.8", default-features = false }
|
||||
winter-crypto = { version = "0.10", default-features = false }
|
||||
winter-math = { version = "0.10", default-features = false }
|
||||
winter-utils = { version = "0.10", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
proptest = "1.4"
|
||||
proptest = "1.5"
|
||||
rand_chacha = { version = "0.3", default-features = false }
|
||||
rand-utils = { version = "0.8", package = "winter-rand-utils" }
|
||||
rand-utils = { version = "0.10", package = "winter-rand-utils" }
|
||||
seq-macro = { version = "0.3" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", optional = true, features = ["parallel"] }
|
||||
cc = { version = "1.1", optional = true, features = ["parallel"] }
|
||||
glob = "0.3"
|
||||
|
||||
86
Makefile
Normal file
86
Makefile
Normal file
@@ -0,0 +1,86 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
# -- variables --------------------------------------------------------------------------------------
|
||||
|
||||
WARNINGS=RUSTDOCFLAGS="-D warnings"
|
||||
DEBUG_OVERFLOW_INFO=RUSTFLAGS="-C debug-assertions -C overflow-checks -C debuginfo=2"
|
||||
|
||||
# -- linting --------------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: clippy
|
||||
clippy: ## Run Clippy with configs
|
||||
$(WARNINGS) cargo +nightly clippy --workspace --all-targets --all-features
|
||||
|
||||
|
||||
.PHONY: fix
|
||||
fix: ## Run Fix with configs
|
||||
cargo +nightly fix --allow-staged --allow-dirty --all-targets --all-features
|
||||
|
||||
|
||||
.PHONY: format
|
||||
format: ## Run Format using nightly toolchain
|
||||
cargo +nightly fmt --all
|
||||
|
||||
|
||||
.PHONY: format-check
|
||||
format-check: ## Run Format using nightly toolchain but only in check mode
|
||||
cargo +nightly fmt --all --check
|
||||
|
||||
|
||||
.PHONY: lint
|
||||
lint: format fix clippy ## Run all linting tasks at once (Clippy, fixing, formatting)
|
||||
|
||||
# --- docs ----------------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: doc
|
||||
doc: ## Generate and check documentation
|
||||
$(WARNINGS) cargo doc --all-features --keep-going --release
|
||||
|
||||
# --- testing -------------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: test-default
|
||||
test-default: ## Run tests with default features
|
||||
$(DEBUG_OVERFLOW_INFO) cargo nextest run --profile default --release --all-features
|
||||
|
||||
|
||||
.PHONY: test-no-std
|
||||
test-no-std: ## Run tests with `no-default-features` (std)
|
||||
$(DEBUG_OVERFLOW_INFO) cargo nextest run --profile default --release --no-default-features
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test: test-default test-no-std ## Run all tests
|
||||
|
||||
# --- checking ------------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: check
|
||||
check: ## Check all targets and features for errors without code generation
|
||||
cargo check --all-targets --all-features
|
||||
|
||||
# --- building ------------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build with default features enabled
|
||||
cargo build --release
|
||||
|
||||
.PHONY: build-no-std
|
||||
build-no-std: ## Build without the standard library
|
||||
cargo build --release --no-default-features --target wasm32-unknown-unknown
|
||||
|
||||
.PHONY: build-avx2
|
||||
build-avx2: ## Build with avx2 support
|
||||
RUSTFLAGS="-C target-feature=+avx2" cargo build --release
|
||||
|
||||
.PHONY: build-sve
|
||||
build-sve: ## Build with sve support
|
||||
RUSTFLAGS="-C target-feature=+sve" cargo build --release
|
||||
|
||||
# --- benchmarking --------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: bench-tx
|
||||
bench-tx: ## Run crypto benchmarks
|
||||
cargo bench
|
||||
@@ -1,86 +0,0 @@
|
||||
# Cargo Makefile
|
||||
|
||||
# -- linting --------------------------------------------------------------------------------------
|
||||
[tasks.format]
|
||||
toolchain = "nightly"
|
||||
command = "cargo"
|
||||
args = ["fmt", "--all"]
|
||||
|
||||
[tasks.format-check]
|
||||
toolchain = "nightly"
|
||||
command = "cargo"
|
||||
args = ["fmt", "--all", "--", "--check"]
|
||||
|
||||
[tasks.clippy-default]
|
||||
command = "cargo"
|
||||
args = ["clippy","--workspace", "--all-targets", "--", "-D", "clippy::all", "-D", "warnings"]
|
||||
|
||||
[tasks.clippy-all-features]
|
||||
command = "cargo"
|
||||
args = ["clippy","--workspace", "--all-targets", "--all-features", "--", "-D", "clippy::all", "-D", "warnings"]
|
||||
|
||||
[tasks.clippy]
|
||||
dependencies = [
|
||||
"clippy-default",
|
||||
"clippy-all-features"
|
||||
]
|
||||
|
||||
[tasks.fix]
|
||||
description = "Runs Fix"
|
||||
command = "cargo"
|
||||
toolchain = "nightly"
|
||||
args = ["fix", "--allow-staged", "--allow-dirty", "--all-targets", "--all-features"]
|
||||
|
||||
[tasks.lint]
|
||||
description = "Runs all linting tasks (Clippy, fixing, formatting)"
|
||||
run_task = { name = ["format", "format-check", "clippy", "docs"] }
|
||||
|
||||
# --- docs ----------------------------------------------------------------------------------------
|
||||
[tasks.doc]
|
||||
env = { "RUSTDOCFLAGS" = "-D warnings" }
|
||||
command = "cargo"
|
||||
args = ["doc", "--all-features", "--keep-going", "--release"]
|
||||
|
||||
# --- testing -------------------------------------------------------------------------------------
|
||||
[tasks.test]
|
||||
description = "Run tests with default features"
|
||||
env = { "RUSTFLAGS" = "-C debug-assertions -C overflow-checks -C debuginfo=2" }
|
||||
workspace = false
|
||||
command = "cargo"
|
||||
args = ["test", "--release"]
|
||||
|
||||
[tasks.test-no-default-features]
|
||||
description = "Run tests with no-default-features"
|
||||
env = { "RUSTFLAGS" = "-C debug-assertions -C overflow-checks -C debuginfo=2" }
|
||||
workspace = false
|
||||
command = "cargo"
|
||||
args = ["test", "--release", "--no-default-features"]
|
||||
|
||||
[tasks.test-all]
|
||||
description = "Run all tests"
|
||||
workspace = false
|
||||
run_task = { name = ["test", "test-no-default-features"], parallel = true }
|
||||
|
||||
# --- building ------------------------------------------------------------------------------------
|
||||
[tasks.build]
|
||||
description = "Build in release mode"
|
||||
command = "cargo"
|
||||
args = ["build", "--release"]
|
||||
|
||||
[tasks.build-no-std]
|
||||
description = "Build using no-std"
|
||||
command = "cargo"
|
||||
args = ["build", "--release", "--no-default-features", "--target", "wasm32-unknown-unknown"]
|
||||
|
||||
[tasks.build-avx2]
|
||||
description = "Build using AVX2 acceleration"
|
||||
env = { "RUSTFLAGS" = "-C target-feature=+avx2" }
|
||||
command = "cargo"
|
||||
args = ["build", "--release"]
|
||||
|
||||
[tasks.build-sve]
|
||||
description = "Build with SVE acceleration"
|
||||
env = { "RUSTFLAGS" = "-C target-feature=+sve" }
|
||||
command = "cargo"
|
||||
args = ["build", "--release"]
|
||||
|
||||
75
README.md
75
README.md
@@ -2,84 +2,107 @@
|
||||
|
||||
[](https://github.com/0xPolygonMiden/crypto/blob/main/LICENSE)
|
||||
[](https://github.com/0xPolygonMiden/crypto/actions/workflows/test.yml)
|
||||
[](https://github.com/0xPolygonMiden/crypto/actions/workflows/no-std.yml)
|
||||
[]()
|
||||
[](https://github.com/0xPolygonMiden/crypto/actions/workflows/build.yml)
|
||||
[](https://www.rust-lang.org/tools/install)
|
||||
[](https://crates.io/crates/miden-crypto)
|
||||
|
||||
This crate contains cryptographic primitives used in Polygon Miden.
|
||||
|
||||
## Hash
|
||||
|
||||
[Hash module](./src/hash) provides a set of cryptographic hash functions which are used by the Miden VM and the Miden rollup. Currently, these functions are:
|
||||
|
||||
* [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) hash function with 256-bit, 192-bit, or 160-bit output. The 192-bit and 160-bit outputs are obtained by truncating the 256-bit output of the standard BLAKE3.
|
||||
* [RPO](https://eprint.iacr.org/2022/1577) hash function with 256-bit output. This hash function is an algebraic hash function suitable for recursive STARKs.
|
||||
* [RPX](https://eprint.iacr.org/2023/1045) hash function with 256-bit output. Similar to RPO, this hash function is suitable for recursive STARKs but it is about 2x faster as compared to RPO.
|
||||
- [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) hash function with 256-bit, 192-bit, or 160-bit output. The 192-bit and 160-bit outputs are obtained by truncating the 256-bit output of the standard BLAKE3.
|
||||
- [RPO](https://eprint.iacr.org/2022/1577) hash function with 256-bit output. This hash function is an algebraic hash function suitable for recursive STARKs.
|
||||
- [RPX](https://eprint.iacr.org/2023/1045) hash function with 256-bit output. Similar to RPO, this hash function is suitable for recursive STARKs but it is about 2x faster as compared to RPO.
|
||||
|
||||
For performance benchmarks of these hash functions and their comparison to other popular hash functions please see [here](./benches/).
|
||||
|
||||
## Merkle
|
||||
|
||||
[Merkle module](./src/merkle/) provides a set of data structures related to Merkle trees. All these data structures are implemented using the RPO hash function described above. The data structures are:
|
||||
|
||||
* `MerkleStore`: a collection of Merkle trees of different heights designed to efficiently store trees with common subtrees. When instantiated with `RecordingMap`, a Merkle store records all accesses to the original data.
|
||||
* `MerkleTree`: a regular fully-balanced binary Merkle tree. The depth of this tree can be at most 64.
|
||||
* `Mmr`: a Merkle mountain range structure designed to function as an append-only log.
|
||||
* `PartialMerkleTree`: a partial view of a Merkle tree where some sub-trees may not be known. This is similar to a collection of Merkle paths all resolving to the same root. The length of the paths can be at most 64.
|
||||
* `PartialMmr`: a partial view of a Merkle mountain range structure.
|
||||
* `SimpleSmt`: a Sparse Merkle Tree (with no compaction), mapping 64-bit keys to 4-element values.
|
||||
* `Smt`: a Sparse Merkle tree (with compaction at depth 64), mapping 4-element keys to 4-element values.
|
||||
- `MerkleStore`: a collection of Merkle trees of different heights designed to efficiently store trees with common subtrees. When instantiated with `RecordingMap`, a Merkle store records all accesses to the original data.
|
||||
- `MerkleTree`: a regular fully-balanced binary Merkle tree. The depth of this tree can be at most 64.
|
||||
- `Mmr`: a Merkle mountain range structure designed to function as an append-only log.
|
||||
- `PartialMerkleTree`: a partial view of a Merkle tree where some sub-trees may not be known. This is similar to a collection of Merkle paths all resolving to the same root. The length of the paths can be at most 64.
|
||||
- `PartialMmr`: a partial view of a Merkle mountain range structure.
|
||||
- `SimpleSmt`: a Sparse Merkle Tree (with no compaction), mapping 64-bit keys to 4-element values.
|
||||
- `Smt`: a Sparse Merkle tree (with compaction at depth 64), mapping 4-element keys to 4-element values.
|
||||
|
||||
The module also contains additional supporting components such as `NodeIndex`, `MerklePath`, and `MerkleError` to assist with tree indexation, opening proofs, and reporting inconsistent arguments/state.
|
||||
The module also contains additional supporting components such as `NodeIndex`, `MerklePath`, and `MerkleError` to assist with tree indexation, opening proofs, and reporting inconsistent arguments/state.
|
||||
|
||||
## Signatures
|
||||
|
||||
[DSA module](./src/dsa) provides a set of digital signature schemes supported by default in the Miden VM. Currently, these schemes are:
|
||||
|
||||
* `RPO Falcon512`: a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from the standard in that instead of using SHAKE256 hash function in the *hash-to-point* algorithm we use RPO256. This makes the signature more efficient to verify in Miden VM.
|
||||
- `RPO Falcon512`: a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from the standard in that instead of using SHAKE256 hash function in the _hash-to-point_ algorithm we use RPO256. This makes the signature more efficient to verify in Miden VM.
|
||||
|
||||
For the above signatures, key generation, signing, and signature verification are available for both `std` and `no_std` contexts (see [crate features](#crate-features) below). However, in `no_std` context, the user is responsible for supplying the key generation and signing procedures with a random number generator.
|
||||
|
||||
## Pseudo-Random Element Generator
|
||||
|
||||
[Pseudo random element generator module](./src/rand/) provides a set of traits and data structures that facilitate generating pseudo-random elements in the context of Miden VM and Miden rollup. The module currently includes:
|
||||
|
||||
* `FeltRng`: a trait for generating random field elements and random 4 field elements.
|
||||
* `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait.
|
||||
|
||||
- `FeltRng`: a trait for generating random field elements and random 4 field elements.
|
||||
- `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPO hash function.
|
||||
- `RpxRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPX hash function.
|
||||
|
||||
## Make commands
|
||||
|
||||
We use `make` to automate building, testing, and other processes. In most cases, `make` commands are wrappers around `cargo` commands with specific arguments. You can view the list of available commands in the [Makefile](Makefile), or run the following command:
|
||||
|
||||
```shell
|
||||
make
|
||||
```
|
||||
|
||||
## Crate features
|
||||
|
||||
This crate can be compiled with the following features:
|
||||
|
||||
* `std` - enabled by default and relies on the Rust standard library.
|
||||
* `no_std` does not rely on the Rust standard library and enables compilation to WebAssembly.
|
||||
- `std` - enabled by default and relies on the Rust standard library.
|
||||
- `no_std` does not rely on the Rust standard library and enables compilation to WebAssembly.
|
||||
|
||||
Both of these features imply the use of [alloc](https://doc.rust-lang.org/alloc/) to support heap-allocated collections.
|
||||
|
||||
To compile with `no_std`, disable default features via `--no-default-features` flag.
|
||||
To compile with `no_std`, disable default features via `--no-default-features` flag or using the following command:
|
||||
|
||||
```shell
|
||||
make build-no-std
|
||||
```
|
||||
|
||||
### AVX2 acceleration
|
||||
|
||||
On platforms with [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) support, RPO and RPX hash function can be accelerated by using the vector processing unit. To enable AVX2 acceleration, the code needs to be compiled with the `avx2` target feature enabled. For example:
|
||||
|
||||
```shell
|
||||
cargo make build-avx2
|
||||
make build-avx2
|
||||
```
|
||||
|
||||
### SVE acceleration
|
||||
On platforms with [SVE](https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)) support, RPO and RPX hash function can be accelerated by using the vector processing unit. To enable SVE acceleration, the code needs to be compiled with the `sve` target feature enabled. For example:
|
||||
|
||||
On platforms with [SVE](<https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)>) support, RPO and RPX hash function can be accelerated by using the vector processing unit. To enable SVE acceleration, the code needs to be compiled with the `sve` target feature enabled. For example:
|
||||
|
||||
```shell
|
||||
cargo make build-sve
|
||||
make build-sve
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The best way to test the library is using our `Makefile.toml` and [cargo-make](https://github.com/sagiegurari/cargo-make), this will enable you to use our pre-defined optimized testing commands:
|
||||
The best way to test the library is using our [Makefile](Makefile), this will enable you to use our pre-defined optimized testing commands:
|
||||
|
||||
```shell
|
||||
cargo make test-all
|
||||
make test
|
||||
```
|
||||
|
||||
For example, some of the functions are heavy and might take a while for the tests to complete if using simply `cargo test`. In order to test in release and optimized mode, we have to replicate the test conditions of the development mode so all debug assertions can be verified.
|
||||
|
||||
We do that by enabling some special [flags](https://doc.rust-lang.org/cargo/reference/profiles.html) for the compilation (which we have set as a default in our [Makefile.toml](Makefile.toml)):
|
||||
We do that by enabling some special [flags](https://doc.rust-lang.org/cargo/reference/profiles.html) for the compilation (which we have set as a default in our [Makefile](Makefile)):
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C debug-assertions -C overflow-checks -C debuginfo=2" cargo test --release
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is [MIT licensed](./LICENSE).
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
1.75
|
||||
5
rust-toolchain.toml
Normal file
5
rust-toolchain.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[toolchain]
|
||||
channel = "1.82"
|
||||
components = ["rustfmt", "rust-src", "clippy"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
profile = "minimal"
|
||||
24
rustfmt.toml
24
rustfmt.toml
@@ -2,20 +2,22 @@ edition = "2021"
|
||||
array_width = 80
|
||||
attr_fn_like_width = 80
|
||||
chain_width = 80
|
||||
#condense_wildcard_suffixes = true
|
||||
#enum_discrim_align_threshold = 40
|
||||
comment_width = 100
|
||||
condense_wildcard_suffixes = true
|
||||
fn_call_width = 80
|
||||
#fn_single_line = true
|
||||
#format_code_in_doc_comments = true
|
||||
#format_macro_matchers = true
|
||||
#format_strings = true
|
||||
#group_imports = "StdExternalCrate"
|
||||
#hex_literal_case = "Lower"
|
||||
#imports_granularity = "Crate"
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
group_imports = "StdExternalCrate"
|
||||
hex_literal_case = "Lower"
|
||||
imports_granularity = "Crate"
|
||||
match_block_trailing_comma = true
|
||||
newline_style = "Unix"
|
||||
#normalize_doc_attributes = true
|
||||
#reorder_impl_items = true
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
single_line_if_else_max_width = 60
|
||||
single_line_let_else_max_width = 60
|
||||
struct_lit_width = 40
|
||||
struct_variant_width = 40
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
||||
|
||||
21
scripts/check-changelog.sh
Executable file
21
scripts/check-changelog.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
CHANGELOG_FILE="${1:-CHANGELOG.md}"
|
||||
|
||||
if [ "${NO_CHANGELOG_LABEL}" = "true" ]; then
|
||||
# 'no changelog' set, so finish successfully
|
||||
echo "\"no changelog\" label has been set"
|
||||
exit 0
|
||||
else
|
||||
# a changelog check is required
|
||||
# fail if the diff is empty
|
||||
if git diff --exit-code "origin/${BASE_REF}" -- "${CHANGELOG_FILE}"; then
|
||||
>&2 echo "Changes should come with an entry in the \"CHANGELOG.md\" file. This behavior
|
||||
can be overridden by using the \"no changelog\" label, which is used for changes
|
||||
that are trivial / explicitely stated not to require a changelog entry."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "The \"CHANGELOG.md\" file has been updated."
|
||||
fi
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check rust-toolchain file
|
||||
TOOLCHAIN_VERSION=$(cat rust-toolchain)
|
||||
# Get rust-toolchain.toml file channel
|
||||
TOOLCHAIN_VERSION=$(grep 'channel' rust-toolchain.toml | sed -E 's/.*"(.*)".*/\1/')
|
||||
|
||||
# Check workspace Cargo.toml file
|
||||
CARGO_VERSION=$(cat Cargo.toml | grep "rust-version" | cut -d '"' -f 2)
|
||||
# Get workspace Cargo.toml file rust-version
|
||||
CARGO_VERSION=$(grep 'rust-version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')
|
||||
|
||||
# Check version match
|
||||
if [ "$CARGO_VERSION" != "$TOOLCHAIN_VERSION" ]; then
|
||||
echo "Mismatch in Cargo.toml: Expected $TOOLCHAIN_VERSION, found $CARGO_VERSION"
|
||||
exit 1
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::{math::FalconFelt, Nonce, Polynomial, Rpo256, Word, MODULUS, N, ZERO};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use num::Zero;
|
||||
|
||||
use super::{math::FalconFelt, Nonce, Polynomial, Rpo256, Word, MODULUS, N, ZERO};
|
||||
|
||||
// HASH-TO-POINT FUNCTIONS
|
||||
// ================================================================================================
|
||||
|
||||
|
||||
@@ -15,12 +15,13 @@ pub use secret_key::SecretKey;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{dsa::rpo_falcon512::SecretKey, Word, ONE};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use winter_math::FieldElement;
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
use crate::{dsa::rpo_falcon512::SecretKey, Word, ONE};
|
||||
|
||||
#[test]
|
||||
fn test_falcon_verification() {
|
||||
let seed = [0_u8; 32];
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::dsa::rpo_falcon512::FALCON_ENCODING_BITS;
|
||||
use alloc::string::ToString;
|
||||
use core::ops::Deref;
|
||||
|
||||
use num::Zero;
|
||||
|
||||
use super::{
|
||||
super::{Rpo256, LOG_N, N, PK_LEN},
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconFelt, Felt, Polynomial,
|
||||
Serializable, Signature, Word,
|
||||
};
|
||||
use alloc::string::ToString;
|
||||
use core::ops::Deref;
|
||||
use num::Zero;
|
||||
use crate::dsa::rpo_falcon512::FALCON_ENCODING_BITS;
|
||||
|
||||
// PUBLIC KEY
|
||||
// ================================================================================================
|
||||
@@ -116,7 +117,7 @@ impl Deserializable for PubKeyPoly {
|
||||
|
||||
if acc_len >= FALCON_ENCODING_BITS {
|
||||
acc_len -= FALCON_ENCODING_BITS;
|
||||
let w = (acc >> acc_len) & 0x3FFF;
|
||||
let w = (acc >> acc_len) & 0x3fff;
|
||||
let element = w.try_into().map_err(|err| {
|
||||
DeserializationError::InvalidValue(format!(
|
||||
"Failed to decode public key: {err}"
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use num::Complex;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use num_complex::Complex64;
|
||||
use rand::Rng;
|
||||
|
||||
use super::{
|
||||
super::{
|
||||
math::{ffldl, ffsampling, gram, normalize_tree, FalconFelt, FastFft, LdlTree, Polynomial},
|
||||
@@ -10,13 +18,6 @@ use super::{
|
||||
use crate::dsa::rpo_falcon512::{
|
||||
hash_to_point::hash_to_point_rpo256, math::ntru_gen, SIG_NONCE_LEN, SK_LEN,
|
||||
};
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use num::Complex;
|
||||
use num_complex::Complex64;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
@@ -27,6 +28,8 @@ const WIDTH_SMALL_POLY_COEFFICIENT: usize = 6;
|
||||
// SECRET KEY
|
||||
// ================================================================================================
|
||||
|
||||
/// Represents the secret key for Falcon DSA.
|
||||
///
|
||||
/// The secret key is a quadruple [[g, -f], [G, -F]] of polynomials with integer coefficients. Each
|
||||
/// polynomial is of degree at most N = 512 and computations with these polynomials is done modulo
|
||||
/// the monic irreducible polynomial ϕ = x^N + 1. The secret key is a basis for a lattice and has
|
||||
@@ -217,15 +220,27 @@ impl Serializable for SecretKey {
|
||||
let mut buffer = Vec::with_capacity(1281);
|
||||
buffer.push(header);
|
||||
|
||||
let f_i8: Vec<i8> = neg_f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
let f_i8: Vec<i8> = neg_f
|
||||
.coefficients
|
||||
.iter()
|
||||
.map(|&a| FalconFelt::new(-a).balanced_value() as i8)
|
||||
.collect();
|
||||
let f_i8_encoded = encode_i8(&f_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&f_i8_encoded);
|
||||
|
||||
let g_i8: Vec<i8> = g.coefficients.iter().map(|&a| a as i8).collect();
|
||||
let g_i8: Vec<i8> = g
|
||||
.coefficients
|
||||
.iter()
|
||||
.map(|&a| FalconFelt::new(a).balanced_value() as i8)
|
||||
.collect();
|
||||
let g_i8_encoded = encode_i8(&g_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&g_i8_encoded);
|
||||
|
||||
let big_f_i8: Vec<i8> = neg_big_f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
let big_f_i8: Vec<i8> = neg_big_f
|
||||
.coefficients
|
||||
.iter()
|
||||
.map(|&a| FalconFelt::new(-a).balanced_value() as i8)
|
||||
.collect();
|
||||
let big_f_i8_encoded = encode_i8(&big_f_i8, WIDTH_BIG_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&big_f_i8_encoded);
|
||||
target.write_bytes(&buffer);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use super::{fft::FastFft, polynomial::Polynomial, samplerz::sampler_z};
|
||||
use alloc::boxed::Box;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use num::{One, Zero};
|
||||
use num_complex::{Complex, Complex64};
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use super::{fft::FastFft, polynomial::Polynomial, samplerz::sampler_z};
|
||||
|
||||
const SIGMIN: f64 = 1.2778336969128337;
|
||||
|
||||
@@ -80,11 +81,11 @@ pub fn normalize_tree(tree: &mut LdlTree, sigma: f64) {
|
||||
LdlTree::Branch(_ell, left, right) => {
|
||||
normalize_tree(left, sigma);
|
||||
normalize_tree(right, sigma);
|
||||
}
|
||||
},
|
||||
LdlTree::Leaf(vector) => {
|
||||
vector[0] = Complex::new(sigma / vector[0].re.sqrt(), 0.0);
|
||||
vector[1] = Complex64::zero();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ pub fn ffsampling<R: Rng>(
|
||||
let z0 = Polynomial::<Complex64>::merge_fft(&bold_z0.0, &bold_z0.1);
|
||||
|
||||
(z0, z1)
|
||||
}
|
||||
},
|
||||
LdlTree::Leaf(value) => {
|
||||
let z0 = sampler_z(t.0.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
let z1 = sampler_z(t.1.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
@@ -118,6 +119,6 @@ pub fn ffsampling<R: Rng>(
|
||||
Polynomial::new(vec![Complex64::new(z0 as f64, 0.0)]),
|
||||
Polynomial::new(vec![Complex64::new(z1 as f64, 0.0)]),
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use super::{field::FalconFelt, polynomial::Polynomial, Inverse};
|
||||
use alloc::vec::Vec;
|
||||
use core::{
|
||||
f64::consts::PI,
|
||||
ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||
};
|
||||
use num::{One, Zero};
|
||||
use num_complex::Complex64;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use num::{One, Zero};
|
||||
use num_complex::Complex64;
|
||||
|
||||
use super::{field::FalconFelt, polynomial::Polynomial, Inverse};
|
||||
|
||||
/// Implements Cyclotomic FFT without bitreversing the outputs, and using precomputed powers of the
|
||||
/// 2n-th primitive root of unity.
|
||||
@@ -73,7 +74,7 @@ where
|
||||
rev
|
||||
}
|
||||
|
||||
/// Computes the first n powers of the 2nth root of unity, and put them in bit-reversed order.
|
||||
/// Computes the first n powers of the 2nd root of unity, and put them in bit-reversed order.
|
||||
#[allow(dead_code)]
|
||||
fn bitreversed_powers(n: usize) -> Vec<Self> {
|
||||
let psi = Self::primitive_root_of_unity(2 * n);
|
||||
@@ -87,7 +88,7 @@ where
|
||||
array
|
||||
}
|
||||
|
||||
/// Computes the first n powers of the 2nth root of unity, invert them, and put them in
|
||||
/// Computes the first n powers of the 2nd root of unity, invert them, and put them in
|
||||
/// bit-reversed order.
|
||||
#[allow(dead_code)]
|
||||
fn bitreversed_powers_inverse(n: usize) -> Vec<Self> {
|
||||
@@ -102,7 +103,8 @@ where
|
||||
array
|
||||
}
|
||||
|
||||
/// Reorders the given elements in the array by reversing the binary expansions of their indices.
|
||||
/// Reorders the given elements in the array by reversing the binary expansions of their
|
||||
/// indices.
|
||||
fn bitreverse_array<T>(array: &mut [T]) {
|
||||
let n = array.len();
|
||||
for i in 0..n {
|
||||
@@ -118,19 +120,14 @@ where
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// - a : &mut [Self]
|
||||
/// (a reference to) a mutable array of field elements which is to
|
||||
/// be transformed under the FFT. The transformation happens in-
|
||||
/// place.
|
||||
/// - a : &mut [Self] (a reference to) a mutable array of field elements which is to be
|
||||
/// transformed under the FFT. The transformation happens in- place.
|
||||
///
|
||||
/// - psi_rev: &[Self]
|
||||
/// (a reference to) an array of powers of psi, from 0 to n-1,
|
||||
/// but ordered by bit-reversed index. Here psi is a primitive root
|
||||
/// of order 2n. You can use
|
||||
/// `Self::bitreversed_powers(psi, n)` for this purpose, but this
|
||||
/// trait implementation is not const. For the performance benefit
|
||||
/// you want a precompiled array, which you can get if you can get
|
||||
/// by implementing the same method and marking it "const".
|
||||
/// - psi_rev: &[Self] (a reference to) an array of powers of psi, from 0 to n-1, but ordered
|
||||
/// by bit-reversed index. Here psi is a primitive root of order 2n. You can use
|
||||
/// `Self::bitreversed_powers(psi, n)` for this purpose, but this trait implementation is not
|
||||
/// const. For the performance benefit you want a precompiled array, which you can get if you
|
||||
/// can get by implementing the same method and marking it "const".
|
||||
fn fft(a: &mut [Self], psi_rev: &[Self]) {
|
||||
let n = a.len();
|
||||
let mut t = n;
|
||||
@@ -158,20 +155,15 @@ where
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// - a : &mut [Self]
|
||||
/// (a reference to) a mutable array of field elements which is to
|
||||
/// be transformed under the IFFT. The transformation happens in-
|
||||
/// place.
|
||||
/// - a : &mut [Self] (a reference to) a mutable array of field elements which is to be
|
||||
/// transformed under the IFFT. The transformation happens in- place.
|
||||
///
|
||||
/// - psi_inv_rev: &[Self]
|
||||
/// (a reference to) an array of powers of psi^-1, from 0 to n-1,
|
||||
/// but ordered by bit-reversed index. Here psi is a primitive root of
|
||||
/// order 2n. You can use
|
||||
/// `Self::bitreversed_powers(Self::inverse_or_zero(psi), n)` for
|
||||
/// this purpose, but this trait implementation is not const. For
|
||||
/// the performance benefit you want a precompiled array, which you
|
||||
/// can get if you can get by implementing the same methods and marking
|
||||
/// them "const".
|
||||
/// - psi_inv_rev: &[Self] (a reference to) an array of powers of psi^-1, from 0 to n-1, but
|
||||
/// ordered by bit-reversed index. Here psi is a primitive root of order 2n. You can use
|
||||
/// `Self::bitreversed_powers(Self::inverse_or_zero(psi), n)` for this purpose, but this
|
||||
/// trait implementation is not const. For the performance benefit you want a precompiled
|
||||
/// array, which you can get if you can get by implementing the same methods and marking them
|
||||
/// "const".
|
||||
fn ifft(a: &mut [Self], psi_inv_rev: &[Self], ninv: Self) {
|
||||
let n = a.len();
|
||||
let mut t = 1;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::{fft::CyclotomicFourier, Inverse, MODULUS};
|
||||
use alloc::string::String;
|
||||
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
||||
use num::{One, Zero};
|
||||
|
||||
use super::{fft::CyclotomicFourier, Inverse, MODULUS};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct FalconFelt(u32);
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
//!
|
||||
//! It uses and acknowledges the work in:
|
||||
//!
|
||||
//! 1. The [reference](https://falcon-sign.info/impl/README.txt.html) implementation by Thomas Pornin.
|
||||
//! 1. The [reference](https://falcon-sign.info/impl/README.txt.html) implementation by Thomas
|
||||
//! Pornin.
|
||||
//! 2. The [Rust](https://github.com/aszepieniec/falcon-rust) implementation by Alan Szepieniec.
|
||||
use super::MODULUS;
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::ops::MulAssign;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use num::{BigInt, FromPrimitive, One, Zero};
|
||||
use num_complex::Complex64;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use super::MODULUS;
|
||||
|
||||
mod fft;
|
||||
pub use fft::{CyclotomicFourier, FastFft};
|
||||
@@ -152,7 +154,7 @@ fn ntru_solve(
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use super::{field::FalconFelt, Inverse};
|
||||
use crate::dsa::rpo_falcon512::{MODULUS, N};
|
||||
use crate::Felt;
|
||||
use alloc::vec::Vec;
|
||||
use core::default::Default;
|
||||
use core::fmt::Debug;
|
||||
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
use core::{
|
||||
default::Default,
|
||||
fmt::Debug,
|
||||
ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use num::{One, Zero};
|
||||
|
||||
use super::{field::FalconFelt, Inverse};
|
||||
use crate::{
|
||||
dsa::rpo_falcon512::{MODULUS, N},
|
||||
Felt,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Polynomial<F> {
|
||||
pub coefficients: Vec<F>,
|
||||
@@ -134,8 +140,8 @@ impl<
|
||||
Self::new(coefficients)
|
||||
}
|
||||
|
||||
/// Computes the galois adjoint of the polynomial in the cyclotomic ring F\[ X \] / < X^n + 1 > ,
|
||||
/// which corresponds to f(x^2).
|
||||
/// Computes the galois adjoint of the polynomial in the cyclotomic ring F\[ X \] / < X^n + 1 >
|
||||
/// , which corresponds to f(x^2).
|
||||
pub fn galois_adjoint(&self) -> Self {
|
||||
Self::new(
|
||||
self.coefficients
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use core::f64::consts::LN_2;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
use rand::Rng;
|
||||
|
||||
/// Samples an integer from {0, ..., 18} according to the distribution χ, which is close to
|
||||
/// the half-Gaussian distribution on the natural numbers with mean 0 and standard deviation
|
||||
@@ -40,18 +40,18 @@ fn approx_exp(x: f64, ccs: f64) -> u64 {
|
||||
// https://eprint.iacr.org/2018/1234
|
||||
// https://github.com/raykzhao/gaussian
|
||||
const C: [u64; 13] = [
|
||||
0x00000004741183A3u64,
|
||||
0x00000036548CFC06u64,
|
||||
0x0000024FDCBF140Au64,
|
||||
0x0000171D939DE045u64,
|
||||
0x0000D00CF58F6F84u64,
|
||||
0x000680681CF796E3u64,
|
||||
0x002D82D8305B0FEAu64,
|
||||
0x011111110E066FD0u64,
|
||||
0x0555555555070F00u64,
|
||||
0x155555555581FF00u64,
|
||||
0x400000000002B400u64,
|
||||
0x7FFFFFFFFFFF4800u64,
|
||||
0x00000004741183a3u64,
|
||||
0x00000036548cfc06u64,
|
||||
0x0000024fdcbf140au64,
|
||||
0x0000171d939de045u64,
|
||||
0x0000d00cf58f6f84u64,
|
||||
0x000680681cf796e3u64,
|
||||
0x002d82d8305b0feau64,
|
||||
0x011111110e066fd0u64,
|
||||
0x0555555555070f00u64,
|
||||
0x155555555581ff00u64,
|
||||
0x400000000002b400u64,
|
||||
0x7fffffffffff4800u64,
|
||||
0x8000000000000000u64,
|
||||
];
|
||||
|
||||
@@ -116,9 +116,10 @@ pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod test {
|
||||
use alloc::vec::Vec;
|
||||
use rand::RngCore;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use rand::RngCore;
|
||||
|
||||
use super::{approx_exp, ber_exp, sampler_z};
|
||||
|
||||
/// RNG used only for testing purposes, whereby the produced
|
||||
|
||||
@@ -9,9 +9,11 @@ mod keys;
|
||||
mod math;
|
||||
mod signature;
|
||||
|
||||
pub use self::keys::{PubKeyPoly, PublicKey, SecretKey};
|
||||
pub use self::math::Polynomial;
|
||||
pub use self::signature::{Signature, SignatureHeader, SignaturePoly};
|
||||
pub use self::{
|
||||
keys::{PubKeyPoly, PublicKey, SecretKey},
|
||||
math::Polynomial,
|
||||
signature::{Signature, SignatureHeader, SignaturePoly},
|
||||
};
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use core::ops::Deref;
|
||||
|
||||
use num::Zero;
|
||||
|
||||
use super::{
|
||||
hash_to_point::hash_to_point_rpo256,
|
||||
keys::PubKeyPoly,
|
||||
@@ -8,7 +10,6 @@ use super::{
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Nonce, Rpo256,
|
||||
Serializable, Word, LOG_N, MODULUS, N, SIG_L2_BOUND, SIG_POLY_BYTE_LEN,
|
||||
};
|
||||
use num::Zero;
|
||||
|
||||
// FALCON SIGNATURE
|
||||
// ================================================================================================
|
||||
@@ -38,12 +39,12 @@ use num::Zero;
|
||||
/// The signature is serialized as:
|
||||
/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial
|
||||
/// together with the degree of the irreducible polynomial phi. For RPO Falcon512, the header
|
||||
/// byte is set to `10111001` which differentiates it from the standardized instantiation of
|
||||
/// the Falcon signature.
|
||||
/// byte is set to `10111001` which differentiates it from the standardized instantiation of the
|
||||
/// Falcon signature.
|
||||
/// 2. 40 bytes for the nonce.
|
||||
/// 4. 625 bytes encoding the `s2` polynomial above.
|
||||
///
|
||||
/// The total size of the signature is (including the extended public key) is 1563 bytes.
|
||||
/// The total size of the signature (including the extended public key) is 1563 bytes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Signature {
|
||||
header: SignatureHeader,
|
||||
@@ -355,10 +356,11 @@ fn are_coefficients_valid(x: &[i16]) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{super::SecretKey, *};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use super::{super::SecretKey, *};
|
||||
|
||||
#[test]
|
||||
fn test_serialization_round_trip() {
|
||||
let seed = [0_u8; 32];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use alloc::string::String;
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::{
|
||||
mem::{size_of, transmute, transmute_copy},
|
||||
ops::Deref,
|
||||
slice::from_raw_parts,
|
||||
slice::{self, from_raw_parts},
|
||||
};
|
||||
|
||||
use super::{Digest, ElementHasher, Felt, FieldElement, Hasher};
|
||||
@@ -33,6 +33,14 @@ const DIGEST20_BYTES: usize = 20;
|
||||
#[cfg_attr(feature = "serde", serde(into = "String", try_from = "&str"))]
|
||||
pub struct Blake3Digest<const N: usize>([u8; N]);
|
||||
|
||||
impl<const N: usize> Blake3Digest<N> {
|
||||
pub fn digests_as_bytes(digests: &[Blake3Digest<N>]) -> &[u8] {
|
||||
let p = digests.as_ptr();
|
||||
let len = digests.len() * N;
|
||||
unsafe { slice::from_raw_parts(p as *const u8, len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for Blake3Digest<N> {
|
||||
fn default() -> Self {
|
||||
Self([0; N])
|
||||
@@ -114,6 +122,10 @@ impl Hasher for Blake3_256 {
|
||||
Self::hash(prepare_merge(values))
|
||||
}
|
||||
|
||||
fn merge_many(values: &[Self::Digest]) -> Self::Digest {
|
||||
Blake3Digest(blake3::hash(Blake3Digest::digests_as_bytes(values)).into())
|
||||
}
|
||||
|
||||
fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
hasher.update(&seed.0);
|
||||
@@ -174,6 +186,11 @@ impl Hasher for Blake3_192 {
|
||||
Blake3Digest(*shrink_bytes(&blake3::hash(bytes).into()))
|
||||
}
|
||||
|
||||
fn merge_many(values: &[Self::Digest]) -> Self::Digest {
|
||||
let bytes: Vec<u8> = values.iter().flat_map(|v| v.as_bytes()).collect();
|
||||
Blake3Digest(*shrink_bytes(&blake3::hash(&bytes).into()))
|
||||
}
|
||||
|
||||
fn merge(values: &[Self::Digest; 2]) -> Self::Digest {
|
||||
Self::hash(prepare_merge(values))
|
||||
}
|
||||
@@ -242,6 +259,11 @@ impl Hasher for Blake3_160 {
|
||||
Self::hash(prepare_merge(values))
|
||||
}
|
||||
|
||||
fn merge_many(values: &[Self::Digest]) -> Self::Digest {
|
||||
let bytes: Vec<u8> = values.iter().flat_map(|v| v.as_bytes()).collect();
|
||||
Blake3Digest(*shrink_bytes(&blake3::hash(&bytes).into()))
|
||||
}
|
||||
|
||||
fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
hasher.update(&seed.0);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rand_utils::rand_vector;
|
||||
|
||||
use super::*;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[test]
|
||||
fn blake3_hash_elements() {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
//! Cryptographic hash functions used by the Miden VM and the Miden rollup.
|
||||
|
||||
use super::{CubeExtension, Felt, FieldElement, StarkField, ONE, ZERO};
|
||||
use super::{CubeExtension, Felt, FieldElement, StarkField, ZERO};
|
||||
|
||||
pub mod blake;
|
||||
|
||||
mod rescue;
|
||||
pub mod rpo {
|
||||
pub use super::rescue::{Rpo256, RpoDigest};
|
||||
pub use super::rescue::{Rpo256, RpoDigest, RpoDigestError};
|
||||
}
|
||||
|
||||
pub mod rpx {
|
||||
pub use super::rescue::{Rpx256, RpxDigest};
|
||||
pub use super::rescue::{Rpx256, RpxDigest, RpxDigestError};
|
||||
}
|
||||
|
||||
// RE-EXPORTS
|
||||
|
||||
@@ -4,40 +4,43 @@ use core::arch::x86_64::*;
|
||||
// https://github.com/0xPolygonZero/plonky2/blob/main/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs
|
||||
|
||||
// Preliminary notes:
|
||||
// 1. AVX does not support addition with carry but 128-bit (2-word) addition can be easily
|
||||
// emulated. The method recognizes that for a + b overflowed iff (a + b) < a:
|
||||
// i. res_lo = a_lo + b_lo
|
||||
// ii. carry_mask = res_lo < a_lo
|
||||
// iii. res_hi = a_hi + b_hi - carry_mask
|
||||
// 1. AVX does not support addition with carry but 128-bit (2-word) addition can be easily emulated.
|
||||
// The method recognizes that for a + b overflowed iff (a + b) < a:
|
||||
// 1. res_lo = a_lo + b_lo
|
||||
// 2. carry_mask = res_lo < a_lo
|
||||
// 3. res_hi = a_hi + b_hi - carry_mask
|
||||
//
|
||||
// Notice that carry_mask is subtracted, not added. This is because AVX comparison instructions
|
||||
// return -1 (all bits 1) for true and 0 for false.
|
||||
//
|
||||
// 2. AVX does not have unsigned 64-bit comparisons. Those can be emulated with signed comparisons
|
||||
// by recognizing that a <u b iff a + (1 << 63) <s b + (1 << 63), where the addition wraps around
|
||||
// and the comparisons are unsigned and signed respectively. The shift function adds/subtracts
|
||||
// 1 << 63 to enable this trick.
|
||||
// Example: addition with carry.
|
||||
// i. a_lo_s = shift(a_lo)
|
||||
// ii. res_lo_s = a_lo_s + b_lo
|
||||
// iii. carry_mask = res_lo_s <s a_lo_s
|
||||
// iv. res_lo = shift(res_lo_s)
|
||||
// v. res_hi = a_hi + b_hi - carry_mask
|
||||
// The suffix _s denotes a value that has been shifted by 1 << 63. The result of addition is
|
||||
// shifted if exactly one of the operands is shifted, as is the case on line ii. Line iii.
|
||||
// performs a signed comparison res_lo_s <s a_lo_s on shifted values to emulate unsigned
|
||||
// comparison res_lo <u a_lo on unshifted values. Finally, line iv. reverses the shift so the
|
||||
// result can be returned.
|
||||
// When performing a chain of calculations, we can often save instructions by letting the shift
|
||||
// propagate through and only undoing it when necessary. For example, to compute the addition of
|
||||
// three two-word (128-bit) numbers we can do:
|
||||
// i. a_lo_s = shift(a_lo)
|
||||
// ii. tmp_lo_s = a_lo_s + b_lo
|
||||
// iii. tmp_carry_mask = tmp_lo_s <s a_lo_s
|
||||
// iv. tmp_hi = a_hi + b_hi - tmp_carry_mask
|
||||
// v. res_lo_s = tmp_lo_s + c_lo
|
||||
// vi. res_carry_mask = res_lo_s <s tmp_lo_s
|
||||
// vii. res_lo = shift(res_lo_s)
|
||||
// viii. res_hi = tmp_hi + c_hi - res_carry_mask
|
||||
// and the comparisons are unsigned and signed respectively. The shift function adds/subtracts 1
|
||||
// << 63 to enable this trick. Addition with carry example:
|
||||
// 1. a_lo_s = shift(a_lo)
|
||||
// 2. res_lo_s = a_lo_s + b_lo
|
||||
// 3. carry_mask = res_lo_s <s a_lo_s
|
||||
// 4. res_lo = shift(res_lo_s)
|
||||
// 5. res_hi = a_hi + b_hi - carry_mask
|
||||
//
|
||||
// The suffix _s denotes a value that has been shifted by 1 << 63. The result of addition
|
||||
// is shifted if exactly one of the operands is shifted, as is the case on
|
||||
// line 2. Line 3. performs a signed comparison res_lo_s <s a_lo_s on shifted values to
|
||||
// emulate unsigned comparison res_lo <u a_lo on unshifted values. Finally, line 4. reverses the
|
||||
// shift so the result can be returned.
|
||||
//
|
||||
// When performing a chain of calculations, we can often save instructions by letting
|
||||
// the shift propagate through and only undoing it when necessary.
|
||||
// For example, to compute the addition of three two-word (128-bit) numbers we can do:
|
||||
// 1. a_lo_s = shift(a_lo)
|
||||
// 2. tmp_lo_s = a_lo_s + b_lo
|
||||
// 3. tmp_carry_mask = tmp_lo_s <s a_lo_s
|
||||
// 4. tmp_hi = a_hi + b_hi - tmp_carry_mask
|
||||
// 5. res_lo_s = tmp_lo_s + c_lo vi. res_carry_mask = res_lo_s <s tmp_lo_s
|
||||
// 6. res_carry_mask = res_lo_s <s tmp_lo_s
|
||||
// 7. res_lo = shift(res_lo_s)
|
||||
// 8. res_hi = tmp_hi + c_hi - res_carry_mask
|
||||
//
|
||||
// Notice that the above 3-value addition still only requires two calls to shift, just like our
|
||||
// 2-value addition.
|
||||
|
||||
@@ -60,10 +63,10 @@ pub fn branch_hint() {
|
||||
}
|
||||
|
||||
macro_rules! map3 {
|
||||
($f:ident::<$l:literal>, $v:ident) => {
|
||||
($f:ident:: < $l:literal > , $v:ident) => {
|
||||
($f::<$l>($v.0), $f::<$l>($v.1), $f::<$l>($v.2))
|
||||
};
|
||||
($f:ident::<$l:literal>, $v1:ident, $v2:ident) => {
|
||||
($f:ident:: < $l:literal > , $v1:ident, $v2:ident) => {
|
||||
($f::<$l>($v1.0, $v2.0), $f::<$l>($v1.1, $v2.1), $f::<$l>($v1.2, $v2.2))
|
||||
};
|
||||
($f:ident, $v:ident) => {
|
||||
@@ -72,11 +75,11 @@ macro_rules! map3 {
|
||||
($f:ident, $v0:ident, $v1:ident) => {
|
||||
($f($v0.0, $v1.0), $f($v0.1, $v1.1), $f($v0.2, $v1.2))
|
||||
};
|
||||
($f:ident, rep $v0:ident, $v1:ident) => {
|
||||
($f:ident,rep $v0:ident, $v1:ident) => {
|
||||
($f($v0, $v1.0), $f($v0, $v1.1), $f($v0, $v1.2))
|
||||
};
|
||||
|
||||
($f:ident, $v0:ident, rep $v1:ident) => {
|
||||
($f:ident, $v0:ident,rep $v1:ident) => {
|
||||
($f($v0.0, $v1), $f($v0.1, $v1), $f($v0.2, $v1))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
// FFT-BASED MDS MULTIPLICATION HELPER FUNCTIONS
|
||||
// ================================================================================================
|
||||
|
||||
/// This module contains helper functions as well as constants used to perform the vector-matrix
|
||||
/// multiplication step of the Rescue prime permutation. The special form of our MDS matrix
|
||||
/// i.e. being circular, allows us to reduce the vector-matrix multiplication to a Hadamard product
|
||||
/// of two vectors in "frequency domain". This follows from the simple fact that every circulant
|
||||
/// matrix has the columns of the discrete Fourier transform matrix as orthogonal eigenvectors.
|
||||
/// The implementation also avoids the use of 3-point FFTs, and 3-point iFFTs, and substitutes that
|
||||
/// with explicit expressions. It also avoids, due to the form of our matrix in the frequency domain,
|
||||
/// divisions by 2 and repeated modular reductions. This is because of our explicit choice of
|
||||
/// an MDS matrix that has small powers of 2 entries in frequency domain.
|
||||
/// The following implementation has benefited greatly from the discussions and insights of
|
||||
/// Hamish Ivey-Law and Jacqueline Nabaglo of Polygon Zero and is base on Nabaglo's Plonky2
|
||||
/// implementation.
|
||||
//! This module contains helper functions as well as constants used to perform the vector-matrix
|
||||
//! multiplication step of the Rescue prime permutation. The special form of our MDS matrix
|
||||
//! i.e. being circular, allows us to reduce the vector-matrix multiplication to a Hadamard product
|
||||
//! of two vectors in "frequency domain". This follows from the simple fact that every circulant
|
||||
//! matrix has the columns of the discrete Fourier transform matrix as orthogonal eigenvectors.
|
||||
//! The implementation also avoids the use of 3-point FFTs, and 3-point iFFTs, and substitutes that
|
||||
//! with explicit expressions. It also avoids, due to the form of our matrix in the frequency
|
||||
//! domain, divisions by 2 and repeated modular reductions. This is because of our explicit choice
|
||||
//! of an MDS matrix that has small powers of 2 entries in frequency domain.
|
||||
//! The following implementation has benefited greatly from the discussions and insights of
|
||||
//! Hamish Ivey-Law and Jacqueline Nabaglo of Polygon Zero and is base on Nabaglo's Plonky2
|
||||
//! implementation.
|
||||
|
||||
// Rescue MDS matrix in frequency domain.
|
||||
//
|
||||
// More precisely, this is the output of the three 4-point (real) FFTs of the first column of
|
||||
// the MDS matrix i.e. just before the multiplication with the appropriate twiddle factors
|
||||
// and application of the final four 3-point FFT in order to get the full 12-point FFT.
|
||||
// The entries have been scaled appropriately in order to avoid divisions by 2 in iFFT2 and iFFT4.
|
||||
// The code to generate the matrix in frequency domain is based on an adaptation of a code, to generate
|
||||
// MDS matrices efficiently in original domain, that was developed by the Polygon Zero team.
|
||||
// The code to generate the matrix in frequency domain is based on an adaptation of a code, to
|
||||
// generate MDS matrices efficiently in original domain, that was developed by the Polygon Zero
|
||||
// team.
|
||||
const MDS_FREQ_BLOCK_ONE: [i64; 3] = [16, 8, 16];
|
||||
const MDS_FREQ_BLOCK_TWO: [(i64, i64); 3] = [(-1, 2), (-1, 1), (4, 8)];
|
||||
const MDS_FREQ_BLOCK_THREE: [i64; 3] = [-8, 1, 1];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use core::ops::Range;
|
||||
|
||||
use super::{
|
||||
CubeExtension, Digest, ElementHasher, Felt, FieldElement, Hasher, StarkField, ONE, ZERO,
|
||||
};
|
||||
use super::{CubeExtension, Digest, ElementHasher, Felt, FieldElement, Hasher, StarkField, ZERO};
|
||||
|
||||
mod arch;
|
||||
pub use arch::optimized::{add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox};
|
||||
@@ -11,10 +9,10 @@ mod mds;
|
||||
use mds::{apply_mds, MDS};
|
||||
|
||||
mod rpo;
|
||||
pub use rpo::{Rpo256, RpoDigest};
|
||||
pub use rpo::{Rpo256, RpoDigest, RpoDigestError};
|
||||
|
||||
mod rpx;
|
||||
pub use rpx::{Rpx256, RpxDigest};
|
||||
pub use rpx::{Rpx256, RpxDigest, RpxDigestError};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use alloc::string::String;
|
||||
use core::{cmp::Ordering, fmt::Display, ops::Deref};
|
||||
use core::{cmp::Ordering, fmt::Display, ops::Deref, slice};
|
||||
|
||||
use super::{Digest, Felt, StarkField, DIGEST_BYTES, DIGEST_SIZE, ZERO};
|
||||
use crate::{
|
||||
@@ -19,6 +19,9 @@ use crate::{
|
||||
pub struct RpoDigest([Felt; DIGEST_SIZE]);
|
||||
|
||||
impl RpoDigest {
|
||||
/// The serialized size of the digest in bytes.
|
||||
pub const SERIALIZED_SIZE: usize = DIGEST_BYTES;
|
||||
|
||||
pub const fn new(value: [Felt; DIGEST_SIZE]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
@@ -31,13 +34,19 @@ impl RpoDigest {
|
||||
<Self as Digest>::as_bytes(self)
|
||||
}
|
||||
|
||||
pub fn digests_as_elements<'a, I>(digests: I) -> impl Iterator<Item = &'a Felt>
|
||||
pub fn digests_as_elements_iter<'a, I>(digests: I) -> impl Iterator<Item = &'a Felt>
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
{
|
||||
digests.flat_map(|d| d.0.iter())
|
||||
}
|
||||
|
||||
pub fn digests_as_elements(digests: &[Self]) -> &[Felt] {
|
||||
let p = digests.as_ptr();
|
||||
let len = digests.len() * DIGEST_SIZE;
|
||||
unsafe { slice::from_raw_parts(p as *const Felt, len) }
|
||||
}
|
||||
|
||||
/// Returns hexadecimal representation of this digest prefixed with `0x`.
|
||||
pub fn to_hex(&self) -> String {
|
||||
bytes_to_hex_string(self.as_bytes())
|
||||
@@ -118,26 +127,106 @@ impl Randomizable for RpoDigest {
|
||||
// CONVERSIONS: FROM RPO DIGEST
|
||||
// ================================================================================================
|
||||
|
||||
impl From<&RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
value.0
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RpoDigestError {
|
||||
InvalidInteger,
|
||||
}
|
||||
|
||||
impl TryFrom<&RpoDigest> for [bool; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &RpoDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.0
|
||||
impl TryFrom<RpoDigest> for [bool; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: RpoDigest) -> Result<Self, Self::Error> {
|
||||
fn to_bool(v: u64) -> Option<bool> {
|
||||
if v <= 1 {
|
||||
Some(v == 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Ok([
|
||||
to_bool(value.0[0].as_int()).ok_or(RpoDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[1].as_int()).ok_or(RpoDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[2].as_int()).ok_or(RpoDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[3].as_int()).ok_or(RpoDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpoDigest> for [u8; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &RpoDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpoDigest> for [u8; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: RpoDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpoDigest> for [u16; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &RpoDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpoDigest> for [u16; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: RpoDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpoDigest> for [u32; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &RpoDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpoDigest> for [u32; DIGEST_SIZE] {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: RpoDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for [u64; DIGEST_SIZE] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
[
|
||||
value.0[0].as_int(),
|
||||
value.0[1].as_int(),
|
||||
value.0[2].as_int(),
|
||||
value.0[3].as_int(),
|
||||
]
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,9 +241,21 @@ impl From<RpoDigest> for [u64; DIGEST_SIZE] {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for [u8; DIGEST_BYTES] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
value.as_bytes()
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +265,6 @@ impl From<RpoDigest> for [u8; DIGEST_BYTES] {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.to_hex()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
@@ -178,13 +272,83 @@ impl From<&RpoDigest> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.to_hex()
|
||||
}
|
||||
}
|
||||
|
||||
// CONVERSIONS: TO RPO DIGEST
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RpoDigestError {
|
||||
/// The provided u64 integer does not fit in the field's moduli.
|
||||
InvalidInteger,
|
||||
impl From<&[bool; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: &[bool; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[bool; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: [bool; DIGEST_SIZE]) -> Self {
|
||||
[value[0] as u32, value[1] as u32, value[2] as u32, value[3] as u32].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: &[u8; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: [u8; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u16; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: &[u16; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u16; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: [u16; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u32; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: &[u32; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u32; DIGEST_SIZE]> for RpoDigest {
|
||||
fn from(value: [u32; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u64; DIGEST_SIZE]> for RpoDigest {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &[u64; DIGEST_SIZE]) -> Result<Self, RpoDigestError> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u64; DIGEST_SIZE]> for RpoDigest {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: [u64; DIGEST_SIZE]) -> Result<Self, RpoDigestError> {
|
||||
Ok(Self([
|
||||
value[0].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[1].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[2].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[3].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[Felt; DIGEST_SIZE]> for RpoDigest {
|
||||
@@ -199,6 +363,14 @@ impl From<[Felt; DIGEST_SIZE]> for RpoDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8; DIGEST_BYTES]> for RpoDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
fn try_from(value: &[u8; DIGEST_BYTES]) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u8; DIGEST_BYTES]> for RpoDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
@@ -218,14 +390,6 @@ impl TryFrom<[u8; DIGEST_BYTES]> for RpoDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8; DIGEST_BYTES]> for RpoDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
fn try_from(value: &[u8; DIGEST_BYTES]) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for RpoDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
@@ -234,33 +398,12 @@ impl TryFrom<&[u8]> for RpoDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u64; DIGEST_SIZE]> for RpoDigest {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: [u64; DIGEST_SIZE]) -> Result<Self, RpoDigestError> {
|
||||
Ok(Self([
|
||||
value[0].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[1].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[2].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
value[3].try_into().map_err(|_| RpoDigestError::InvalidInteger)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u64; DIGEST_SIZE]> for RpoDigest {
|
||||
type Error = RpoDigestError;
|
||||
|
||||
fn try_from(value: &[u64; DIGEST_SIZE]) -> Result<Self, RpoDigestError> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RpoDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
/// Expects the string to start with `0x`.
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
hex_to_bytes(value).and_then(|v| v.try_into())
|
||||
hex_to_bytes::<DIGEST_BYTES>(value).and_then(RpoDigest::try_from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +432,10 @@ impl Serializable for RpoDigest {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.as_bytes());
|
||||
}
|
||||
|
||||
fn get_size_hint(&self) -> usize {
|
||||
Self::SERIALIZED_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for RpoDigest {
|
||||
@@ -325,6 +472,7 @@ impl IntoIterator for RpoDigest {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::String;
|
||||
|
||||
use rand_utils::rand_value;
|
||||
|
||||
use super::{Deserializable, Felt, RpoDigest, Serializable, DIGEST_BYTES, DIGEST_SIZE};
|
||||
@@ -342,6 +490,7 @@ mod tests {
|
||||
let mut bytes = vec![];
|
||||
d1.write_into(&mut bytes);
|
||||
assert_eq!(DIGEST_BYTES, bytes.len());
|
||||
assert_eq!(bytes.len(), d1.get_size_hint());
|
||||
|
||||
let mut reader = SliceReader::new(&bytes);
|
||||
let d2 = RpoDigest::read_from(&mut reader).unwrap();
|
||||
@@ -373,44 +522,72 @@ mod tests {
|
||||
Felt::new(rand_value()),
|
||||
]);
|
||||
|
||||
let v: [Felt; DIGEST_SIZE] = digest.into();
|
||||
// BY VALUE
|
||||
// ----------------------------------------------------------------------------------------
|
||||
let v: [bool; DIGEST_SIZE] = [true, false, true, true];
|
||||
let v2: RpoDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
assert_eq!(v, <[bool; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [Felt; DIGEST_SIZE] = (&digest).into();
|
||||
let v: [u8; DIGEST_SIZE] = [0_u8, 1_u8, 2_u8, 3_u8];
|
||||
let v2: RpoDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
assert_eq!(v, <[u8; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u16; DIGEST_SIZE] = [0_u16, 1_u16, 2_u16, 3_u16];
|
||||
let v2: RpoDigest = v.into();
|
||||
assert_eq!(v, <[u16; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u32; DIGEST_SIZE] = [0_u32, 1_u32, 2_u32, 3_u32];
|
||||
let v2: RpoDigest = v.into();
|
||||
assert_eq!(v, <[u32; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = digest.into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
let v: [Felt; DIGEST_SIZE] = digest.into();
|
||||
let v2: RpoDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = digest.into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = (&digest).into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = digest.into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = (&digest).into();
|
||||
let v2: RpoDigest = v.try_into().unwrap();
|
||||
// BY REF
|
||||
// ----------------------------------------------------------------------------------------
|
||||
let v: [bool; DIGEST_SIZE] = [true, false, true, true];
|
||||
let v2: RpoDigest = (&v).into();
|
||||
assert_eq!(v, <[bool; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u8; DIGEST_SIZE] = [0_u8, 1_u8, 2_u8, 3_u8];
|
||||
let v2: RpoDigest = (&v).into();
|
||||
assert_eq!(v, <[u8; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u16; DIGEST_SIZE] = [0_u16, 1_u16, 2_u16, 3_u16];
|
||||
let v2: RpoDigest = (&v).into();
|
||||
assert_eq!(v, <[u16; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u32; DIGEST_SIZE] = [0_u32, 1_u32, 2_u32, 3_u32];
|
||||
let v2: RpoDigest = (&v).into();
|
||||
assert_eq!(v, <[u32; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpoDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = digest.into();
|
||||
let v2: RpoDigest = (&v).try_into().unwrap();
|
||||
let v: [Felt; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpoDigest = (&v).into();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = (&digest).into();
|
||||
let v2: RpoDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = (&digest).into();
|
||||
let v2: RpoDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ use super::{
|
||||
add_constants, add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox, apply_inv_sbox,
|
||||
apply_mds, apply_sbox, Digest, ElementHasher, Felt, FieldElement, Hasher, StarkField, ARK1,
|
||||
ARK2, BINARY_CHUNK_SIZE, CAPACITY_RANGE, DIGEST_BYTES, DIGEST_RANGE, DIGEST_SIZE, INPUT1_RANGE,
|
||||
INPUT2_RANGE, MDS, NUM_ROUNDS, ONE, RATE_RANGE, RATE_WIDTH, STATE_WIDTH, ZERO,
|
||||
INPUT2_RANGE, MDS, NUM_ROUNDS, RATE_RANGE, RATE_WIDTH, STATE_WIDTH, ZERO,
|
||||
};
|
||||
|
||||
mod digest;
|
||||
pub use digest::RpoDigest;
|
||||
pub use digest::{RpoDigest, RpoDigestError};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -19,12 +19,14 @@ mod tests;
|
||||
/// Implementation of the Rescue Prime Optimized hash function with 256-bit output.
|
||||
///
|
||||
/// The hash function is implemented according to the Rescue Prime Optimized
|
||||
/// [specifications](https://eprint.iacr.org/2022/1577)
|
||||
/// [specifications](https://eprint.iacr.org/2022/1577) while the padding rule follows the one
|
||||
/// described [here](https://eprint.iacr.org/2023/1045).
|
||||
///
|
||||
/// The parameters used to instantiate the function are:
|
||||
/// * Field: 64-bit prime field with modulus 2^64 - 2^32 + 1.
|
||||
/// * Field: 64-bit prime field with modulus p = 2^64 - 2^32 + 1.
|
||||
/// * State width: 12 field elements.
|
||||
/// * Capacity size: 4 field elements.
|
||||
/// * Rate size: r = 8 field elements.
|
||||
/// * Capacity size: c = 4 field elements.
|
||||
/// * Number of founds: 7.
|
||||
/// * S-Box degree: 7.
|
||||
///
|
||||
@@ -50,8 +52,23 @@ mod tests;
|
||||
///
|
||||
/// Thus, if the underlying data consists of valid field elements, it might make more sense
|
||||
/// to deserialize them into field elements and then hash them using
|
||||
/// [hash_elements()](Rpo256::hash_elements) function rather then hashing the serialized bytes
|
||||
/// [hash_elements()](Rpo256::hash_elements) function rather than hashing the serialized bytes
|
||||
/// using [hash()](Rpo256::hash) function.
|
||||
///
|
||||
/// ## Domain separation
|
||||
/// [merge_in_domain()](Rpo256::merge_in_domain) hashes two digests into one digest with some domain
|
||||
/// identifier and the current implementation sets the second capacity element to the value of
|
||||
/// this domain identifier. Using a similar argument to the one formulated for domain separation of
|
||||
/// the RPX hash function in Appendix C of its [specification](https://eprint.iacr.org/2023/1045),
|
||||
/// one sees that doing so degrades only pre-image resistance, from its initial bound of c.log_2(p),
|
||||
/// by as much as the log_2 of the size of the domain identifier space. Since pre-image resistance
|
||||
/// becomes the bottleneck for the security bound of the sponge in overwrite-mode only when it is
|
||||
/// lower than 2^128, we see that the target 128-bit security level is maintained as long as
|
||||
/// the size of the domain identifier space, including for padding, is less than 2^128.
|
||||
///
|
||||
/// ## Hashing of empty input
|
||||
/// The current implementation hashes empty input to the zero digest [0, 0, 0, 0]. This has
|
||||
/// the benefit of requiring no calls to the RPO permutation when hashing empty input.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Rpo256();
|
||||
|
||||
@@ -65,14 +82,16 @@ impl Hasher for Rpo256 {
|
||||
// initialize the state with zeroes
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
|
||||
// set the capacity (first element) to a flag on whether or not the input length is evenly
|
||||
// divided by the rate. this will prevent collisions between padded and non-padded inputs,
|
||||
// and will rule out the need to perform an extra permutation in case of evenly divided
|
||||
// inputs.
|
||||
let is_rate_multiple = bytes.len() % RATE_WIDTH == 0;
|
||||
if !is_rate_multiple {
|
||||
state[CAPACITY_RANGE.start] = ONE;
|
||||
}
|
||||
// determine the number of field elements needed to encode `bytes` when each field element
|
||||
// represents at most 7 bytes.
|
||||
let num_field_elem = bytes.len().div_ceil(BINARY_CHUNK_SIZE);
|
||||
|
||||
// set the first capacity element to `RATE_WIDTH + (num_field_elem % RATE_WIDTH)`. We do
|
||||
// this to achieve:
|
||||
// 1. Domain separating hashing of `[u8]` from hashing of `[Felt]`.
|
||||
// 2. Avoiding collisions at the `[Felt]` representation of the encoded bytes.
|
||||
state[CAPACITY_RANGE.start] =
|
||||
Felt::from((RATE_WIDTH + (num_field_elem % RATE_WIDTH)) as u8);
|
||||
|
||||
// initialize a buffer to receive the little-endian elements.
|
||||
let mut buf = [0_u8; 8];
|
||||
@@ -81,41 +100,49 @@ impl Hasher for Rpo256 {
|
||||
// into the state.
|
||||
//
|
||||
// every time the rate range is filled, a permutation is performed. if the final value of
|
||||
// `i` is not zero, then the chunks count wasn't enough to fill the state range, and an
|
||||
// additional permutation must be performed.
|
||||
let i = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |i, chunk| {
|
||||
// the last element of the iteration may or may not be a full chunk. if it's not, then
|
||||
// we need to pad the remainder bytes of the chunk with zeroes, separated by a `1`.
|
||||
// this will avoid collisions.
|
||||
if chunk.len() == BINARY_CHUNK_SIZE {
|
||||
// `rate_pos` is not zero, then the chunks count wasn't enough to fill the state range,
|
||||
// and an additional permutation must be performed.
|
||||
let mut current_chunk_idx = 0_usize;
|
||||
// handle the case of an empty `bytes`
|
||||
let last_chunk_idx = if num_field_elem == 0 {
|
||||
current_chunk_idx
|
||||
} else {
|
||||
num_field_elem - 1
|
||||
};
|
||||
let rate_pos = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |rate_pos, chunk| {
|
||||
// copy the chunk into the buffer
|
||||
if current_chunk_idx != last_chunk_idx {
|
||||
buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
|
||||
} else {
|
||||
// on the last iteration, we pad `buf` with a 1 followed by as many 0's as are
|
||||
// needed to fill it
|
||||
buf.fill(0);
|
||||
buf[..chunk.len()].copy_from_slice(chunk);
|
||||
buf[chunk.len()] = 1;
|
||||
}
|
||||
current_chunk_idx += 1;
|
||||
|
||||
// set the current rate element to the input. since we take at most 7 bytes, we are
|
||||
// guaranteed that the inputs data will fit into a single field element.
|
||||
state[RATE_RANGE.start + i] = Felt::new(u64::from_le_bytes(buf));
|
||||
state[RATE_RANGE.start + rate_pos] = Felt::new(u64::from_le_bytes(buf));
|
||||
|
||||
// proceed filling the range. if it's full, then we apply a permutation and reset the
|
||||
// counter to the beginning of the range.
|
||||
if i == RATE_WIDTH - 1 {
|
||||
if rate_pos == RATE_WIDTH - 1 {
|
||||
Self::apply_permutation(&mut state);
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
rate_pos + 1
|
||||
}
|
||||
});
|
||||
|
||||
// if we absorbed some elements but didn't apply a permutation to them (would happen when
|
||||
// the number of elements is not a multiple of RATE_WIDTH), apply the RPO permutation. we
|
||||
// don't need to apply any extra padding because the first capacity element contains a
|
||||
// flag indicating whether the input is evenly divisible by the rate.
|
||||
if i != 0 {
|
||||
state[RATE_RANGE.start + i..RATE_RANGE.end].fill(ZERO);
|
||||
state[RATE_RANGE.start + i] = ONE;
|
||||
// flag indicating the number of field elements constituting the last block when the latter
|
||||
// is not divisible by `RATE_WIDTH`.
|
||||
if rate_pos != 0 {
|
||||
state[RATE_RANGE.start + rate_pos..RATE_RANGE.end].fill(ZERO);
|
||||
Self::apply_permutation(&mut state);
|
||||
}
|
||||
|
||||
@@ -127,7 +154,7 @@ impl Hasher for Rpo256 {
|
||||
// initialize the state by copying the digest elements into the rate portion of the state
|
||||
// (8 total elements), and set the capacity elements to 0.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
let it = Self::Digest::digests_as_elements(values.iter());
|
||||
let it = Self::Digest::digests_as_elements_iter(values.iter());
|
||||
for (i, v) in it.enumerate() {
|
||||
state[RATE_RANGE.start + i] = *v;
|
||||
}
|
||||
@@ -137,29 +164,28 @@ impl Hasher for Rpo256 {
|
||||
RpoDigest::new(state[DIGEST_RANGE].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn merge_many(values: &[Self::Digest]) -> Self::Digest {
|
||||
Self::hash_elements(Self::Digest::digests_as_elements(values))
|
||||
}
|
||||
|
||||
fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest {
|
||||
// initialize the state as follows:
|
||||
// - seed is copied into the first 4 elements of the rate portion of the state.
|
||||
// - if the value fits into a single field element, copy it into the fifth rate element
|
||||
// and set the sixth rate element to 1.
|
||||
// - if the value doesn't fit into a single field element, split it into two field
|
||||
// elements, copy them into rate elements 5 and 6, and set the seventh rate element
|
||||
// to 1.
|
||||
// - set the first capacity element to 1
|
||||
// - if the value fits into a single field element, copy it into the fifth rate element and
|
||||
// set the first capacity element to 5.
|
||||
// - if the value doesn't fit into a single field element, split it into two field elements,
|
||||
// copy them into rate elements 5 and 6 and set the first capacity element to 6.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
state[INPUT1_RANGE].copy_from_slice(seed.as_elements());
|
||||
state[INPUT2_RANGE.start] = Felt::new(value);
|
||||
if value < Felt::MODULUS {
|
||||
state[INPUT2_RANGE.start + 1] = ONE;
|
||||
state[CAPACITY_RANGE.start] = Felt::from(5_u8);
|
||||
} else {
|
||||
state[INPUT2_RANGE.start + 1] = Felt::new(value / Felt::MODULUS);
|
||||
state[INPUT2_RANGE.start + 2] = ONE;
|
||||
state[CAPACITY_RANGE.start] = Felt::from(6_u8);
|
||||
}
|
||||
|
||||
// common padding for both cases
|
||||
state[CAPACITY_RANGE.start] = ONE;
|
||||
|
||||
// apply the RPO permutation and return the first four elements of the state
|
||||
// apply the RPO permutation and return the first four elements of the rate
|
||||
Self::apply_permutation(&mut state);
|
||||
RpoDigest::new(state[DIGEST_RANGE].try_into().unwrap())
|
||||
}
|
||||
@@ -173,11 +199,9 @@ impl ElementHasher for Rpo256 {
|
||||
let elements = E::slice_as_base_elements(elements);
|
||||
|
||||
// initialize state to all zeros, except for the first element of the capacity part, which
|
||||
// is set to 1 if the number of elements is not a multiple of RATE_WIDTH.
|
||||
// is set to `elements.len() % RATE_WIDTH`.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
if elements.len() % RATE_WIDTH != 0 {
|
||||
state[CAPACITY_RANGE.start] = ONE;
|
||||
}
|
||||
state[CAPACITY_RANGE.start] = Self::BaseField::from((elements.len() % RATE_WIDTH) as u8);
|
||||
|
||||
// absorb elements into the state one by one until the rate portion of the state is filled
|
||||
// up; then apply the Rescue permutation and start absorbing again; repeat until all
|
||||
@@ -194,11 +218,8 @@ impl ElementHasher for Rpo256 {
|
||||
|
||||
// if we absorbed some elements but didn't apply a permutation to them (would happen when
|
||||
// the number of elements is not a multiple of RATE_WIDTH), apply the RPO permutation after
|
||||
// padding by appending a 1 followed by as many 0 as necessary to make the input length a
|
||||
// multiple of the RATE_WIDTH.
|
||||
// padding by as many 0 as necessary to make the input length a multiple of the RATE_WIDTH.
|
||||
if i > 0 {
|
||||
state[RATE_RANGE.start + i] = ONE;
|
||||
i += 1;
|
||||
while i != RATE_WIDTH {
|
||||
state[RATE_RANGE.start + i] = ZERO;
|
||||
i += 1;
|
||||
@@ -273,7 +294,7 @@ impl Rpo256 {
|
||||
// initialize the state by copying the digest elements into the rate portion of the state
|
||||
// (8 total elements), and set the capacity elements to 0.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
let it = RpoDigest::digests_as_elements(values.iter());
|
||||
let it = RpoDigest::digests_as_elements_iter(values.iter());
|
||||
for (i, v) in it.enumerate() {
|
||||
state[RATE_RANGE.start + i] = *v;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rand_utils::rand_value;
|
||||
|
||||
use super::{
|
||||
super::{apply_inv_sbox, apply_sbox, ALPHA, INV_ALPHA},
|
||||
Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ONE, STATE_WIDTH, ZERO,
|
||||
Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, STATE_WIDTH, ZERO,
|
||||
};
|
||||
use crate::{
|
||||
hash::rescue::{BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_WIDTH},
|
||||
Word, ONE,
|
||||
};
|
||||
use crate::Word;
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
|
||||
#[test]
|
||||
fn test_sbox() {
|
||||
@@ -58,7 +62,7 @@ fn merge_vs_merge_in_domain() {
|
||||
];
|
||||
let merge_result = Rpo256::merge(&digests);
|
||||
|
||||
// ------------- merge with domain = 0 ----------------------------------------------------------
|
||||
// ------------- merge with domain = 0 -------------
|
||||
|
||||
// set domain to ZERO. This should not change the result.
|
||||
let domain = ZERO;
|
||||
@@ -66,7 +70,7 @@ fn merge_vs_merge_in_domain() {
|
||||
let merge_in_domain_result = Rpo256::merge_in_domain(&digests, domain);
|
||||
assert_eq!(merge_result, merge_in_domain_result);
|
||||
|
||||
// ------------- merge with domain = 1 ----------------------------------------------------------
|
||||
// ------------- merge with domain = 1 -------------
|
||||
|
||||
// set domain to ONE. This should change the result.
|
||||
let domain = ONE;
|
||||
@@ -125,6 +129,27 @@ fn hash_padding() {
|
||||
assert_ne!(r1, r2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_padding_no_extra_permutation_call() {
|
||||
use crate::hash::rescue::DIGEST_RANGE;
|
||||
|
||||
// Implementation
|
||||
let num_bytes = BINARY_CHUNK_SIZE * RATE_WIDTH;
|
||||
let mut buffer = vec![0_u8; num_bytes];
|
||||
*buffer.last_mut().unwrap() = 97;
|
||||
let r1 = Rpo256::hash(&buffer);
|
||||
|
||||
// Expected
|
||||
let final_chunk = [0_u8, 0, 0, 0, 0, 0, 97, 1];
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
// padding when hashing bytes
|
||||
state[CAPACITY_RANGE.start] = Felt::from(RATE_WIDTH as u8);
|
||||
*state.last_mut().unwrap() = Felt::new(u64::from_le_bytes(final_chunk));
|
||||
Rpo256::apply_permutation(&mut state);
|
||||
|
||||
assert_eq!(&r1[0..4], &state[DIGEST_RANGE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_elements_padding() {
|
||||
let e1 = [Felt::new(rand_value()); 2];
|
||||
@@ -158,6 +183,24 @@ fn hash_elements() {
|
||||
assert_eq!(m_result, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_empty() {
|
||||
let elements: Vec<Felt> = vec![];
|
||||
|
||||
let zero_digest = RpoDigest::default();
|
||||
let h_result = Rpo256::hash_elements(&elements);
|
||||
assert_eq!(zero_digest, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_empty_bytes() {
|
||||
let bytes: Vec<u8> = vec![];
|
||||
|
||||
let zero_digest = RpoDigest::default();
|
||||
let h_result = Rpo256::hash(&bytes);
|
||||
assert_eq!(zero_digest, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_test_vectors() {
|
||||
let elements = [
|
||||
@@ -228,46 +271,46 @@ proptest! {
|
||||
|
||||
const EXPECTED: [Word; 19] = [
|
||||
[
|
||||
Felt::new(1502364727743950833),
|
||||
Felt::new(5880949717274681448),
|
||||
Felt::new(162790463902224431),
|
||||
Felt::new(6901340476773664264),
|
||||
Felt::new(18126731724905382595),
|
||||
Felt::new(7388557040857728717),
|
||||
Felt::new(14290750514634285295),
|
||||
Felt::new(7852282086160480146),
|
||||
],
|
||||
[
|
||||
Felt::new(7478710183745780580),
|
||||
Felt::new(3308077307559720969),
|
||||
Felt::new(3383561985796182409),
|
||||
Felt::new(17205078494700259815),
|
||||
Felt::new(10139303045932500183),
|
||||
Felt::new(2293916558361785533),
|
||||
Felt::new(15496361415980502047),
|
||||
Felt::new(17904948502382283940),
|
||||
],
|
||||
[
|
||||
Felt::new(17439912364295172999),
|
||||
Felt::new(17979156346142712171),
|
||||
Felt::new(8280795511427637894),
|
||||
Felt::new(9349844417834368814),
|
||||
Felt::new(17457546260239634015),
|
||||
Felt::new(803990662839494686),
|
||||
Felt::new(10386005777401424878),
|
||||
Felt::new(18168807883298448638),
|
||||
],
|
||||
[
|
||||
Felt::new(5105868198472766874),
|
||||
Felt::new(13090564195691924742),
|
||||
Felt::new(1058904296915798891),
|
||||
Felt::new(18379501748825152268),
|
||||
Felt::new(13072499238647455740),
|
||||
Felt::new(10174350003422057273),
|
||||
Felt::new(9201651627651151113),
|
||||
Felt::new(6872461887313298746),
|
||||
],
|
||||
[
|
||||
Felt::new(9133662113608941286),
|
||||
Felt::new(12096627591905525991),
|
||||
Felt::new(14963426595993304047),
|
||||
Felt::new(13290205840019973377),
|
||||
Felt::new(2903803350580990546),
|
||||
Felt::new(1838870750730563299),
|
||||
Felt::new(4258619137315479708),
|
||||
Felt::new(17334260395129062936),
|
||||
],
|
||||
[
|
||||
Felt::new(3134262397541159485),
|
||||
Felt::new(10106105871979362399),
|
||||
Felt::new(138768814855329459),
|
||||
Felt::new(15044809212457404677),
|
||||
Felt::new(8571221005243425262),
|
||||
Felt::new(3016595589318175865),
|
||||
Felt::new(13933674291329928438),
|
||||
Felt::new(678640375034313072),
|
||||
],
|
||||
[
|
||||
Felt::new(162696376578462826),
|
||||
Felt::new(4991300494838863586),
|
||||
Felt::new(660346084748120605),
|
||||
Felt::new(13179389528641752698),
|
||||
Felt::new(16314113978986502310),
|
||||
Felt::new(14587622368743051587),
|
||||
Felt::new(2808708361436818462),
|
||||
Felt::new(10660517522478329440),
|
||||
],
|
||||
[
|
||||
Felt::new(2242391899857912644),
|
||||
@@ -276,46 +319,46 @@ const EXPECTED: [Word; 19] = [
|
||||
Felt::new(5046143039268215739),
|
||||
],
|
||||
[
|
||||
Felt::new(9585630502158073976),
|
||||
Felt::new(1310051013427303477),
|
||||
Felt::new(7491921222636097758),
|
||||
Felt::new(9417501558995216762),
|
||||
Felt::new(5218076004221736204),
|
||||
Felt::new(17169400568680971304),
|
||||
Felt::new(8840075572473868990),
|
||||
Felt::new(12382372614369863623),
|
||||
],
|
||||
[
|
||||
Felt::new(1994394001720334744),
|
||||
Felt::new(10866209900885216467),
|
||||
Felt::new(13836092831163031683),
|
||||
Felt::new(10814636682252756697),
|
||||
Felt::new(9783834557155203486),
|
||||
Felt::new(12317263104955018849),
|
||||
Felt::new(3933748931816109604),
|
||||
Felt::new(1843043029836917214),
|
||||
],
|
||||
[
|
||||
Felt::new(17486854790732826405),
|
||||
Felt::new(17376549265955727562),
|
||||
Felt::new(2371059831956435003),
|
||||
Felt::new(17585704935858006533),
|
||||
Felt::new(14498234468286984551),
|
||||
Felt::new(16837257669834682387),
|
||||
Felt::new(6664141123711355107),
|
||||
Felt::new(4590460158294697186),
|
||||
],
|
||||
[
|
||||
Felt::new(11368277489137713825),
|
||||
Felt::new(3906270146963049287),
|
||||
Felt::new(10236262408213059745),
|
||||
Felt::new(78552867005814007),
|
||||
Felt::new(4661800562479916067),
|
||||
Felt::new(11794407552792839953),
|
||||
Felt::new(9037742258721863712),
|
||||
Felt::new(6287820818064278819),
|
||||
],
|
||||
[
|
||||
Felt::new(17899847381280262181),
|
||||
Felt::new(14717912805498651446),
|
||||
Felt::new(10769146203951775298),
|
||||
Felt::new(2774289833490417856),
|
||||
Felt::new(7752693085194633729),
|
||||
Felt::new(7379857372245835536),
|
||||
Felt::new(9270229380648024178),
|
||||
Felt::new(10638301488452560378),
|
||||
],
|
||||
[
|
||||
Felt::new(3794717687462954368),
|
||||
Felt::new(4386865643074822822),
|
||||
Felt::new(8854162840275334305),
|
||||
Felt::new(7129983987107225269),
|
||||
Felt::new(11542686762698783357),
|
||||
Felt::new(15570714990728449027),
|
||||
Felt::new(7518801014067819501),
|
||||
Felt::new(12706437751337583515),
|
||||
],
|
||||
[
|
||||
Felt::new(7244773535611633983),
|
||||
Felt::new(19359923075859320),
|
||||
Felt::new(10898655967774994333),
|
||||
Felt::new(9319339563065736480),
|
||||
Felt::new(9553923701032839042),
|
||||
Felt::new(7281190920209838818),
|
||||
Felt::new(2488477917448393955),
|
||||
Felt::new(5088955350303368837),
|
||||
],
|
||||
[
|
||||
Felt::new(4935426252518736883),
|
||||
@@ -324,21 +367,21 @@ const EXPECTED: [Word; 19] = [
|
||||
Felt::new(18159875708229758073),
|
||||
],
|
||||
[
|
||||
Felt::new(14871230873837295931),
|
||||
Felt::new(11225255908868362971),
|
||||
Felt::new(18100987641405432308),
|
||||
Felt::new(1559244340089644233),
|
||||
Felt::new(12795429638314178838),
|
||||
Felt::new(14360248269767567855),
|
||||
Felt::new(3819563852436765058),
|
||||
Felt::new(10859123583999067291),
|
||||
],
|
||||
[
|
||||
Felt::new(8348203744950016968),
|
||||
Felt::new(4041411241960726733),
|
||||
Felt::new(17584743399305468057),
|
||||
Felt::new(16836952610803537051),
|
||||
Felt::new(2695742617679420093),
|
||||
Felt::new(9151515850666059759),
|
||||
Felt::new(15855828029180595485),
|
||||
Felt::new(17190029785471463210),
|
||||
],
|
||||
[
|
||||
Felt::new(16139797453633030050),
|
||||
Felt::new(1090233424040889412),
|
||||
Felt::new(10770255347785669036),
|
||||
Felt::new(16982398877290254028),
|
||||
Felt::new(13205273108219124830),
|
||||
Felt::new(2524898486192849221),
|
||||
Felt::new(14618764355375283547),
|
||||
Felt::new(10615614265042186874),
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use alloc::string::String;
|
||||
use core::{cmp::Ordering, fmt::Display, ops::Deref};
|
||||
use core::{cmp::Ordering, fmt::Display, ops::Deref, slice};
|
||||
|
||||
use super::{Digest, Felt, StarkField, DIGEST_BYTES, DIGEST_SIZE, ZERO};
|
||||
use crate::{
|
||||
@@ -19,6 +19,9 @@ use crate::{
|
||||
pub struct RpxDigest([Felt; DIGEST_SIZE]);
|
||||
|
||||
impl RpxDigest {
|
||||
/// The serialized size of the digest in bytes.
|
||||
pub const SERIALIZED_SIZE: usize = DIGEST_BYTES;
|
||||
|
||||
pub const fn new(value: [Felt; DIGEST_SIZE]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
@@ -31,13 +34,19 @@ impl RpxDigest {
|
||||
<Self as Digest>::as_bytes(self)
|
||||
}
|
||||
|
||||
pub fn digests_as_elements<'a, I>(digests: I) -> impl Iterator<Item = &'a Felt>
|
||||
pub fn digests_as_elements_iter<'a, I>(digests: I) -> impl Iterator<Item = &'a Felt>
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
{
|
||||
digests.flat_map(|d| d.0.iter())
|
||||
}
|
||||
|
||||
pub fn digests_as_elements(digests: &[Self]) -> &[Felt] {
|
||||
let p = digests.as_ptr();
|
||||
let len = digests.len() * DIGEST_SIZE;
|
||||
unsafe { slice::from_raw_parts(p as *const Felt, len) }
|
||||
}
|
||||
|
||||
/// Returns hexadecimal representation of this digest prefixed with `0x`.
|
||||
pub fn to_hex(&self) -> String {
|
||||
bytes_to_hex_string(self.as_bytes())
|
||||
@@ -118,26 +127,106 @@ impl Randomizable for RpxDigest {
|
||||
// CONVERSIONS: FROM RPX DIGEST
|
||||
// ================================================================================================
|
||||
|
||||
impl From<&RpxDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: &RpxDigest) -> Self {
|
||||
value.0
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RpxDigestError {
|
||||
InvalidInteger,
|
||||
}
|
||||
|
||||
impl TryFrom<&RpxDigest> for [bool; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &RpxDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpxDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: RpxDigest) -> Self {
|
||||
value.0
|
||||
impl TryFrom<RpxDigest> for [bool; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: RpxDigest) -> Result<Self, Self::Error> {
|
||||
fn to_bool(v: u64) -> Option<bool> {
|
||||
if v <= 1 {
|
||||
Some(v == 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Ok([
|
||||
to_bool(value.0[0].as_int()).ok_or(RpxDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[1].as_int()).ok_or(RpxDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[2].as_int()).ok_or(RpxDigestError::InvalidInteger)?,
|
||||
to_bool(value.0[3].as_int()).ok_or(RpxDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpxDigest> for [u8; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &RpxDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpxDigest> for [u8; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: RpxDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpxDigest> for [u16; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &RpxDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpxDigest> for [u16; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: RpxDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RpxDigest> for [u32; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &RpxDigest) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RpxDigest> for [u32; DIGEST_SIZE] {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: RpxDigest) -> Result<Self, Self::Error> {
|
||||
Ok([
|
||||
value.0[0].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[1].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[2].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value.0[3].as_int().try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpxDigest> for [u64; DIGEST_SIZE] {
|
||||
fn from(value: &RpxDigest) -> Self {
|
||||
[
|
||||
value.0[0].as_int(),
|
||||
value.0[1].as_int(),
|
||||
value.0[2].as_int(),
|
||||
value.0[3].as_int(),
|
||||
]
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +241,18 @@ impl From<RpxDigest> for [u64; DIGEST_SIZE] {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpxDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: &RpxDigest) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpxDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: RpxDigest) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpxDigest> for [u8; DIGEST_BYTES] {
|
||||
fn from(value: &RpxDigest) -> Self {
|
||||
value.as_bytes()
|
||||
@@ -164,13 +265,6 @@ impl From<RpxDigest> for [u8; DIGEST_BYTES] {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpxDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: RpxDigest) -> Self {
|
||||
value.to_hex()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpxDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: &RpxDigest) -> Self {
|
||||
@@ -178,13 +272,83 @@ impl From<&RpxDigest> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpxDigest> for String {
|
||||
/// The returned string starts with `0x`.
|
||||
fn from(value: RpxDigest) -> Self {
|
||||
value.to_hex()
|
||||
}
|
||||
}
|
||||
|
||||
// CONVERSIONS: TO RPX DIGEST
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RpxDigestError {
|
||||
/// The provided u64 integer does not fit in the field's moduli.
|
||||
InvalidInteger,
|
||||
impl From<&[bool; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: &[bool; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[bool; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: [bool; DIGEST_SIZE]) -> Self {
|
||||
[value[0] as u32, value[1] as u32, value[2] as u32, value[3] as u32].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: &[u8; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: [u8; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u16; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: &[u16; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u16; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: [u16; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u32; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: &[u32; DIGEST_SIZE]) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u32; DIGEST_SIZE]> for RpxDigest {
|
||||
fn from(value: [u32; DIGEST_SIZE]) -> Self {
|
||||
Self([value[0].into(), value[1].into(), value[2].into(), value[3].into()])
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u64; DIGEST_SIZE]> for RpxDigest {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &[u64; DIGEST_SIZE]) -> Result<Self, RpxDigestError> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u64; DIGEST_SIZE]> for RpxDigest {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: [u64; DIGEST_SIZE]) -> Result<Self, RpxDigestError> {
|
||||
Ok(Self([
|
||||
value[0].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[1].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[2].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[3].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[Felt; DIGEST_SIZE]> for RpxDigest {
|
||||
@@ -199,6 +363,14 @@ impl From<[Felt; DIGEST_SIZE]> for RpxDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8; DIGEST_BYTES]> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
fn try_from(value: &[u8; DIGEST_BYTES]) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u8; DIGEST_BYTES]> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
@@ -218,14 +390,6 @@ impl TryFrom<[u8; DIGEST_BYTES]> for RpxDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8; DIGEST_BYTES]> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
fn try_from(value: &[u8; DIGEST_BYTES]) -> Result<Self, Self::Error> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
@@ -234,42 +398,12 @@ impl TryFrom<&[u8]> for RpxDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[u64; DIGEST_SIZE]> for RpxDigest {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: [u64; DIGEST_SIZE]) -> Result<Self, RpxDigestError> {
|
||||
Ok(Self([
|
||||
value[0].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[1].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[2].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
value[3].try_into().map_err(|_| RpxDigestError::InvalidInteger)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u64; DIGEST_SIZE]> for RpxDigest {
|
||||
type Error = RpxDigestError;
|
||||
|
||||
fn try_from(value: &[u64; DIGEST_SIZE]) -> Result<Self, RpxDigestError> {
|
||||
(*value).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
/// Expects the string to start with `0x`.
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
hex_to_bytes(value).and_then(|v| v.try_into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
/// Expects the string to start with `0x`.
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
value.as_str().try_into()
|
||||
hex_to_bytes::<DIGEST_BYTES>(value).and_then(RpxDigest::try_from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +416,15 @@ impl TryFrom<&String> for RpxDigest {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RpxDigest {
|
||||
type Error = HexParseError;
|
||||
|
||||
/// Expects the string to start with `0x`.
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
value.as_str().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION / DESERIALIZATION
|
||||
// ================================================================================================
|
||||
|
||||
@@ -289,6 +432,10 @@ impl Serializable for RpxDigest {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.as_bytes());
|
||||
}
|
||||
|
||||
fn get_size_hint(&self) -> usize {
|
||||
Self::SERIALIZED_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for RpxDigest {
|
||||
@@ -308,12 +455,24 @@ impl Deserializable for RpxDigest {
|
||||
}
|
||||
}
|
||||
|
||||
// ITERATORS
|
||||
// ================================================================================================
|
||||
impl IntoIterator for RpxDigest {
|
||||
type Item = Felt;
|
||||
type IntoIter = <[Felt; 4] as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::String;
|
||||
|
||||
use rand_utils::rand_value;
|
||||
|
||||
use super::{Deserializable, Felt, RpxDigest, Serializable, DIGEST_BYTES, DIGEST_SIZE};
|
||||
@@ -331,6 +490,7 @@ mod tests {
|
||||
let mut bytes = vec![];
|
||||
d1.write_into(&mut bytes);
|
||||
assert_eq!(DIGEST_BYTES, bytes.len());
|
||||
assert_eq!(bytes.len(), d1.get_size_hint());
|
||||
|
||||
let mut reader = SliceReader::new(&bytes);
|
||||
let d2 = RpxDigest::read_from(&mut reader).unwrap();
|
||||
@@ -338,7 +498,6 @@ mod tests {
|
||||
assert_eq!(d1, d2);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
fn digest_encoding() {
|
||||
let digest = RpxDigest([
|
||||
@@ -363,44 +522,72 @@ mod tests {
|
||||
Felt::new(rand_value()),
|
||||
]);
|
||||
|
||||
let v: [Felt; DIGEST_SIZE] = digest.into();
|
||||
// BY VALUE
|
||||
// ----------------------------------------------------------------------------------------
|
||||
let v: [bool; DIGEST_SIZE] = [true, false, true, true];
|
||||
let v2: RpxDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
assert_eq!(v, <[bool; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [Felt; DIGEST_SIZE] = (&digest).into();
|
||||
let v: [u8; DIGEST_SIZE] = [0_u8, 1_u8, 2_u8, 3_u8];
|
||||
let v2: RpxDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
assert_eq!(v, <[u8; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u16; DIGEST_SIZE] = [0_u16, 1_u16, 2_u16, 3_u16];
|
||||
let v2: RpxDigest = v.into();
|
||||
assert_eq!(v, <[u16; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u32; DIGEST_SIZE] = [0_u32, 1_u32, 2_u32, 3_u32];
|
||||
let v2: RpxDigest = v.into();
|
||||
assert_eq!(v, <[u32; DIGEST_SIZE]>::try_from(v2).unwrap());
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = digest.into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
let v: [Felt; DIGEST_SIZE] = digest.into();
|
||||
let v2: RpxDigest = v.into();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = digest.into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = (&digest).into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = digest.into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = (&digest).into();
|
||||
let v2: RpxDigest = v.try_into().unwrap();
|
||||
// BY REF
|
||||
// ----------------------------------------------------------------------------------------
|
||||
let v: [bool; DIGEST_SIZE] = [true, false, true, true];
|
||||
let v2: RpxDigest = (&v).into();
|
||||
assert_eq!(v, <[bool; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u8; DIGEST_SIZE] = [0_u8, 1_u8, 2_u8, 3_u8];
|
||||
let v2: RpxDigest = (&v).into();
|
||||
assert_eq!(v, <[u8; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u16; DIGEST_SIZE] = [0_u16, 1_u16, 2_u16, 3_u16];
|
||||
let v2: RpxDigest = (&v).into();
|
||||
assert_eq!(v, <[u16; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u32; DIGEST_SIZE] = [0_u32, 1_u32, 2_u32, 3_u32];
|
||||
let v2: RpxDigest = (&v).into();
|
||||
assert_eq!(v, <[u32; DIGEST_SIZE]>::try_from(&v2).unwrap());
|
||||
|
||||
let v: [u64; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpxDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = digest.into();
|
||||
let v2: RpxDigest = (&v).try_into().unwrap();
|
||||
let v: [Felt; DIGEST_SIZE] = (&digest).into();
|
||||
let v2: RpxDigest = (&v).into();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: [u8; DIGEST_BYTES] = (&digest).into();
|
||||
let v2: RpxDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
|
||||
let v: String = (&digest).into();
|
||||
let v2: RpxDigest = (&v).try_into().unwrap();
|
||||
assert_eq!(digest, v2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use super::{
|
||||
};
|
||||
|
||||
mod digest;
|
||||
pub use digest::RpxDigest;
|
||||
pub use digest::{RpxDigest, RpxDigestError};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub type CubicExtElement = CubeExtension<Felt>;
|
||||
|
||||
@@ -26,8 +29,10 @@ pub type CubicExtElement = CubeExtension<Felt>;
|
||||
/// * Capacity size: 4 field elements.
|
||||
/// * S-Box degree: 7.
|
||||
/// * Rounds: There are 3 different types of rounds:
|
||||
/// - (FB): `apply_mds` → `add_constants` → `apply_sbox` → `apply_mds` → `add_constants` → `apply_inv_sbox`.
|
||||
/// - (E): `add_constants` → `ext_sbox` (which is raising to power 7 in the degree 3 extension field).
|
||||
/// - (FB): `apply_mds` → `add_constants` → `apply_sbox` → `apply_mds` → `add_constants` →
|
||||
/// `apply_inv_sbox`.
|
||||
/// - (E): `add_constants` → `ext_sbox` (which is raising to power 7 in the degree 3 extension
|
||||
/// field).
|
||||
/// - (M): `apply_mds` → `add_constants`.
|
||||
/// * Permutation: (FB) (E) (FB) (E) (FB) (E) (M).
|
||||
///
|
||||
@@ -53,8 +58,23 @@ pub type CubicExtElement = CubeExtension<Felt>;
|
||||
///
|
||||
/// Thus, if the underlying data consists of valid field elements, it might make more sense
|
||||
/// to deserialize them into field elements and then hash them using
|
||||
/// [hash_elements()](Rpx256::hash_elements) function rather then hashing the serialized bytes
|
||||
/// [hash_elements()](Rpx256::hash_elements) function rather than hashing the serialized bytes
|
||||
/// using [hash()](Rpx256::hash) function.
|
||||
///
|
||||
/// ## Domain separation
|
||||
/// [merge_in_domain()](Rpx256::merge_in_domain) hashes two digests into one digest with some domain
|
||||
/// identifier and the current implementation sets the second capacity element to the value of
|
||||
/// this domain identifier. Using a similar argument to the one formulated for domain separation
|
||||
/// in Appendix C of the [specifications](https://eprint.iacr.org/2023/1045), one sees that doing
|
||||
/// so degrades only pre-image resistance, from its initial bound of c.log_2(p), by as much as
|
||||
/// the log_2 of the size of the domain identifier space. Since pre-image resistance becomes
|
||||
/// the bottleneck for the security bound of the sponge in overwrite-mode only when it is
|
||||
/// lower than 2^128, we see that the target 128-bit security level is maintained as long as
|
||||
/// the size of the domain identifier space, including for padding, is less than 2^128.
|
||||
///
|
||||
/// ## Hashing of empty input
|
||||
/// The current implementation hashes empty input to the zero digest [0, 0, 0, 0]. This has
|
||||
/// the benefit of requiring no calls to the RPX permutation when hashing empty input.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Rpx256();
|
||||
|
||||
@@ -86,11 +106,18 @@ impl Hasher for Rpx256 {
|
||||
// into the state.
|
||||
//
|
||||
// every time the rate range is filled, a permutation is performed. if the final value of
|
||||
// `i` is not zero, then the chunks count wasn't enough to fill the state range, and an
|
||||
// additional permutation must be performed.
|
||||
let i = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |i, chunk| {
|
||||
// `rate_pos` is not zero, then the chunks count wasn't enough to fill the state range,
|
||||
// and an additional permutation must be performed.
|
||||
let mut current_chunk_idx = 0_usize;
|
||||
// handle the case of an empty `bytes`
|
||||
let last_chunk_idx = if num_field_elem == 0 {
|
||||
current_chunk_idx
|
||||
} else {
|
||||
num_field_elem - 1
|
||||
};
|
||||
let rate_pos = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |rate_pos, chunk| {
|
||||
// copy the chunk into the buffer
|
||||
if i != num_field_elem - 1 {
|
||||
if current_chunk_idx != last_chunk_idx {
|
||||
buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
|
||||
} else {
|
||||
// on the last iteration, we pad `buf` with a 1 followed by as many 0's as are
|
||||
@@ -99,18 +126,19 @@ impl Hasher for Rpx256 {
|
||||
buf[..chunk.len()].copy_from_slice(chunk);
|
||||
buf[chunk.len()] = 1;
|
||||
}
|
||||
current_chunk_idx += 1;
|
||||
|
||||
// set the current rate element to the input. since we take at most 7 bytes, we are
|
||||
// guaranteed that the inputs data will fit into a single field element.
|
||||
state[RATE_RANGE.start + i] = Felt::new(u64::from_le_bytes(buf));
|
||||
state[RATE_RANGE.start + rate_pos] = Felt::new(u64::from_le_bytes(buf));
|
||||
|
||||
// proceed filling the range. if it's full, then we apply a permutation and reset the
|
||||
// counter to the beginning of the range.
|
||||
if i == RATE_WIDTH - 1 {
|
||||
if rate_pos == RATE_WIDTH - 1 {
|
||||
Self::apply_permutation(&mut state);
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
rate_pos + 1
|
||||
}
|
||||
});
|
||||
|
||||
@@ -119,8 +147,8 @@ impl Hasher for Rpx256 {
|
||||
// don't need to apply any extra padding because the first capacity element contains a
|
||||
// flag indicating the number of field elements constituting the last block when the latter
|
||||
// is not divisible by `RATE_WIDTH`.
|
||||
if i != 0 {
|
||||
state[RATE_RANGE.start + i..RATE_RANGE.end].fill(ZERO);
|
||||
if rate_pos != 0 {
|
||||
state[RATE_RANGE.start + rate_pos..RATE_RANGE.end].fill(ZERO);
|
||||
Self::apply_permutation(&mut state);
|
||||
}
|
||||
|
||||
@@ -132,7 +160,7 @@ impl Hasher for Rpx256 {
|
||||
// initialize the state by copying the digest elements into the rate portion of the state
|
||||
// (8 total elements), and set the capacity elements to 0.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
let it = Self::Digest::digests_as_elements(values.iter());
|
||||
let it = Self::Digest::digests_as_elements_iter(values.iter());
|
||||
for (i, v) in it.enumerate() {
|
||||
state[RATE_RANGE.start + i] = *v;
|
||||
}
|
||||
@@ -142,13 +170,17 @@ impl Hasher for Rpx256 {
|
||||
RpxDigest::new(state[DIGEST_RANGE].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn merge_many(values: &[Self::Digest]) -> Self::Digest {
|
||||
Self::hash_elements(Self::Digest::digests_as_elements(values))
|
||||
}
|
||||
|
||||
fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest {
|
||||
// initialize the state as follows:
|
||||
// - seed is copied into the first 4 elements of the rate portion of the state.
|
||||
// - if the value fits into a single field element, copy it into the fifth rate element and
|
||||
// set the first capacity element to 5.
|
||||
// - if the value doesn't fit into a single field element, split it into two field
|
||||
// elements, copy them into rate elements 5 and 6 and set the first capacity element to 6.
|
||||
// - if the value doesn't fit into a single field element, split it into two field elements,
|
||||
// copy them into rate elements 5 and 6 and set the first capacity element to 6.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
state[INPUT1_RANGE].copy_from_slice(seed.as_elements());
|
||||
state[INPUT2_RANGE.start] = Felt::new(value);
|
||||
@@ -159,7 +191,7 @@ impl Hasher for Rpx256 {
|
||||
state[CAPACITY_RANGE.start] = Felt::from(6_u8);
|
||||
}
|
||||
|
||||
// apply the RPX permutation and return the first four elements of the state
|
||||
// apply the RPX permutation and return the first four elements of the rate
|
||||
Self::apply_permutation(&mut state);
|
||||
RpxDigest::new(state[DIGEST_RANGE].try_into().unwrap())
|
||||
}
|
||||
@@ -265,7 +297,7 @@ impl Rpx256 {
|
||||
// initialize the state by copying the digest elements into the rate portion of the state
|
||||
// (8 total elements), and set the capacity elements to 0.
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
let it = RpxDigest::digests_as_elements(values.iter());
|
||||
let it = RpxDigest::digests_as_elements_iter(values.iter());
|
||||
for (i, v) in it.enumerate() {
|
||||
state[RATE_RANGE.start + i] = *v;
|
||||
}
|
||||
|
||||
186
src/hash/rescue/rpx/tests.rs
Normal file
186
src/hash/rescue/rpx/tests.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rand_utils::rand_value;
|
||||
|
||||
use super::{Felt, Hasher, Rpx256, StarkField, ZERO};
|
||||
use crate::{hash::rescue::RpxDigest, ONE};
|
||||
|
||||
#[test]
|
||||
fn hash_elements_vs_merge() {
|
||||
let elements = [Felt::new(rand_value()); 8];
|
||||
|
||||
let digests: [RpxDigest; 2] = [
|
||||
RpxDigest::new(elements[..4].try_into().unwrap()),
|
||||
RpxDigest::new(elements[4..].try_into().unwrap()),
|
||||
];
|
||||
|
||||
let m_result = Rpx256::merge(&digests);
|
||||
let h_result = Rpx256::hash_elements(&elements);
|
||||
assert_eq!(m_result, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_vs_merge_in_domain() {
|
||||
let elements = [Felt::new(rand_value()); 8];
|
||||
|
||||
let digests: [RpxDigest; 2] = [
|
||||
RpxDigest::new(elements[..4].try_into().unwrap()),
|
||||
RpxDigest::new(elements[4..].try_into().unwrap()),
|
||||
];
|
||||
let merge_result = Rpx256::merge(&digests);
|
||||
|
||||
// ----- merge with domain = 0 ----------------------------------------------------------------
|
||||
|
||||
// set domain to ZERO. This should not change the result.
|
||||
let domain = ZERO;
|
||||
|
||||
let merge_in_domain_result = Rpx256::merge_in_domain(&digests, domain);
|
||||
assert_eq!(merge_result, merge_in_domain_result);
|
||||
|
||||
// ----- merge with domain = 1 ----------------------------------------------------------------
|
||||
|
||||
// set domain to ONE. This should change the result.
|
||||
let domain = ONE;
|
||||
|
||||
let merge_in_domain_result = Rpx256::merge_in_domain(&digests, domain);
|
||||
assert_ne!(merge_result, merge_in_domain_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_elements_vs_merge_with_int() {
|
||||
let tmp = [Felt::new(rand_value()); 4];
|
||||
let seed = RpxDigest::new(tmp);
|
||||
|
||||
// ----- value fits into a field element ------------------------------------------------------
|
||||
let val: Felt = Felt::new(rand_value());
|
||||
let m_result = Rpx256::merge_with_int(seed, val.as_int());
|
||||
|
||||
let mut elements = seed.as_elements().to_vec();
|
||||
elements.push(val);
|
||||
let h_result = Rpx256::hash_elements(&elements);
|
||||
|
||||
assert_eq!(m_result, h_result);
|
||||
|
||||
// ----- value does not fit into a field element ----------------------------------------------
|
||||
let val = Felt::MODULUS + 2;
|
||||
let m_result = Rpx256::merge_with_int(seed, val);
|
||||
|
||||
let mut elements = seed.as_elements().to_vec();
|
||||
elements.push(Felt::new(val));
|
||||
elements.push(ONE);
|
||||
let h_result = Rpx256::hash_elements(&elements);
|
||||
|
||||
assert_eq!(m_result, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_padding() {
|
||||
// adding a zero bytes at the end of a byte string should result in a different hash
|
||||
let r1 = Rpx256::hash(&[1_u8, 2, 3]);
|
||||
let r2 = Rpx256::hash(&[1_u8, 2, 3, 0]);
|
||||
assert_ne!(r1, r2);
|
||||
|
||||
// same as above but with bigger inputs
|
||||
let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6]);
|
||||
let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 0]);
|
||||
assert_ne!(r1, r2);
|
||||
|
||||
// same as above but with input splitting over two elements
|
||||
let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7]);
|
||||
let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0]);
|
||||
assert_ne!(r1, r2);
|
||||
|
||||
// same as above but with multiple zeros
|
||||
let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0]);
|
||||
let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0]);
|
||||
assert_ne!(r1, r2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_elements_padding() {
|
||||
let e1 = [Felt::new(rand_value()); 2];
|
||||
let e2 = [e1[0], e1[1], ZERO];
|
||||
|
||||
let r1 = Rpx256::hash_elements(&e1);
|
||||
let r2 = Rpx256::hash_elements(&e2);
|
||||
assert_ne!(r1, r2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_elements() {
|
||||
let elements = [
|
||||
ZERO,
|
||||
ONE,
|
||||
Felt::new(2),
|
||||
Felt::new(3),
|
||||
Felt::new(4),
|
||||
Felt::new(5),
|
||||
Felt::new(6),
|
||||
Felt::new(7),
|
||||
];
|
||||
|
||||
let digests: [RpxDigest; 2] = [
|
||||
RpxDigest::new(elements[..4].try_into().unwrap()),
|
||||
RpxDigest::new(elements[4..8].try_into().unwrap()),
|
||||
];
|
||||
|
||||
let m_result = Rpx256::merge(&digests);
|
||||
let h_result = Rpx256::hash_elements(&elements);
|
||||
assert_eq!(m_result, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_empty() {
|
||||
let elements: Vec<Felt> = vec![];
|
||||
|
||||
let zero_digest = RpxDigest::default();
|
||||
let h_result = Rpx256::hash_elements(&elements);
|
||||
assert_eq!(zero_digest, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_empty_bytes() {
|
||||
let bytes: Vec<u8> = vec![];
|
||||
|
||||
let zero_digest = RpxDigest::default();
|
||||
let h_result = Rpx256::hash(&bytes);
|
||||
assert_eq!(zero_digest, h_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sponge_bytes_with_remainder_length_wont_panic() {
|
||||
// this test targets to assert that no panic will happen with the edge case of having an inputs
|
||||
// with length that is not divisible by the used binary chunk size. 113 is a non-negligible
|
||||
// input length that is prime; hence guaranteed to not be divisible by any choice of chunk
|
||||
// size.
|
||||
//
|
||||
// this is a preliminary test to the fuzzy-stress of proptest.
|
||||
Rpx256::hash(&[0; 113]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sponge_collision_for_wrapped_field_element() {
|
||||
let a = Rpx256::hash(&[0; 8]);
|
||||
let b = Rpx256::hash(&Felt::MODULUS.to_le_bytes());
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sponge_zeroes_collision() {
|
||||
let mut zeroes = Vec::with_capacity(255);
|
||||
let mut set = BTreeSet::new();
|
||||
(0..255).for_each(|_| {
|
||||
let hash = Rpx256::hash(&zeroes);
|
||||
zeroes.push(0);
|
||||
// panic if a collision was found
|
||||
assert!(set.insert(hash));
|
||||
});
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn rpo256_wont_panic_with_arbitrary_input(ref bytes in any::<Vec<u8>>()) {
|
||||
Rpx256::hash(bytes);
|
||||
}
|
||||
}
|
||||
49
src/main.rs
49
src/main.rs
@@ -35,6 +35,7 @@ pub fn benchmark_smt() {
|
||||
|
||||
let mut tree = construction(entries, tree_size).unwrap();
|
||||
insertion(&mut tree, tree_size).unwrap();
|
||||
batched_insertion(&mut tree, tree_size).unwrap();
|
||||
proof_generation(&mut tree, tree_size).unwrap();
|
||||
}
|
||||
|
||||
@@ -82,6 +83,54 @@ pub fn insertion(tree: &mut Smt, size: u64) -> Result<(), MerkleError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn batched_insertion(tree: &mut Smt, size: u64) -> Result<(), MerkleError> {
|
||||
println!("Running a batched insertion benchmark:");
|
||||
|
||||
let new_pairs: Vec<(RpoDigest, Word)> = (0..1000)
|
||||
.map(|i| {
|
||||
let key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
|
||||
let value = [ONE, ONE, ONE, Felt::new(size + i)];
|
||||
(key, value)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let now = Instant::now();
|
||||
let mutations = tree.compute_mutations(new_pairs);
|
||||
let compute_elapsed = now.elapsed();
|
||||
|
||||
let now = Instant::now();
|
||||
tree.apply_mutations(mutations).unwrap();
|
||||
let apply_elapsed = now.elapsed();
|
||||
|
||||
println!(
|
||||
"An average batch computation time measured by a 1k-batch into an SMT with {} key-value pairs over {:.3} milliseconds is {:.3} milliseconds",
|
||||
size,
|
||||
compute_elapsed.as_secs_f32() * 1000f32,
|
||||
// Dividing by the number of iterations, 1000, and then multiplying by 1000 to get
|
||||
// milliseconds, cancels out.
|
||||
compute_elapsed.as_secs_f32(),
|
||||
);
|
||||
|
||||
println!(
|
||||
"An average batch application time measured by a 1k-batch into an SMT with {} key-value pairs over {:.3} milliseconds is {:.3} milliseconds",
|
||||
size,
|
||||
apply_elapsed.as_secs_f32() * 1000f32,
|
||||
// Dividing by the number of iterations, 1000, and then multiplying by 1000 to get
|
||||
// milliseconds, cancels out.
|
||||
apply_elapsed.as_secs_f32(),
|
||||
);
|
||||
|
||||
println!(
|
||||
"An average batch insertion time measured by a 1k-batch into an SMT with {} key-value pairs totals to {:.3} milliseconds",
|
||||
size,
|
||||
(compute_elapsed + apply_elapsed).as_secs_f32() * 1000f32,
|
||||
);
|
||||
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the proof generation benchmark for the [`Smt`].
|
||||
pub fn proof_generation(tree: &mut Smt, size: u64) -> Result<(), MerkleError> {
|
||||
println!("Running a proof generation benchmark:");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::slice;
|
||||
|
||||
use super::{Felt, RpoDigest, EMPTY_WORD};
|
||||
use super::{smt::InnerNode, Felt, RpoDigest, EMPTY_WORD};
|
||||
|
||||
// EMPTY NODES SUBTREES
|
||||
// ================================================================================================
|
||||
@@ -25,6 +25,17 @@ impl EmptySubtreeRoots {
|
||||
let pos = 255 - tree_depth + node_depth;
|
||||
&EMPTY_SUBTREES[pos as usize]
|
||||
}
|
||||
|
||||
/// Returns a sparse Merkle tree [`InnerNode`] with two empty children.
|
||||
///
|
||||
/// # Note
|
||||
/// `node_depth` is the depth of the **parent** to have empty children. That is, `node_depth`
|
||||
/// and the depth of the returned [`InnerNode`] are the same, and thus the empty hashes are for
|
||||
/// subtrees of depth `node_depth + 1`.
|
||||
pub(crate) const fn get_inner_node(tree_depth: u8, node_depth: u8) -> InnerNode {
|
||||
let &child = Self::entry(tree_depth, node_depth + 1);
|
||||
InnerNode { left: child, right: child }
|
||||
}
|
||||
}
|
||||
|
||||
const EMPTY_SUBTREES: [RpoDigest; 256] = [
|
||||
|
||||
@@ -33,22 +33,22 @@ impl fmt::Display for MerkleError {
|
||||
DuplicateValuesForKey(key) => write!(f, "multiple values provided for key {key}"),
|
||||
InvalidIndex { depth, value } => {
|
||||
write!(f, "the index value {value} is not valid for the depth {depth}")
|
||||
}
|
||||
},
|
||||
InvalidDepth { expected, provided } => {
|
||||
write!(f, "the provided depth {provided} is not valid for {expected}")
|
||||
}
|
||||
},
|
||||
InvalidSubtreeDepth { subtree_depth, tree_depth } => {
|
||||
write!(f, "tried inserting a subtree of depth {subtree_depth} into a tree of depth {tree_depth}")
|
||||
}
|
||||
},
|
||||
InvalidPath(_path) => write!(f, "the provided path is not valid"),
|
||||
InvalidNumEntries(max) => write!(f, "number of entries exceeded the maximum: {max}"),
|
||||
NodeNotInSet(index) => write!(f, "the node with index ({index}) is not in the set"),
|
||||
NodeNotInStore(hash, index) => {
|
||||
write!(f, "the node {hash:?} with index ({index}) is not in the store")
|
||||
}
|
||||
},
|
||||
NumLeavesNotPowerOfTwo(leaves) => {
|
||||
write!(f, "the leaves count {leaves} is not a power of 2")
|
||||
}
|
||||
},
|
||||
RootNotInStore(root) => write!(f, "the root {:?} is not in the store", root),
|
||||
SmtLeaf(smt_leaf_error) => write!(f, "smt leaf error: {smt_leaf_error}"),
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::{fmt, ops::Deref, slice};
|
||||
|
||||
use winter_math::log2;
|
||||
|
||||
use super::{InnerNodeInfo, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Word};
|
||||
use crate::utils::{uninit_vector, word_to_hex};
|
||||
|
||||
@@ -70,7 +68,7 @@ impl MerkleTree {
|
||||
///
|
||||
/// Merkle tree of depth 1 has two leaves, depth 2 has four leaves etc.
|
||||
pub fn depth(&self) -> u8 {
|
||||
log2(self.nodes.len() / 2) as u8
|
||||
(self.nodes.len() / 2).ilog2() as u8
|
||||
}
|
||||
|
||||
/// Returns a node at the specified depth and index value.
|
||||
@@ -213,7 +211,7 @@ pub struct InnerNodeIterator<'a> {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InnerNodeIterator<'a> {
|
||||
impl Iterator for InnerNodeIterator<'_> {
|
||||
type Item = InnerNodeInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::super::RpoDigest;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::super::RpoDigest;
|
||||
|
||||
/// Container for the update data of a [super::PartialMmr]
|
||||
#[derive(Debug)]
|
||||
pub struct MmrDelta {
|
||||
|
||||
@@ -9,6 +9,7 @@ pub enum MmrError {
|
||||
InvalidPosition(usize),
|
||||
InvalidPeaks,
|
||||
InvalidPeak,
|
||||
PeakOutOfBounds(usize, usize),
|
||||
InvalidUpdate,
|
||||
UnknownPeak,
|
||||
MerkleError(MerkleError),
|
||||
@@ -21,11 +22,16 @@ impl Display for MmrError {
|
||||
MmrError::InvalidPeaks => write!(fmt, "Invalid peaks count"),
|
||||
MmrError::InvalidPeak => {
|
||||
write!(fmt, "Peak values does not match merkle path computed root")
|
||||
}
|
||||
MmrError::InvalidUpdate => write!(fmt, "Invalid mmr update"),
|
||||
},
|
||||
MmrError::PeakOutOfBounds(peak_idx, peaks_len) => write!(
|
||||
fmt,
|
||||
"Requested peak index is {} but the number of peaks is {}",
|
||||
peak_idx, peaks_len
|
||||
),
|
||||
MmrError::InvalidUpdate => write!(fmt, "Invalid Mmr update"),
|
||||
MmrError::UnknownPeak => {
|
||||
write!(fmt, "Peak not in Mmr")
|
||||
}
|
||||
},
|
||||
MmrError::MerkleError(err) => write!(fmt, "{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
//!
|
||||
//! Additionally the structure only supports adding leaves to the right-most tree, the one with the
|
||||
//! least number of leaves. The structure preserves the invariant that each tree has different
|
||||
//! depths, i.e. as part of adding adding a new element to the forest the trees with same depth are
|
||||
//! depths, i.e. as part of adding a new element to the forest the trees with same depth are
|
||||
//! merged, creating a new tree with depth d+1, this process is continued until the property is
|
||||
//! reestablished.
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{
|
||||
super::{InnerNodeInfo, MerklePath},
|
||||
bit::TrueBitPositionIterator,
|
||||
leaf_to_corresponding_tree, nodes_in_forest, MmrDelta, MmrError, MmrPeaks, MmrProof, Rpo256,
|
||||
RpoDigest,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// MMR
|
||||
// ===============================================================================================
|
||||
@@ -72,19 +73,36 @@ impl Mmr {
|
||||
// FUNCTIONALITY
|
||||
// ============================================================================================
|
||||
|
||||
/// Given a leaf position, returns the Merkle path to its corresponding peak. If the position
|
||||
/// is greater-or-equal than the tree size an error is returned.
|
||||
/// Returns an [MmrProof] for the leaf at the specified position.
|
||||
///
|
||||
/// Note: The leaf position is the 0-indexed number corresponding to the order the leaves were
|
||||
/// added, this corresponds to the MMR size _prior_ to adding the element. So the 1st element
|
||||
/// has position 0, the second position 1, and so on.
|
||||
pub fn open(&self, pos: usize, target_forest: usize) -> Result<MmrProof, MmrError> {
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the specified leaf position is out of bounds for this MMR.
|
||||
pub fn open(&self, pos: usize) -> Result<MmrProof, MmrError> {
|
||||
self.open_at(pos, self.forest)
|
||||
}
|
||||
|
||||
/// Returns an [MmrProof] for the leaf at the specified position using the state of the MMR
|
||||
/// at the specified `forest`.
|
||||
///
|
||||
/// Note: The leaf position is the 0-indexed number corresponding to the order the leaves were
|
||||
/// added, this corresponds to the MMR size _prior_ to adding the element. So the 1st element
|
||||
/// has position 0, the second position 1, and so on.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - The specified leaf position is out of bounds for this MMR.
|
||||
/// - The specified `forest` value is not valid for this MMR.
|
||||
pub fn open_at(&self, pos: usize, forest: usize) -> Result<MmrProof, MmrError> {
|
||||
// find the target tree responsible for the MMR position
|
||||
let tree_bit =
|
||||
leaf_to_corresponding_tree(pos, target_forest).ok_or(MmrError::InvalidPosition(pos))?;
|
||||
leaf_to_corresponding_tree(pos, forest).ok_or(MmrError::InvalidPosition(pos))?;
|
||||
|
||||
// isolate the trees before the target
|
||||
let forest_before = target_forest & high_bitmask(tree_bit + 1);
|
||||
let forest_before = forest & high_bitmask(tree_bit + 1);
|
||||
let index_offset = nodes_in_forest(forest_before);
|
||||
|
||||
// update the value position from global to the target tree
|
||||
@@ -94,7 +112,7 @@ impl Mmr {
|
||||
let (_, path) = self.collect_merkle_path_and_value(tree_bit, relative_pos, index_offset);
|
||||
|
||||
Ok(MmrProof {
|
||||
forest: target_forest,
|
||||
forest,
|
||||
position: pos,
|
||||
merkle_path: MerklePath::new(path),
|
||||
})
|
||||
@@ -145,8 +163,16 @@ impl Mmr {
|
||||
self.forest += 1;
|
||||
}
|
||||
|
||||
/// Returns an peaks of the MMR for the version specified by `forest`.
|
||||
pub fn peaks(&self, forest: usize) -> Result<MmrPeaks, MmrError> {
|
||||
/// Returns the current peaks of the MMR.
|
||||
pub fn peaks(&self) -> MmrPeaks {
|
||||
self.peaks_at(self.forest).expect("failed to get peaks at current forest")
|
||||
}
|
||||
|
||||
/// Returns the peaks of the MMR at the state specified by `forest`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the specified `forest` value is not valid for this MMR.
|
||||
pub fn peaks_at(&self, forest: usize) -> Result<MmrPeaks, MmrError> {
|
||||
if forest > self.forest {
|
||||
return Err(MmrError::InvalidPeaks);
|
||||
}
|
||||
@@ -344,7 +370,7 @@ pub struct MmrNodes<'a> {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MmrNodes<'a> {
|
||||
impl Iterator for MmrNodes<'_> {
|
||||
type Item = InnerNodeInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -377,7 +403,8 @@ impl<'a> Iterator for MmrNodes<'a> {
|
||||
// the next parent position is one above the position of the pair
|
||||
let parent = self.last_right << 1;
|
||||
|
||||
// the left node has been paired and the current parent yielded, removed it from the forest
|
||||
// the left node has been paired and the current parent yielded, removed it from the
|
||||
// forest
|
||||
self.forest ^= self.last_right;
|
||||
if self.forest & parent == 0 {
|
||||
// this iteration yielded the left parent node
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
//! leaves count.
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
// IN-ORDER INDEX
|
||||
// ================================================================================================
|
||||
|
||||
@@ -112,6 +114,21 @@ impl InOrderIndex {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for InOrderIndex {
|
||||
fn write_into<W: winter_utils::ByteWriter>(&self, target: &mut W) {
|
||||
target.write_usize(self.idx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for InOrderIndex {
|
||||
fn read_from<R: winter_utils::ByteReader>(
|
||||
source: &mut R,
|
||||
) -> Result<Self, winter_utils::DeserializationError> {
|
||||
let idx = source.read_usize()?;
|
||||
Ok(InOrderIndex { idx })
|
||||
}
|
||||
}
|
||||
|
||||
// CONVERSIONS FROM IN-ORDER INDEX
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -127,6 +144,7 @@ impl From<InOrderIndex> for u64 {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proptest::prelude::*;
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
use super::InOrderIndex;
|
||||
|
||||
@@ -162,4 +180,12 @@ mod test {
|
||||
assert_eq!(left.sibling(), right);
|
||||
assert_eq!(left, right.sibling());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inorder_index_serialization() {
|
||||
let index = InOrderIndex::from_leaf_pos(5);
|
||||
let bytes = index.to_bytes();
|
||||
let index2 = InOrderIndex::read_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(index, index2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ mod proof;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use super::{Felt, Rpo256, RpoDigest, Word};
|
||||
|
||||
// REEXPORTS
|
||||
// ================================================================================================
|
||||
pub use delta::MmrDelta;
|
||||
@@ -22,6 +20,8 @@ pub use partial::PartialMmr;
|
||||
pub use peaks::MmrPeaks;
|
||||
pub use proof::MmrProof;
|
||||
|
||||
use super::{Felt, Rpo256, RpoDigest, Word};
|
||||
|
||||
// UTILITIES
|
||||
// ===============================================================================================
|
||||
|
||||
@@ -42,8 +42,8 @@ const fn leaf_to_corresponding_tree(pos: usize, forest: usize) -> Option<u32> {
|
||||
// - this means the first tree owns from `0` up to the `2^k_0` first positions, where `k_0`
|
||||
// is the highest true bit position, the second tree from `2^k_0 + 1` up to `2^k_1` where
|
||||
// `k_1` is the second highest bit, so on.
|
||||
// - this means the highest bits work as a category marker, and the position is owned by
|
||||
// the first tree which doesn't share a high bit with the position
|
||||
// - this means the highest bits work as a category marker, and the position is owned by the
|
||||
// first tree which doesn't share a high bit with the position
|
||||
let before = forest & pos;
|
||||
let after = forest ^ before;
|
||||
let tree = after.ilog2();
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
use super::{MmrDelta, MmrProof, Rpo256, RpoDigest};
|
||||
use crate::merkle::{
|
||||
mmr::{leaf_to_corresponding_tree, nodes_in_forest},
|
||||
InOrderIndex, InnerNodeInfo, MerklePath, MmrError, MmrPeaks,
|
||||
};
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
// TYPE ALIASES
|
||||
// ================================================================================================
|
||||
@@ -183,7 +186,7 @@ impl PartialMmr {
|
||||
pub fn inner_nodes<'a, I: Iterator<Item = (usize, RpoDigest)> + 'a>(
|
||||
&'a self,
|
||||
mut leaves: I,
|
||||
) -> impl Iterator<Item = InnerNodeInfo> + '_ {
|
||||
) -> impl Iterator<Item = InnerNodeInfo> + 'a {
|
||||
let stack = if let Some((pos, leaf)) = leaves.next() {
|
||||
let idx = InOrderIndex::from_leaf_pos(pos);
|
||||
vec![(idx, leaf)]
|
||||
@@ -536,7 +539,7 @@ pub struct InnerNodeIterator<'a, I: Iterator<Item = (usize, RpoDigest)>> {
|
||||
seen_nodes: BTreeSet<InOrderIndex>,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = (usize, RpoDigest)>> Iterator for InnerNodeIterator<'a, I> {
|
||||
impl<I: Iterator<Item = (usize, RpoDigest)>> Iterator for InnerNodeIterator<'_, I> {
|
||||
type Item = InnerNodeInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -572,6 +575,28 @@ impl<'a, I: Iterator<Item = (usize, RpoDigest)>> Iterator for InnerNodeIterator<
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for PartialMmr {
|
||||
fn write_into<W: winter_utils::ByteWriter>(&self, target: &mut W) {
|
||||
self.forest.write_into(target);
|
||||
self.peaks.write_into(target);
|
||||
self.nodes.write_into(target);
|
||||
target.write_bool(self.track_latest);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for PartialMmr {
|
||||
fn read_from<R: winter_utils::ByteReader>(
|
||||
source: &mut R,
|
||||
) -> Result<Self, winter_utils::DeserializationError> {
|
||||
let forest = usize::read_from(source)?;
|
||||
let peaks = Vec::<RpoDigest>::read_from(source)?;
|
||||
let nodes = NodeMap::read_from(source)?;
|
||||
let track_latest = source.read_bool()?;
|
||||
|
||||
Ok(Self { forest, peaks, nodes, track_latest })
|
||||
}
|
||||
}
|
||||
|
||||
// UTILS
|
||||
// ================================================================================================
|
||||
|
||||
@@ -613,12 +638,15 @@ fn forest_to_rightmost_index(forest: usize) -> InOrderIndex {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
use super::{
|
||||
forest_to_rightmost_index, forest_to_root_index, InOrderIndex, MmrPeaks, PartialMmr,
|
||||
RpoDigest,
|
||||
};
|
||||
use crate::merkle::{int_to_node, MerkleStore, Mmr, NodeIndex};
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
|
||||
const LEAVES: [RpoDigest; 7] = [
|
||||
int_to_node(0),
|
||||
@@ -688,18 +716,18 @@ mod tests {
|
||||
// build an MMR with 10 nodes (2 peaks) and a partial MMR based on it
|
||||
let mut mmr = Mmr::default();
|
||||
(0..10).for_each(|i| mmr.add(int_to_node(i)));
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks(mmr.forest()).unwrap().into();
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks().into();
|
||||
|
||||
// add authentication path for position 1 and 8
|
||||
{
|
||||
let node = mmr.get(1).unwrap();
|
||||
let proof = mmr.open(1, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(1).unwrap();
|
||||
partial_mmr.track(1, node, &proof.merkle_path).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let node = mmr.get(8).unwrap();
|
||||
let proof = mmr.open(8, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(8).unwrap();
|
||||
partial_mmr.track(8, node, &proof.merkle_path).unwrap();
|
||||
}
|
||||
|
||||
@@ -712,7 +740,7 @@ mod tests {
|
||||
validate_apply_delta(&mmr, &mut partial_mmr);
|
||||
{
|
||||
let node = mmr.get(12).unwrap();
|
||||
let proof = mmr.open(12, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(12).unwrap();
|
||||
partial_mmr.track(12, node, &proof.merkle_path).unwrap();
|
||||
assert!(partial_mmr.track_latest);
|
||||
}
|
||||
@@ -737,7 +765,7 @@ mod tests {
|
||||
let nodes_delta = partial.apply(delta).unwrap();
|
||||
|
||||
// new peaks were computed correctly
|
||||
assert_eq!(mmr.peaks(mmr.forest()).unwrap(), partial.peaks());
|
||||
assert_eq!(mmr.peaks(), partial.peaks());
|
||||
|
||||
let mut expected_nodes = nodes_before;
|
||||
for (key, value) in nodes_delta {
|
||||
@@ -753,7 +781,7 @@ mod tests {
|
||||
let index_value: u64 = index.into();
|
||||
let pos = index_value / 2;
|
||||
let proof1 = partial.open(pos as usize).unwrap().unwrap();
|
||||
let proof2 = mmr.open(pos as usize, mmr.forest()).unwrap();
|
||||
let proof2 = mmr.open(pos as usize).unwrap();
|
||||
assert_eq!(proof1, proof2);
|
||||
}
|
||||
}
|
||||
@@ -762,16 +790,16 @@ mod tests {
|
||||
fn test_partial_mmr_inner_nodes_iterator() {
|
||||
// build the MMR
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let first_peak = mmr.peaks(mmr.forest).unwrap().peaks()[0];
|
||||
let first_peak = mmr.peaks().peaks()[0];
|
||||
|
||||
// -- test single tree ----------------------------
|
||||
|
||||
// get path and node for position 1
|
||||
let node1 = mmr.get(1).unwrap();
|
||||
let proof1 = mmr.open(1, mmr.forest()).unwrap();
|
||||
let proof1 = mmr.open(1).unwrap();
|
||||
|
||||
// create partial MMR and add authentication path to node at position 1
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks(mmr.forest()).unwrap().into();
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks().into();
|
||||
partial_mmr.track(1, node1, &proof1.merkle_path).unwrap();
|
||||
|
||||
// empty iterator should have no nodes
|
||||
@@ -789,13 +817,13 @@ mod tests {
|
||||
// -- test no duplicates --------------------------
|
||||
|
||||
// build the partial MMR
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks(mmr.forest()).unwrap().into();
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks().into();
|
||||
|
||||
let node0 = mmr.get(0).unwrap();
|
||||
let proof0 = mmr.open(0, mmr.forest()).unwrap();
|
||||
let proof0 = mmr.open(0).unwrap();
|
||||
|
||||
let node2 = mmr.get(2).unwrap();
|
||||
let proof2 = mmr.open(2, mmr.forest()).unwrap();
|
||||
let proof2 = mmr.open(2).unwrap();
|
||||
|
||||
partial_mmr.track(0, node0, &proof0.merkle_path).unwrap();
|
||||
partial_mmr.track(1, node1, &proof1.merkle_path).unwrap();
|
||||
@@ -826,10 +854,10 @@ mod tests {
|
||||
// -- test multiple trees -------------------------
|
||||
|
||||
// build the partial MMR
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks(mmr.forest()).unwrap().into();
|
||||
let mut partial_mmr: PartialMmr = mmr.peaks().into();
|
||||
|
||||
let node5 = mmr.get(5).unwrap();
|
||||
let proof5 = mmr.open(5, mmr.forest()).unwrap();
|
||||
let proof5 = mmr.open(5).unwrap();
|
||||
|
||||
partial_mmr.track(1, node1, &proof1.merkle_path).unwrap();
|
||||
partial_mmr.track(5, node5, &proof5.merkle_path).unwrap();
|
||||
@@ -841,7 +869,7 @@ mod tests {
|
||||
let index1 = NodeIndex::new(2, 1).unwrap();
|
||||
let index5 = NodeIndex::new(1, 1).unwrap();
|
||||
|
||||
let second_peak = mmr.peaks(mmr.forest).unwrap().peaks()[1];
|
||||
let second_peak = mmr.peaks().peaks()[1];
|
||||
|
||||
let path1 = store.get_path(first_peak, index1).unwrap().path;
|
||||
let path5 = store.get_path(second_peak, index5).unwrap().path;
|
||||
@@ -860,8 +888,7 @@ mod tests {
|
||||
mmr.add(el);
|
||||
partial_mmr.add(el, false);
|
||||
|
||||
let mmr_peaks = mmr.peaks(mmr.forest()).unwrap();
|
||||
assert_eq!(mmr_peaks, partial_mmr.peaks());
|
||||
assert_eq!(mmr.peaks(), partial_mmr.peaks());
|
||||
assert_eq!(mmr.forest(), partial_mmr.forest());
|
||||
}
|
||||
}
|
||||
@@ -877,12 +904,11 @@ mod tests {
|
||||
mmr.add(el);
|
||||
partial_mmr.add(el, true);
|
||||
|
||||
let mmr_peaks = mmr.peaks(mmr.forest()).unwrap();
|
||||
assert_eq!(mmr_peaks, partial_mmr.peaks());
|
||||
assert_eq!(mmr.peaks(), partial_mmr.peaks());
|
||||
assert_eq!(mmr.forest(), partial_mmr.forest());
|
||||
|
||||
for pos in 0..i {
|
||||
let mmr_proof = mmr.open(pos as usize, mmr.forest()).unwrap();
|
||||
let mmr_proof = mmr.open(pos as usize).unwrap();
|
||||
let partialmmr_proof = partial_mmr.open(pos as usize).unwrap().unwrap();
|
||||
assert_eq!(mmr_proof, partialmmr_proof);
|
||||
}
|
||||
@@ -894,8 +920,8 @@ mod tests {
|
||||
let mut mmr = Mmr::from((0..7).map(int_to_node));
|
||||
|
||||
// derive a partial Mmr from it which tracks authentication path to leaf 5
|
||||
let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks(mmr.forest()).unwrap());
|
||||
let path_to_5 = mmr.open(5, mmr.forest()).unwrap().merkle_path;
|
||||
let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
|
||||
let path_to_5 = mmr.open(5).unwrap().merkle_path;
|
||||
let leaf_at_5 = mmr.get(5).unwrap();
|
||||
partial_mmr.track(5, leaf_at_5, &path_to_5).unwrap();
|
||||
|
||||
@@ -905,6 +931,17 @@ mod tests {
|
||||
partial_mmr.add(leaf_at_7, false);
|
||||
|
||||
// the openings should be the same
|
||||
assert_eq!(mmr.open(5, mmr.forest()).unwrap(), partial_mmr.open(5).unwrap().unwrap());
|
||||
assert_eq!(mmr.open(5).unwrap(), partial_mmr.open(5).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_mmr_serialization() {
|
||||
let mmr = Mmr::from((0..7).map(int_to_node));
|
||||
let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
|
||||
|
||||
let bytes = partial_mmr.to_bytes();
|
||||
let decoded = PartialMmr::read_from_bytes(&bytes).unwrap();
|
||||
|
||||
assert_eq!(partial_mmr, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{super::ZERO, Felt, MmrError, MmrProof, Rpo256, RpoDigest, Word};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{super::ZERO, Felt, MmrError, MmrProof, Rpo256, RpoDigest, Word};
|
||||
|
||||
// MMR PEAKS
|
||||
// ================================================================================================
|
||||
|
||||
@@ -18,12 +19,12 @@ pub struct MmrPeaks {
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// - With 5 leaves, the binary `0b101`. The number of set bits is equal the number
|
||||
/// of peaks, in this case there are 2 peaks. The 0-indexed least-significant position of
|
||||
/// the bit determines the number of elements of a tree, so the rightmost tree has `2**0`
|
||||
/// elements and the left most has `2**2`.
|
||||
/// - With 12 leaves, the binary is `0b1100`, this case also has 2 peaks, the
|
||||
/// leftmost tree has `2**3=8` elements, and the right most has `2**2=4` elements.
|
||||
/// - With 5 leaves, the binary `0b101`. The number of set bits is equal the number of peaks,
|
||||
/// in this case there are 2 peaks. The 0-indexed least-significant position of the bit
|
||||
/// determines the number of elements of a tree, so the rightmost tree has `2**0` elements
|
||||
/// and the left most has `2**2`.
|
||||
/// - With 12 leaves, the binary is `0b1100`, this case also has 2 peaks, the leftmost tree has
|
||||
/// `2**3=8` elements, and the right most has `2**2=4` elements.
|
||||
num_leaves: usize,
|
||||
|
||||
/// All the peaks of every tree in the MMR forest. The peaks are always ordered by number of
|
||||
@@ -68,6 +69,17 @@ impl MmrPeaks {
|
||||
&self.peaks
|
||||
}
|
||||
|
||||
/// Returns the peak by the provided index.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the provided peak index is greater or equal to the current number of
|
||||
/// peaks in the Mmr.
|
||||
pub fn get_peak(&self, peak_idx: usize) -> Result<&RpoDigest, MmrError> {
|
||||
self.peaks
|
||||
.get(peak_idx)
|
||||
.ok_or(MmrError::PeakOutOfBounds(peak_idx, self.peaks.len()))
|
||||
}
|
||||
|
||||
/// Converts this [MmrPeaks] into its components: number of leaves and a vector of peaks of
|
||||
/// the underlying MMR.
|
||||
pub fn into_parts(self) -> (usize, Vec<RpoDigest>) {
|
||||
@@ -83,9 +95,18 @@ impl MmrPeaks {
|
||||
Rpo256::hash_elements(&self.flatten_and_pad_peaks())
|
||||
}
|
||||
|
||||
pub fn verify(&self, value: RpoDigest, opening: MmrProof) -> bool {
|
||||
let root = &self.peaks[opening.peak_index()];
|
||||
opening.merkle_path.verify(opening.relative_pos() as u64, value, root)
|
||||
/// Verifies the Merkle opening proof.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - provided opening proof is invalid.
|
||||
/// - Mmr root value computed using the provided leaf value differs from the actual one.
|
||||
pub fn verify(&self, value: RpoDigest, opening: MmrProof) -> Result<(), MmrError> {
|
||||
let root = self.get_peak(opening.peak_index())?;
|
||||
opening
|
||||
.merkle_path
|
||||
.verify(opening.relative_pos() as u64, value, root)
|
||||
.map_err(MmrError::MerkleError)
|
||||
}
|
||||
|
||||
/// Flattens and pads the peaks to make hashing inside of the Miden VM easier.
|
||||
@@ -94,16 +115,15 @@ impl MmrPeaks {
|
||||
/// - Flatten the vector of Words into a vector of Felts.
|
||||
/// - Pad the peaks with ZERO to an even number of words, this removes the need to handle RPO
|
||||
/// padding.
|
||||
/// - Pad the peaks to a minimum length of 16 words, which reduces the constant cost of
|
||||
/// hashing.
|
||||
/// - Pad the peaks to a minimum length of 16 words, which reduces the constant cost of hashing.
|
||||
pub fn flatten_and_pad_peaks(&self) -> Vec<Felt> {
|
||||
let num_peaks = self.peaks.len();
|
||||
|
||||
// To achieve the padding rules above we calculate the length of the final vector.
|
||||
// This is calculated as the number of field elements. Each peak is 4 field elements.
|
||||
// The length is calculated as follows:
|
||||
// - If there are less than 16 peaks, the data is padded to 16 peaks and as such requires
|
||||
// 64 field elements.
|
||||
// - If there are less than 16 peaks, the data is padded to 16 peaks and as such requires 64
|
||||
// field elements.
|
||||
// - If there are more than 16 peaks and the number of peaks is odd, the data is padded to
|
||||
// an even number of peaks and as such requires `(num_peaks + 1) * 4` field elements.
|
||||
// - If there are more than 16 peaks and the number of peaks is even, the data is not padded
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{
|
||||
super::{InnerNodeInfo, Rpo256, RpoDigest},
|
||||
bit::TrueBitPositionIterator,
|
||||
@@ -8,7 +10,6 @@ use crate::{
|
||||
merkle::{int_to_node, InOrderIndex, MerklePath, MerkleTree, MmrProof, NodeIndex},
|
||||
Felt, Word,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[test]
|
||||
fn test_position_equal_or_higher_than_leafs_is_never_contained() {
|
||||
@@ -138,7 +139,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 1);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 1);
|
||||
assert_eq!(acc.peaks(), &[postorder[0]]);
|
||||
|
||||
@@ -147,7 +148,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 3);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 2);
|
||||
assert_eq!(acc.peaks(), &[postorder[2]]);
|
||||
|
||||
@@ -156,7 +157,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 4);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 3);
|
||||
assert_eq!(acc.peaks(), &[postorder[2], postorder[3]]);
|
||||
|
||||
@@ -165,7 +166,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 7);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 4);
|
||||
assert_eq!(acc.peaks(), &[postorder[6]]);
|
||||
|
||||
@@ -174,7 +175,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 8);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 5);
|
||||
assert_eq!(acc.peaks(), &[postorder[6], postorder[7]]);
|
||||
|
||||
@@ -183,7 +184,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 10);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 6);
|
||||
assert_eq!(acc.peaks(), &[postorder[6], postorder[9]]);
|
||||
|
||||
@@ -192,7 +193,7 @@ fn test_mmr_simple() {
|
||||
assert_eq!(mmr.nodes.len(), 11);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
assert_eq!(acc.num_leaves(), 7);
|
||||
assert_eq!(acc.peaks(), &[postorder[6], postorder[9], postorder[10]]);
|
||||
}
|
||||
@@ -204,97 +205,73 @@ fn test_mmr_open() {
|
||||
let h23 = merge(LEAVES[2], LEAVES[3]);
|
||||
|
||||
// node at pos 7 is the root
|
||||
assert!(
|
||||
mmr.open(7, mmr.forest()).is_err(),
|
||||
"Element 7 is not in the tree, result should be None"
|
||||
);
|
||||
assert!(mmr.open(7).is_err(), "Element 7 is not in the tree, result should be None");
|
||||
|
||||
// node at pos 6 is the root
|
||||
let empty: MerklePath = MerklePath::new(vec![]);
|
||||
let opening = mmr
|
||||
.open(6, mmr.forest())
|
||||
.open(6)
|
||||
.expect("Element 6 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, empty);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 6);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[6], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[6], opening).unwrap();
|
||||
|
||||
// nodes 4,5 are depth 1
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[4]]);
|
||||
let opening = mmr
|
||||
.open(5, mmr.forest())
|
||||
.open(5)
|
||||
.expect("Element 5 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 5);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[5], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[5], opening).unwrap();
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[5]]);
|
||||
let opening = mmr
|
||||
.open(4, mmr.forest())
|
||||
.open(4)
|
||||
.expect("Element 4 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 4);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[4], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[4], opening).unwrap();
|
||||
|
||||
// nodes 0,1,2,3 are detph 2
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[2], h01]);
|
||||
let opening = mmr
|
||||
.open(3, mmr.forest())
|
||||
.open(3)
|
||||
.expect("Element 3 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 3);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[3], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[3], opening).unwrap();
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[3], h01]);
|
||||
let opening = mmr
|
||||
.open(2, mmr.forest())
|
||||
.open(2)
|
||||
.expect("Element 2 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 2);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[2], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[2], opening).unwrap();
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[0], h23]);
|
||||
let opening = mmr
|
||||
.open(1, mmr.forest())
|
||||
.open(1)
|
||||
.expect("Element 1 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 1);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[1], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[1], opening).unwrap();
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[1], h23]);
|
||||
let opening = mmr
|
||||
.open(0, mmr.forest())
|
||||
.open(0)
|
||||
.expect("Element 0 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 0);
|
||||
assert!(
|
||||
mmr.peaks(mmr.forest()).unwrap().verify(LEAVES[0], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
mmr.peaks().verify(LEAVES[0], opening).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -308,7 +285,7 @@ fn test_mmr_open_older_version() {
|
||||
// merkle path of a node is empty if there are no elements to pair with it
|
||||
for pos in (0..mmr.forest()).filter(is_even) {
|
||||
let forest = pos + 1;
|
||||
let proof = mmr.open(pos, forest).unwrap();
|
||||
let proof = mmr.open_at(pos, forest).unwrap();
|
||||
assert_eq!(proof.forest, forest);
|
||||
assert_eq!(proof.merkle_path.nodes(), []);
|
||||
assert_eq!(proof.position, pos);
|
||||
@@ -320,7 +297,7 @@ fn test_mmr_open_older_version() {
|
||||
for pos in 0..4 {
|
||||
let idx = NodeIndex::new(2, pos).unwrap();
|
||||
let path = mtree.get_path(idx).unwrap();
|
||||
let proof = mmr.open(pos as usize, forest).unwrap();
|
||||
let proof = mmr.open_at(pos as usize, forest).unwrap();
|
||||
assert_eq!(path, proof.merkle_path);
|
||||
}
|
||||
}
|
||||
@@ -331,7 +308,7 @@ fn test_mmr_open_older_version() {
|
||||
let path = mtree.get_path(idx).unwrap();
|
||||
// account for the bigger tree with 4 elements
|
||||
let mmr_pos = (pos + 4) as usize;
|
||||
let proof = mmr.open(mmr_pos, forest).unwrap();
|
||||
let proof = mmr.open_at(mmr_pos, forest).unwrap();
|
||||
assert_eq!(path, proof.merkle_path);
|
||||
}
|
||||
}
|
||||
@@ -357,49 +334,49 @@ fn test_mmr_open_eight() {
|
||||
let root = mtree.root();
|
||||
|
||||
let position = 0;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 1;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 2;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 3;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 4;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 5;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 6;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
|
||||
let position = 7;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path = mtree.get_path(NodeIndex::new(3, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(position as u64, leaves[position]).unwrap(), root);
|
||||
@@ -415,47 +392,47 @@ fn test_mmr_open_seven() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
|
||||
let position = 0;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath =
|
||||
mtree1.get_path(NodeIndex::new(2, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(0, LEAVES[0]).unwrap(), mtree1.root());
|
||||
|
||||
let position = 1;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath =
|
||||
mtree1.get_path(NodeIndex::new(2, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(1, LEAVES[1]).unwrap(), mtree1.root());
|
||||
|
||||
let position = 2;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath =
|
||||
mtree1.get_path(NodeIndex::new(2, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(2, LEAVES[2]).unwrap(), mtree1.root());
|
||||
|
||||
let position = 3;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath =
|
||||
mtree1.get_path(NodeIndex::new(2, position as u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(3, LEAVES[3]).unwrap(), mtree1.root());
|
||||
|
||||
let position = 4;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath = mtree2.get_path(NodeIndex::new(1, 0u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(0, LEAVES[4]).unwrap(), mtree2.root());
|
||||
|
||||
let position = 5;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath = mtree2.get_path(NodeIndex::new(1, 1u64).unwrap()).unwrap();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(1, LEAVES[5]).unwrap(), mtree2.root());
|
||||
|
||||
let position = 6;
|
||||
let proof = mmr.open(position, mmr.forest()).unwrap();
|
||||
let proof = mmr.open(position).unwrap();
|
||||
let merkle_path: MerklePath = [].as_ref().into();
|
||||
assert_eq!(proof, MmrProof { forest, position, merkle_path });
|
||||
assert_eq!(proof.merkle_path.compute_root(0, LEAVES[6]).unwrap(), LEAVES[6]);
|
||||
@@ -479,7 +456,7 @@ fn test_mmr_invariants() {
|
||||
let mut mmr = Mmr::new();
|
||||
for v in 1..=1028 {
|
||||
mmr.add(int_to_node(v));
|
||||
let accumulator = mmr.peaks(mmr.forest()).unwrap();
|
||||
let accumulator = mmr.peaks();
|
||||
assert_eq!(v as usize, mmr.forest(), "MMR leaf count must increase by one on every add");
|
||||
assert_eq!(
|
||||
v as usize,
|
||||
@@ -565,37 +542,37 @@ fn test_mmr_peaks() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
|
||||
let forest = 0b0001;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[0]]);
|
||||
|
||||
let forest = 0b0010;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[2]]);
|
||||
|
||||
let forest = 0b0011;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[2], mmr.nodes[3]]);
|
||||
|
||||
let forest = 0b0100;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[6]]);
|
||||
|
||||
let forest = 0b0101;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[6], mmr.nodes[7]]);
|
||||
|
||||
let forest = 0b0110;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[6], mmr.nodes[9]]);
|
||||
|
||||
let forest = 0b0111;
|
||||
let acc = mmr.peaks(forest).unwrap();
|
||||
let acc = mmr.peaks_at(forest).unwrap();
|
||||
assert_eq!(acc.num_leaves(), forest);
|
||||
assert_eq!(acc.peaks(), &[mmr.nodes[6], mmr.nodes[9], mmr.nodes[10]]);
|
||||
}
|
||||
@@ -603,7 +580,7 @@ fn test_mmr_peaks() {
|
||||
#[test]
|
||||
fn test_mmr_hash_peaks() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let peaks = mmr.peaks(mmr.forest()).unwrap();
|
||||
let peaks = mmr.peaks();
|
||||
|
||||
let first_peak = Rpo256::merge(&[
|
||||
Rpo256::merge(&[LEAVES[0], LEAVES[1]]),
|
||||
@@ -657,7 +634,7 @@ fn test_mmr_peaks_hash_odd() {
|
||||
#[test]
|
||||
fn test_mmr_delta() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
|
||||
// original_forest can't have more elements
|
||||
assert!(
|
||||
@@ -757,7 +734,7 @@ fn test_mmr_delta_old_forest() {
|
||||
#[test]
|
||||
fn test_partial_mmr_simple() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let peaks = mmr.peaks(mmr.forest()).unwrap();
|
||||
let peaks = mmr.peaks();
|
||||
let mut partial: PartialMmr = peaks.clone().into();
|
||||
|
||||
// check initial state of the partial mmr
|
||||
@@ -768,7 +745,7 @@ fn test_partial_mmr_simple() {
|
||||
assert_eq!(partial.nodes.len(), 0);
|
||||
|
||||
// check state after adding tracking one element
|
||||
let proof1 = mmr.open(0, mmr.forest()).unwrap();
|
||||
let proof1 = mmr.open(0).unwrap();
|
||||
let el1 = mmr.get(proof1.position).unwrap();
|
||||
partial.track(proof1.position, el1, &proof1.merkle_path).unwrap();
|
||||
|
||||
@@ -780,7 +757,7 @@ fn test_partial_mmr_simple() {
|
||||
let idx = idx.parent();
|
||||
assert_eq!(partial.nodes[&idx.sibling()], proof1.merkle_path[1]);
|
||||
|
||||
let proof2 = mmr.open(1, mmr.forest()).unwrap();
|
||||
let proof2 = mmr.open(1).unwrap();
|
||||
let el2 = mmr.get(proof2.position).unwrap();
|
||||
partial.track(proof2.position, el2, &proof2.merkle_path).unwrap();
|
||||
|
||||
@@ -798,9 +775,9 @@ fn test_partial_mmr_update_single() {
|
||||
let mut full = Mmr::new();
|
||||
let zero = int_to_node(0);
|
||||
full.add(zero);
|
||||
let mut partial: PartialMmr = full.peaks(full.forest()).unwrap().into();
|
||||
let mut partial: PartialMmr = full.peaks().into();
|
||||
|
||||
let proof = full.open(0, full.forest()).unwrap();
|
||||
let proof = full.open(0).unwrap();
|
||||
partial.track(proof.position, zero, &proof.merkle_path).unwrap();
|
||||
|
||||
for i in 1..100 {
|
||||
@@ -810,9 +787,9 @@ fn test_partial_mmr_update_single() {
|
||||
partial.apply(delta).unwrap();
|
||||
|
||||
assert_eq!(partial.forest(), full.forest());
|
||||
assert_eq!(partial.peaks(), full.peaks(full.forest()).unwrap());
|
||||
assert_eq!(partial.peaks(), full.peaks());
|
||||
|
||||
let proof1 = full.open(i as usize, full.forest()).unwrap();
|
||||
let proof1 = full.open(i as usize).unwrap();
|
||||
partial.track(proof1.position, node, &proof1.merkle_path).unwrap();
|
||||
let proof2 = partial.open(proof1.position).unwrap().unwrap();
|
||||
assert_eq!(proof1.merkle_path, proof2.merkle_path);
|
||||
@@ -822,7 +799,7 @@ fn test_partial_mmr_update_single() {
|
||||
#[test]
|
||||
fn test_mmr_add_invalid_odd_leaf() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let acc = mmr.peaks(mmr.forest()).unwrap();
|
||||
let acc = mmr.peaks();
|
||||
let mut partial: PartialMmr = acc.clone().into();
|
||||
|
||||
let empty = MerklePath::new(Vec::new());
|
||||
@@ -837,6 +814,39 @@ fn test_mmr_add_invalid_odd_leaf() {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests that a proof whose peak count exceeds the peak count of the MMR returns an error.
|
||||
///
|
||||
/// Here we manipulate the proof to return a peak index of 1 while the MMR only has 1 peak (with
|
||||
/// index 0).
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_mmr_proof_num_peaks_exceeds_current_num_peaks() {
|
||||
let mmr: Mmr = LEAVES[0..4].iter().cloned().into();
|
||||
let mut proof = mmr.open(3).unwrap();
|
||||
proof.forest = 5;
|
||||
proof.position = 4;
|
||||
mmr.peaks().verify(LEAVES[3], proof).unwrap();
|
||||
}
|
||||
|
||||
/// Tests that a proof whose peak count exceeds the peak count of the MMR returns an error.
|
||||
///
|
||||
/// We create an MmrProof for a leaf whose peak index to verify against is 1.
|
||||
/// Then we add another leaf which results in an Mmr with just one peak due to trees
|
||||
/// being merged. If we try to use the old proof against the new Mmr, we should get an error.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_mmr_old_proof_num_peaks_exceeds_current_num_peaks() {
|
||||
let leaves_len = 3;
|
||||
let mut mmr = Mmr::from(LEAVES[0..leaves_len].iter().cloned());
|
||||
|
||||
let leaf_idx = leaves_len - 1;
|
||||
let proof = mmr.open(leaf_idx).unwrap();
|
||||
assert!(mmr.peaks().verify(LEAVES[leaf_idx], proof.clone()).is_ok());
|
||||
|
||||
mmr.add(LEAVES[leaves_len]);
|
||||
mmr.peaks().verify(LEAVES[leaf_idx], proof).unwrap();
|
||||
}
|
||||
|
||||
mod property_tests {
|
||||
use proptest::prelude::*;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ pub use path::{MerklePath, RootPath, ValuePath};
|
||||
|
||||
mod smt;
|
||||
pub use smt::{
|
||||
LeafIndex, SimpleSmt, Smt, SmtLeaf, SmtLeafError, SmtProof, SmtProofError, SMT_DEPTH,
|
||||
SMT_MAX_DEPTH, SMT_MIN_DEPTH,
|
||||
LeafIndex, MutationSet, SimpleSmt, Smt, SmtLeaf, SmtLeafError, SmtProof, SmtProofError,
|
||||
SMT_DEPTH, SMT_MAX_DEPTH, SMT_MIN_DEPTH,
|
||||
};
|
||||
|
||||
mod mmr;
|
||||
|
||||
@@ -214,7 +214,7 @@ impl PartialMerkleTree {
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - the specified index has depth set to 0 or the depth is greater than the depth of this
|
||||
/// Merkle tree.
|
||||
/// Merkle tree.
|
||||
/// - the specified index is not contained in the nodes map.
|
||||
pub fn get_path(&self, mut index: NodeIndex) -> Result<MerklePath, MerkleError> {
|
||||
if index.is_root() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use alloc::{collections::BTreeMap, vec::Vec};
|
||||
|
||||
use super::{
|
||||
super::{
|
||||
digests_to_words, int_to_node, DefaultMerkleStore as MerkleStore, MerkleTree, NodeIndex,
|
||||
@@ -5,7 +7,6 @@ use super::{
|
||||
},
|
||||
Deserializable, InnerNodeInfo, RpoDigest, Serializable, ValuePath,
|
||||
};
|
||||
use alloc::{collections::BTreeMap, vec::Vec};
|
||||
|
||||
// TEST DATA
|
||||
// ================================================================================================
|
||||
@@ -294,7 +295,8 @@ fn leaves() {
|
||||
assert!(expected_leaves.eq(pmt.leaves()));
|
||||
}
|
||||
|
||||
/// Checks that nodes of the PMT returned by `inner_nodes()` function are equal to the expected ones.
|
||||
/// Checks that nodes of the PMT returned by `inner_nodes()` function are equal to the expected
|
||||
/// ones.
|
||||
#[test]
|
||||
fn test_inner_node_iterator() {
|
||||
let mt = MerkleTree::new(digests_to_words(&VALUES8)).unwrap();
|
||||
|
||||
@@ -54,12 +54,17 @@ impl MerklePath {
|
||||
|
||||
/// Verifies the Merkle opening proof towards the provided root.
|
||||
///
|
||||
/// Returns `true` if `node` exists at `index` in a Merkle tree with `root`.
|
||||
pub fn verify(&self, index: u64, node: RpoDigest, root: &RpoDigest) -> bool {
|
||||
match self.compute_root(index, node) {
|
||||
Ok(computed_root) => root == &computed_root,
|
||||
Err(_) => false,
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - provided node index is invalid.
|
||||
/// - root calculated during the verification differs from the provided one.
|
||||
pub fn verify(&self, index: u64, node: RpoDigest, root: &RpoDigest) -> Result<(), MerkleError> {
|
||||
let computed_root = self.compute_root(index, node)?;
|
||||
if &computed_root != root {
|
||||
return Err(MerkleError::ConflictingRoots(vec![computed_root, *root]));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over every inner node of this [MerklePath].
|
||||
@@ -143,7 +148,7 @@ pub struct InnerNodeIterator<'a> {
|
||||
value: RpoDigest,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InnerNodeIterator<'a> {
|
||||
impl Iterator for InnerNodeIterator<'_> {
|
||||
type Item = InnerNodeInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
|
||||
@@ -37,17 +37,17 @@ impl fmt::Display for SmtLeafError {
|
||||
match self {
|
||||
InvalidNumEntriesForMultiple(num_entries) => {
|
||||
write!(f, "Multiple leaf requires 2 or more entries. Got: {num_entries}")
|
||||
}
|
||||
},
|
||||
InconsistentKeys { entries, key_1, key_2 } => {
|
||||
write!(f, "Multiple leaf requires all keys to map to the same leaf index. Offending keys: {key_1} and {key_2}. Entries: {entries:?}.")
|
||||
}
|
||||
},
|
||||
SingleKeyInconsistentWithLeafIndex { key, leaf_index } => {
|
||||
write!(
|
||||
f,
|
||||
"Single key in leaf inconsistent with leaf index. Key: {key}, leaf index: {}",
|
||||
leaf_index.value()
|
||||
)
|
||||
}
|
||||
},
|
||||
MultipleKeysInconsistentWithLeafIndex {
|
||||
leaf_index_from_keys,
|
||||
leaf_index_supplied,
|
||||
@@ -58,7 +58,7 @@ impl fmt::Display for SmtLeafError {
|
||||
leaf_index_from_keys.value(),
|
||||
leaf_index_supplied.value()
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl fmt::Display for SmtProofError {
|
||||
match self {
|
||||
InvalidPathLength(path_length) => {
|
||||
write!(f, "Invalid Merkle path length. Expected {SMT_DEPTH}, got {path_length}")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ impl SmtLeaf {
|
||||
///
|
||||
/// # Errors
|
||||
/// - Returns an error if 2 keys in `entries` map to a different leaf index
|
||||
/// - Returns an error if 1 or more keys in `entries` map to a leaf index
|
||||
/// different from `leaf_index`
|
||||
/// - Returns an error if 1 or more keys in `entries` map to a leaf index different from
|
||||
/// `leaf_index`
|
||||
pub fn new(
|
||||
entries: Vec<(RpoDigest, Word)>,
|
||||
leaf_index: LeafIndex<SMT_DEPTH>,
|
||||
@@ -39,7 +39,7 @@ impl SmtLeaf {
|
||||
}
|
||||
|
||||
Ok(Self::new_single(key, value))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let leaf = Self::new_multiple(entries)?;
|
||||
|
||||
@@ -53,7 +53,7 @@ impl SmtLeaf {
|
||||
} else {
|
||||
Ok(leaf)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl SmtLeaf {
|
||||
// Note: All keys are guaranteed to have the same leaf index
|
||||
let (first_key, _) = entries[0];
|
||||
first_key.into()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ impl SmtLeaf {
|
||||
SmtLeaf::Single(_) => 1,
|
||||
SmtLeaf::Multiple(entries) => {
|
||||
entries.len().try_into().expect("shouldn't have more than 2^64 entries")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ impl SmtLeaf {
|
||||
SmtLeaf::Multiple(kvs) => {
|
||||
let elements: Vec<Felt> = kvs.iter().copied().flat_map(kv_to_elements).collect();
|
||||
Rpo256::hash_elements(&elements)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,8 @@ impl SmtLeaf {
|
||||
// HELPERS
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the value associated with `key` in the leaf, or `None` if `key` maps to another leaf.
|
||||
/// Returns the value associated with `key` in the leaf, or `None` if `key` maps to another
|
||||
/// leaf.
|
||||
pub(super) fn get_value(&self, key: &RpoDigest) -> Option<Word> {
|
||||
// Ensure that `key` maps to this leaf
|
||||
if self.index() != key.into() {
|
||||
@@ -197,7 +198,7 @@ impl SmtLeaf {
|
||||
} else {
|
||||
Some(EMPTY_WORD)
|
||||
}
|
||||
}
|
||||
},
|
||||
SmtLeaf::Multiple(kv_pairs) => {
|
||||
for (key_in_leaf, value_in_leaf) in kv_pairs {
|
||||
if key == key_in_leaf {
|
||||
@@ -206,7 +207,7 @@ impl SmtLeaf {
|
||||
}
|
||||
|
||||
Some(EMPTY_WORD)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ impl SmtLeaf {
|
||||
SmtLeaf::Empty(_) => {
|
||||
*self = SmtLeaf::new_single(key, value);
|
||||
None
|
||||
}
|
||||
},
|
||||
SmtLeaf::Single(kv_pair) => {
|
||||
if kv_pair.0 == key {
|
||||
// the key is already in this leaf. Update the value and return the previous
|
||||
@@ -237,7 +238,7 @@ impl SmtLeaf {
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
SmtLeaf::Multiple(kv_pairs) => {
|
||||
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
||||
Ok(pos) => {
|
||||
@@ -245,14 +246,14 @@ impl SmtLeaf {
|
||||
kv_pairs[pos].1 = value;
|
||||
|
||||
Some(old_value)
|
||||
}
|
||||
},
|
||||
Err(pos) => {
|
||||
kv_pairs.insert(pos, (key, value));
|
||||
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +278,7 @@ impl SmtLeaf {
|
||||
// another key is stored at leaf; nothing to update
|
||||
(None, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
SmtLeaf::Multiple(kv_pairs) => {
|
||||
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
||||
Ok(pos) => {
|
||||
@@ -292,13 +293,13 @@ impl SmtLeaf {
|
||||
}
|
||||
|
||||
(Some(old_value), false)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// other keys are stored at leaf; nothing to update
|
||||
(None, false)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +350,7 @@ impl Deserializable for SmtLeaf {
|
||||
// ================================================================================================
|
||||
|
||||
/// Converts a key-value tuple to an iterator of `Felt`s
|
||||
fn kv_to_elements((key, value): (RpoDigest, Word)) -> impl Iterator<Item = Felt> {
|
||||
pub(crate) fn kv_to_elements((key, value): (RpoDigest, Word)) -> impl Iterator<Item = Felt> {
|
||||
let key_elements = key.into_iter();
|
||||
let value_elements = value.into_iter();
|
||||
|
||||
@@ -358,7 +359,7 @@ fn kv_to_elements((key, value): (RpoDigest, Word)) -> impl Iterator<Item = Felt>
|
||||
|
||||
/// Compares two keys, compared element-by-element using their integer representations starting with
|
||||
/// the most significant element.
|
||||
fn cmp_keys(key_1: RpoDigest, key_2: RpoDigest) -> Ordering {
|
||||
pub(crate) fn cmp_keys(key_1: RpoDigest, key_2: RpoDigest) -> Ordering {
|
||||
for (v1, v2) in key_1.iter().zip(key_2.iter()).rev() {
|
||||
let v1 = v1.as_int();
|
||||
let v2 = v2.as_int();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::{
|
||||
EmptySubtreeRoots, Felt, InnerNode, InnerNodeInfo, LeafIndex, MerkleError, MerklePath,
|
||||
NodeIndex, Rpo256, RpoDigest, SparseMerkleTree, Word, EMPTY_WORD,
|
||||
};
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
string::ToString,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use super::{
|
||||
EmptySubtreeRoots, Felt, InnerNode, InnerNodeInfo, LeafIndex, MerkleError, MerklePath,
|
||||
MutationSet, NodeIndex, Rpo256, RpoDigest, SparseMerkleTree, Word, EMPTY_WORD,
|
||||
};
|
||||
|
||||
mod error;
|
||||
pub use error::{SmtLeafError, SmtProofError};
|
||||
|
||||
@@ -32,8 +33,8 @@ pub const SMT_DEPTH: u8 = 64;
|
||||
/// Sparse Merkle tree mapping 256-bit keys to 256-bit values. Both keys and values are represented
|
||||
/// by 4 field elements.
|
||||
///
|
||||
/// All leaves sit at depth 64. The most significant element of the key is used to identify the leaf to
|
||||
/// which the key maps.
|
||||
/// All leaves sit at depth 64. The most significant element of the key is used to identify the leaf
|
||||
/// to which the key maps.
|
||||
///
|
||||
/// A leaf is either empty, or holds one or more key-value pairs. An empty leaf hashes to the empty
|
||||
/// word. Otherwise, a leaf hashes to the hash of its key-value pairs, ordered by key first, value
|
||||
@@ -120,12 +121,7 @@ impl Smt {
|
||||
|
||||
/// Returns the value associated with `key`
|
||||
pub fn get_value(&self, key: &RpoDigest) -> Word {
|
||||
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();
|
||||
|
||||
match self.leaves.get(&leaf_pos) {
|
||||
Some(leaf) => leaf.get_value(key).unwrap_or_default(),
|
||||
None => EMPTY_WORD,
|
||||
}
|
||||
<Self as SparseMerkleTree<SMT_DEPTH>>::get_value(self, key)
|
||||
}
|
||||
|
||||
/// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
|
||||
@@ -134,6 +130,12 @@ impl Smt {
|
||||
<Self as SparseMerkleTree<SMT_DEPTH>>::open(self, key)
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the SMT is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
debug_assert_eq!(self.leaves.is_empty(), self.root == Self::EMPTY_ROOT);
|
||||
self.root == Self::EMPTY_ROOT
|
||||
}
|
||||
|
||||
// ITERATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -171,6 +173,47 @@ impl Smt {
|
||||
<Self as SparseMerkleTree<SMT_DEPTH>>::insert(self, key, value)
|
||||
}
|
||||
|
||||
/// Computes what changes are necessary to insert the specified key-value pairs into this Merkle
|
||||
/// tree, allowing for validation before applying those changes.
|
||||
///
|
||||
/// This method returns a [`MutationSet`], which contains all the information for inserting
|
||||
/// `kv_pairs` into this Merkle tree already calculated, including the new root hash, which can
|
||||
/// be queried with [`MutationSet::root()`]. Once a mutation set is returned,
|
||||
/// [`Smt::apply_mutations()`] can be called in order to commit these changes to the Merkle
|
||||
/// tree, or [`drop()`] to discard them.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use miden_crypto::{hash::rpo::RpoDigest, Felt, Word};
|
||||
/// # use miden_crypto::merkle::{Smt, EmptySubtreeRoots, SMT_DEPTH};
|
||||
/// let mut smt = Smt::new();
|
||||
/// let pair = (RpoDigest::default(), Word::default());
|
||||
/// let mutations = smt.compute_mutations(vec![pair]);
|
||||
/// assert_eq!(mutations.root(), *EmptySubtreeRoots::entry(SMT_DEPTH, 0));
|
||||
/// smt.apply_mutations(mutations);
|
||||
/// assert_eq!(smt.root(), *EmptySubtreeRoots::entry(SMT_DEPTH, 0));
|
||||
/// ```
|
||||
pub fn compute_mutations(
|
||||
&self,
|
||||
kv_pairs: impl IntoIterator<Item = (RpoDigest, Word)>,
|
||||
) -> MutationSet<SMT_DEPTH, RpoDigest, Word> {
|
||||
<Self as SparseMerkleTree<SMT_DEPTH>>::compute_mutations(self, kv_pairs)
|
||||
}
|
||||
|
||||
/// Apply the prospective mutations computed with [`Smt::compute_mutations()`] to this tree.
|
||||
///
|
||||
/// # Errors
|
||||
/// If `mutations` was computed on a tree with a different root than this one, returns
|
||||
/// [`MerkleError::ConflictingRoots`] with a two-item [`Vec`]. The first item is the root hash
|
||||
/// the `mutations` were computed against, and the second item is the actual current root of
|
||||
/// this tree.
|
||||
pub fn apply_mutations(
|
||||
&mut self,
|
||||
mutations: MutationSet<SMT_DEPTH, RpoDigest, Word>,
|
||||
) -> Result<(), MerkleError> {
|
||||
<Self as SparseMerkleTree<SMT_DEPTH>>::apply_mutations(self, mutations)
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -187,7 +230,7 @@ impl Smt {
|
||||
self.leaves.insert(leaf_index.value(), SmtLeaf::Single((key, value)));
|
||||
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +258,7 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||
type Opening = SmtProof;
|
||||
|
||||
const EMPTY_VALUE: Self::Value = EMPTY_WORD;
|
||||
const EMPTY_ROOT: RpoDigest = *EmptySubtreeRoots::entry(SMT_DEPTH, 0);
|
||||
|
||||
fn root(&self) -> RpoDigest {
|
||||
self.root
|
||||
@@ -225,11 +269,10 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||
}
|
||||
|
||||
fn get_inner_node(&self, index: NodeIndex) -> InnerNode {
|
||||
self.inner_nodes.get(&index).cloned().unwrap_or_else(|| {
|
||||
let node = EmptySubtreeRoots::entry(SMT_DEPTH, index.depth() + 1);
|
||||
|
||||
InnerNode { left: *node, right: *node }
|
||||
})
|
||||
self.inner_nodes
|
||||
.get(&index)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| EmptySubtreeRoots::get_inner_node(SMT_DEPTH, index.depth()))
|
||||
}
|
||||
|
||||
fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) {
|
||||
@@ -249,6 +292,15 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value(&self, key: &Self::Key) -> Self::Value {
|
||||
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();
|
||||
|
||||
match self.leaves.get(&leaf_pos) {
|
||||
Some(leaf) => leaf.get_value(key).unwrap_or_default(),
|
||||
None => EMPTY_WORD,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_leaf(&self, key: &RpoDigest) -> Self::Leaf {
|
||||
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();
|
||||
|
||||
@@ -262,6 +314,28 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||
leaf.hash()
|
||||
}
|
||||
|
||||
fn construct_prospective_leaf(
|
||||
&self,
|
||||
mut existing_leaf: SmtLeaf,
|
||||
key: &RpoDigest,
|
||||
value: &Word,
|
||||
) -> SmtLeaf {
|
||||
debug_assert_eq!(existing_leaf.index(), Self::key_to_leaf_index(key));
|
||||
|
||||
match existing_leaf {
|
||||
SmtLeaf::Empty(_) => SmtLeaf::new_single(*key, *value),
|
||||
_ => {
|
||||
if *value != EMPTY_WORD {
|
||||
existing_leaf.insert(*key, *value);
|
||||
} else {
|
||||
existing_leaf.remove(*key);
|
||||
}
|
||||
|
||||
existing_leaf
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_leaf_index(key: &RpoDigest) -> LeafIndex<SMT_DEPTH> {
|
||||
let most_significant_felt = key[3];
|
||||
LeafIndex::new_max_depth(most_significant_felt.as_int())
|
||||
@@ -314,6 +388,14 @@ impl Serializable for Smt {
|
||||
target.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size_hint(&self) -> usize {
|
||||
let entries_count = self.entries().count();
|
||||
|
||||
// Each entry is the size of a digest plus a word.
|
||||
entries_count.get_size_hint()
|
||||
+ entries_count * (RpoDigest::SERIALIZED_SIZE + EMPTY_WORD.get_size_hint())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for Smt {
|
||||
@@ -339,6 +421,7 @@ fn test_smt_serialization_deserialization() {
|
||||
let smt_default = Smt::default();
|
||||
let bytes = smt_default.to_bytes();
|
||||
assert_eq!(smt_default, Smt::read_from_bytes(&bytes).unwrap());
|
||||
assert_eq!(bytes.len(), smt_default.get_size_hint());
|
||||
|
||||
// Smt with values
|
||||
let smt_leaves_2: [(RpoDigest, Word); 2] = [
|
||||
@@ -355,4 +438,5 @@ fn test_smt_serialization_deserialization() {
|
||||
|
||||
let bytes = smt.to_bytes();
|
||||
assert_eq!(smt, Smt::read_from_bytes(&bytes).unwrap());
|
||||
assert_eq!(bytes.len(), smt.get_size_hint());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::{MerklePath, RpoDigest, SmtLeaf, SmtProofError, Word, SMT_DEPTH};
|
||||
use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
|
||||
use alloc::string::ToString;
|
||||
|
||||
/// A proof which can be used to assert membership (or non-membership) of key-value pairs in a
|
||||
/// [`super::Smt`].
|
||||
@@ -57,7 +58,7 @@ impl SmtProof {
|
||||
|
||||
// make sure the Merkle path resolves to the correct root
|
||||
self.compute_root() == *root
|
||||
}
|
||||
},
|
||||
// If the key maps to a different leaf, the proof cannot verify membership of `value`
|
||||
None => false,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{Felt, LeafIndex, NodeIndex, Rpo256, RpoDigest, Smt, SmtLeaf, EMPTY_WORD, SMT_DEPTH};
|
||||
use crate::{
|
||||
merkle::{EmptySubtreeRoots, MerkleStore},
|
||||
merkle::{smt::SparseMerkleTree, EmptySubtreeRoots, MerkleStore},
|
||||
utils::{Deserializable, Serializable},
|
||||
Word, ONE, WORD_SIZE,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// SMT
|
||||
// --------------------------------------------------------------------------------------------
|
||||
@@ -257,6 +258,195 @@ fn test_smt_removal() {
|
||||
}
|
||||
}
|
||||
|
||||
/// This tests that we can correctly calculate prospective leaves -- that is, we can construct
|
||||
/// correct [`SmtLeaf`] values for a theoretical insertion on a Merkle tree without mutating or
|
||||
/// cloning the tree.
|
||||
#[test]
|
||||
fn test_prospective_hash() {
|
||||
let mut smt = Smt::default();
|
||||
|
||||
let raw = 0b_01101001_01101100_00011111_11111111_10010110_10010011_11100000_00000000_u64;
|
||||
|
||||
let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, Felt::new(raw)]);
|
||||
let key_2: RpoDigest =
|
||||
RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), Felt::new(raw)]);
|
||||
// Sort key_3 before key_1, to test non-append insertion.
|
||||
let key_3: RpoDigest =
|
||||
RpoDigest::from([0_u32.into(), 0_u32.into(), 0_u32.into(), Felt::new(raw)]);
|
||||
|
||||
let value_1 = [ONE; WORD_SIZE];
|
||||
let value_2 = [2_u32.into(); WORD_SIZE];
|
||||
let value_3: [Felt; 4] = [3_u32.into(); WORD_SIZE];
|
||||
|
||||
// insert key-value 1
|
||||
{
|
||||
let prospective =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_1), &key_1, &value_1).hash();
|
||||
smt.insert(key_1, value_1);
|
||||
|
||||
let leaf = smt.get_leaf(&key_1);
|
||||
assert_eq!(
|
||||
prospective,
|
||||
leaf.hash(),
|
||||
"prospective hash for leaf {leaf:?} did not match actual hash",
|
||||
);
|
||||
}
|
||||
|
||||
// insert key-value 2
|
||||
{
|
||||
let prospective =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_2), &key_2, &value_2).hash();
|
||||
smt.insert(key_2, value_2);
|
||||
|
||||
let leaf = smt.get_leaf(&key_2);
|
||||
assert_eq!(
|
||||
prospective,
|
||||
leaf.hash(),
|
||||
"prospective hash for leaf {leaf:?} did not match actual hash",
|
||||
);
|
||||
}
|
||||
|
||||
// insert key-value 3
|
||||
{
|
||||
let prospective =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_3), &key_3, &value_3).hash();
|
||||
smt.insert(key_3, value_3);
|
||||
|
||||
let leaf = smt.get_leaf(&key_3);
|
||||
assert_eq!(
|
||||
prospective,
|
||||
leaf.hash(),
|
||||
"prospective hash for leaf {leaf:?} did not match actual hash",
|
||||
);
|
||||
}
|
||||
|
||||
// remove key 3
|
||||
{
|
||||
let old_leaf = smt.get_leaf(&key_3);
|
||||
let old_value_3 = smt.insert(key_3, EMPTY_WORD);
|
||||
assert_eq!(old_value_3, value_3);
|
||||
let prospective_leaf =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_3), &key_3, &old_value_3);
|
||||
|
||||
assert_eq!(
|
||||
old_leaf.hash(),
|
||||
prospective_leaf.hash(),
|
||||
"removing and prospectively re-adding a leaf didn't yield the original leaf:\
|
||||
\n original leaf: {old_leaf:?}\
|
||||
\n prospective leaf: {prospective_leaf:?}",
|
||||
);
|
||||
}
|
||||
|
||||
// remove key 2
|
||||
{
|
||||
let old_leaf = smt.get_leaf(&key_2);
|
||||
let old_value_2 = smt.insert(key_2, EMPTY_WORD);
|
||||
assert_eq!(old_value_2, value_2);
|
||||
let prospective_leaf =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_2), &key_2, &old_value_2);
|
||||
|
||||
assert_eq!(
|
||||
old_leaf.hash(),
|
||||
prospective_leaf.hash(),
|
||||
"removing and prospectively re-adding a leaf didn't yield the original leaf:\
|
||||
\n original leaf: {old_leaf:?}\
|
||||
\n prospective leaf: {prospective_leaf:?}",
|
||||
);
|
||||
}
|
||||
|
||||
// remove key 1
|
||||
{
|
||||
let old_leaf = smt.get_leaf(&key_1);
|
||||
let old_value_1 = smt.insert(key_1, EMPTY_WORD);
|
||||
assert_eq!(old_value_1, value_1);
|
||||
let prospective_leaf =
|
||||
smt.construct_prospective_leaf(smt.get_leaf(&key_1), &key_1, &old_value_1);
|
||||
assert_eq!(
|
||||
old_leaf.hash(),
|
||||
prospective_leaf.hash(),
|
||||
"removing and prospectively re-adding a leaf didn't yield the original leaf:\
|
||||
\n original leaf: {old_leaf:?}\
|
||||
\n prospective leaf: {prospective_leaf:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This tests that we can perform prospective changes correctly.
|
||||
#[test]
|
||||
fn test_prospective_insertion() {
|
||||
let mut smt = Smt::default();
|
||||
|
||||
let raw = 0b_01101001_01101100_00011111_11111111_10010110_10010011_11100000_00000000_u64;
|
||||
|
||||
let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, Felt::new(raw)]);
|
||||
let key_2: RpoDigest =
|
||||
RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), Felt::new(raw)]);
|
||||
// Sort key_3 before key_1, to test non-append insertion.
|
||||
let key_3: RpoDigest =
|
||||
RpoDigest::from([0_u32.into(), 0_u32.into(), 0_u32.into(), Felt::new(raw)]);
|
||||
|
||||
let value_1 = [ONE; WORD_SIZE];
|
||||
let value_2 = [2_u32.into(); WORD_SIZE];
|
||||
let value_3: [Felt; 4] = [3_u32.into(); WORD_SIZE];
|
||||
|
||||
let root_empty = smt.root();
|
||||
|
||||
let root_1 = {
|
||||
smt.insert(key_1, value_1);
|
||||
smt.root()
|
||||
};
|
||||
|
||||
let root_2 = {
|
||||
smt.insert(key_2, value_2);
|
||||
smt.root()
|
||||
};
|
||||
|
||||
let root_3 = {
|
||||
smt.insert(key_3, value_3);
|
||||
smt.root()
|
||||
};
|
||||
|
||||
// Test incremental updates.
|
||||
|
||||
let mut smt = Smt::default();
|
||||
|
||||
let mutations = smt.compute_mutations(vec![(key_1, value_1)]);
|
||||
assert_eq!(mutations.root(), root_1, "prospective root 1 did not match actual root 1");
|
||||
smt.apply_mutations(mutations).unwrap();
|
||||
assert_eq!(smt.root(), root_1, "mutations before and after apply did not match");
|
||||
|
||||
let mutations = smt.compute_mutations(vec![(key_2, value_2)]);
|
||||
assert_eq!(mutations.root(), root_2, "prospective root 2 did not match actual root 2");
|
||||
let mutations =
|
||||
smt.compute_mutations(vec![(key_3, EMPTY_WORD), (key_2, value_2), (key_3, value_3)]);
|
||||
assert_eq!(mutations.root(), root_3, "mutations before and after apply did not match");
|
||||
smt.apply_mutations(mutations).unwrap();
|
||||
|
||||
// Edge case: multiple values at the same key, where a later pair restores the original value.
|
||||
let mutations = smt.compute_mutations(vec![(key_3, EMPTY_WORD), (key_3, value_3)]);
|
||||
assert_eq!(mutations.root(), root_3);
|
||||
smt.apply_mutations(mutations).unwrap();
|
||||
assert_eq!(smt.root(), root_3);
|
||||
|
||||
// Test batch updates, and that the order doesn't matter.
|
||||
let pairs =
|
||||
vec![(key_3, value_2), (key_2, EMPTY_WORD), (key_1, EMPTY_WORD), (key_3, EMPTY_WORD)];
|
||||
let mutations = smt.compute_mutations(pairs);
|
||||
assert_eq!(
|
||||
mutations.root(),
|
||||
root_empty,
|
||||
"prospective root for batch removal did not match actual root",
|
||||
);
|
||||
smt.apply_mutations(mutations).unwrap();
|
||||
assert_eq!(smt.root(), root_empty, "mutations before and after apply did not match");
|
||||
|
||||
let pairs = vec![(key_3, value_3), (key_1, value_1), (key_2, value_2)];
|
||||
let mutations = smt.compute_mutations(pairs);
|
||||
assert_eq!(mutations.root(), root_3);
|
||||
smt.apply_mutations(mutations).unwrap();
|
||||
assert_eq!(smt.root(), root_3);
|
||||
}
|
||||
|
||||
/// Tests that 2 key-value pairs stored in the same leaf have the same path
|
||||
#[test]
|
||||
fn test_smt_path_to_keys_in_same_leaf_are_equal() {
|
||||
@@ -287,8 +477,7 @@ fn test_empty_leaf_hash() {
|
||||
#[test]
|
||||
fn test_smt_get_value() {
|
||||
let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, ONE]);
|
||||
let key_2: RpoDigest =
|
||||
RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), 2_u32.into()]);
|
||||
let key_2: RpoDigest = RpoDigest::from([2_u32, 2_u32, 2_u32, 2_u32]);
|
||||
|
||||
let value_1 = [ONE; WORD_SIZE];
|
||||
let value_2 = [2_u32.into(); WORD_SIZE];
|
||||
@@ -302,8 +491,7 @@ fn test_smt_get_value() {
|
||||
assert_eq!(value_2, returned_value_2);
|
||||
|
||||
// Check that a key with no inserted value returns the empty word
|
||||
let key_no_value =
|
||||
RpoDigest::from([42_u32.into(), 42_u32.into(), 42_u32.into(), 42_u32.into()]);
|
||||
let key_no_value = RpoDigest::from([42_u32, 42_u32, 42_u32, 42_u32]);
|
||||
|
||||
assert_eq!(EMPTY_WORD, smt.get_value(&key_no_value));
|
||||
}
|
||||
@@ -312,8 +500,7 @@ fn test_smt_get_value() {
|
||||
#[test]
|
||||
fn test_smt_entries() {
|
||||
let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, ONE]);
|
||||
let key_2: RpoDigest =
|
||||
RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), 2_u32.into()]);
|
||||
let key_2: RpoDigest = RpoDigest::from([2_u32, 2_u32, 2_u32, 2_u32]);
|
||||
|
||||
let value_1 = [ONE; WORD_SIZE];
|
||||
let value_2 = [2_u32.into(); WORD_SIZE];
|
||||
@@ -329,6 +516,16 @@ fn test_smt_entries() {
|
||||
assert!(entries.next().is_none());
|
||||
}
|
||||
|
||||
/// Tests that `EMPTY_ROOT` constant generated in the `Smt` equals to the root of the empty tree of
|
||||
/// depth 64
|
||||
#[test]
|
||||
fn test_smt_check_empty_root_constant() {
|
||||
// get the root of the empty tree of depth 64
|
||||
let empty_root_64_depth = EmptySubtreeRoots::empty_hashes(64)[0];
|
||||
|
||||
assert_eq!(empty_root_64_depth, Smt::EMPTY_ROOT);
|
||||
}
|
||||
|
||||
// SMT LEAF
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -347,7 +544,7 @@ fn test_empty_smt_leaf_serialization() {
|
||||
#[test]
|
||||
fn test_single_smt_leaf_serialization() {
|
||||
let single_leaf = SmtLeaf::new_single(
|
||||
RpoDigest::from([10_u32.into(), 11_u32.into(), 12_u32.into(), 13_u32.into()]),
|
||||
RpoDigest::from([10_u32, 11_u32, 12_u32, 13_u32]),
|
||||
[1_u32.into(), 2_u32.into(), 3_u32.into(), 4_u32.into()],
|
||||
);
|
||||
|
||||
@@ -363,11 +560,11 @@ fn test_single_smt_leaf_serialization() {
|
||||
fn test_multiple_smt_leaf_serialization_success() {
|
||||
let multiple_leaf = SmtLeaf::new_multiple(vec![
|
||||
(
|
||||
RpoDigest::from([10_u32.into(), 11_u32.into(), 12_u32.into(), 13_u32.into()]),
|
||||
RpoDigest::from([10_u32, 11_u32, 12_u32, 13_u32]),
|
||||
[1_u32.into(), 2_u32.into(), 3_u32.into(), 4_u32.into()],
|
||||
),
|
||||
(
|
||||
RpoDigest::from([100_u32.into(), 101_u32.into(), 102_u32.into(), 13_u32.into()]),
|
||||
RpoDigest::from([100_u32, 101_u32, 102_u32, 13_u32]),
|
||||
[11_u32.into(), 12_u32.into(), 13_u32.into(), 14_u32.into()],
|
||||
),
|
||||
])
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use alloc::{collections::BTreeMap, vec::Vec};
|
||||
|
||||
use super::{EmptySubtreeRoots, InnerNodeInfo, MerkleError, MerklePath, NodeIndex};
|
||||
use crate::{
|
||||
hash::rpo::{Rpo256, RpoDigest},
|
||||
Felt, Word, EMPTY_WORD,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
mod full;
|
||||
pub use full::{Smt, SmtLeaf, SmtLeafError, SmtProof, SmtProofError, SMT_DEPTH};
|
||||
@@ -44,17 +45,20 @@ pub const SMT_MAX_DEPTH: u8 = 64;
|
||||
/// [SparseMerkleTree] currently doesn't support optimizations that compress Merkle proofs.
|
||||
pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||
/// The type for a key
|
||||
type Key: Clone;
|
||||
type Key: Clone + Ord;
|
||||
/// The type for a value
|
||||
type Value: Clone + PartialEq;
|
||||
/// The type for a leaf
|
||||
type Leaf;
|
||||
type Leaf: Clone;
|
||||
/// The type for an opening (i.e. a "proof") of a leaf
|
||||
type Opening;
|
||||
|
||||
/// The default value used to compute the hash of empty leaves
|
||||
const EMPTY_VALUE: Self::Value;
|
||||
|
||||
/// The root of the empty tree with provided DEPTH
|
||||
const EMPTY_ROOT: RpoDigest;
|
||||
|
||||
// PROVIDED METHODS
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -139,6 +143,149 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||
self.set_root(node_hash);
|
||||
}
|
||||
|
||||
/// Computes what changes are necessary to insert the specified key-value pairs into this Merkle
|
||||
/// tree, allowing for validation before applying those changes.
|
||||
///
|
||||
/// This method returns a [`MutationSet`], which contains all the information for inserting
|
||||
/// `kv_pairs` into this Merkle tree already calculated, including the new root hash, which can
|
||||
/// be queried with [`MutationSet::root()`]. Once a mutation set is returned,
|
||||
/// [`SparseMerkleTree::apply_mutations()`] can be called in order to commit these changes to
|
||||
/// the Merkle tree, or [`drop()`] to discard them.
|
||||
fn compute_mutations(
|
||||
&self,
|
||||
kv_pairs: impl IntoIterator<Item = (Self::Key, Self::Value)>,
|
||||
) -> MutationSet<DEPTH, Self::Key, Self::Value> {
|
||||
use NodeMutation::*;
|
||||
|
||||
let mut new_root = self.root();
|
||||
let mut new_pairs: BTreeMap<Self::Key, Self::Value> = Default::default();
|
||||
let mut node_mutations: BTreeMap<NodeIndex, NodeMutation> = Default::default();
|
||||
|
||||
for (key, value) in kv_pairs {
|
||||
// If the old value and the new value are the same, there is nothing to update.
|
||||
// For the unusual case that kv_pairs has multiple values at the same key, we'll have
|
||||
// to check the key-value pairs we've already seen to get the "effective" old value.
|
||||
let old_value = new_pairs.get(&key).cloned().unwrap_or_else(|| self.get_value(&key));
|
||||
if value == old_value {
|
||||
continue;
|
||||
}
|
||||
|
||||
let leaf_index = Self::key_to_leaf_index(&key);
|
||||
let mut node_index = NodeIndex::from(leaf_index);
|
||||
|
||||
// We need the current leaf's hash to calculate the new leaf, but in the rare case that
|
||||
// `kv_pairs` has multiple pairs that go into the same leaf, then those pairs are also
|
||||
// part of the "current leaf".
|
||||
let old_leaf = {
|
||||
let pairs_at_index = new_pairs
|
||||
.iter()
|
||||
.filter(|&(new_key, _)| Self::key_to_leaf_index(new_key) == leaf_index);
|
||||
|
||||
pairs_at_index.fold(self.get_leaf(&key), |acc, (k, v)| {
|
||||
// Most of the time `pairs_at_index` should only contain a single entry (or
|
||||
// none at all), as multi-leaves should be really rare.
|
||||
let existing_leaf = acc.clone();
|
||||
self.construct_prospective_leaf(existing_leaf, k, v)
|
||||
})
|
||||
};
|
||||
|
||||
let new_leaf = self.construct_prospective_leaf(old_leaf, &key, &value);
|
||||
|
||||
let mut new_child_hash = Self::hash_leaf(&new_leaf);
|
||||
|
||||
for node_depth in (0..node_index.depth()).rev() {
|
||||
// Whether the node we're replacing is the right child or the left child.
|
||||
let is_right = node_index.is_value_odd();
|
||||
node_index.move_up();
|
||||
|
||||
let old_node = node_mutations
|
||||
.get(&node_index)
|
||||
.map(|mutation| match mutation {
|
||||
Addition(node) => node.clone(),
|
||||
Removal => EmptySubtreeRoots::get_inner_node(DEPTH, node_depth),
|
||||
})
|
||||
.unwrap_or_else(|| self.get_inner_node(node_index));
|
||||
|
||||
let new_node = if is_right {
|
||||
InnerNode {
|
||||
left: old_node.left,
|
||||
right: new_child_hash,
|
||||
}
|
||||
} else {
|
||||
InnerNode {
|
||||
left: new_child_hash,
|
||||
right: old_node.right,
|
||||
}
|
||||
};
|
||||
|
||||
// The next iteration will operate on this new node's hash.
|
||||
new_child_hash = new_node.hash();
|
||||
|
||||
let &equivalent_empty_hash = EmptySubtreeRoots::entry(DEPTH, node_depth);
|
||||
let is_removal = new_child_hash == equivalent_empty_hash;
|
||||
let new_entry = if is_removal { Removal } else { Addition(new_node) };
|
||||
node_mutations.insert(node_index, new_entry);
|
||||
}
|
||||
|
||||
// Once we're at depth 0, the last node we made is the new root.
|
||||
new_root = new_child_hash;
|
||||
// And then we're done with this pair; on to the next one.
|
||||
new_pairs.insert(key, value);
|
||||
}
|
||||
|
||||
MutationSet {
|
||||
old_root: self.root(),
|
||||
new_root,
|
||||
node_mutations,
|
||||
new_pairs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the prospective mutations computed with [`SparseMerkleTree::compute_mutations()`] to
|
||||
/// this tree.
|
||||
///
|
||||
/// # Errors
|
||||
/// If `mutations` was computed on a tree with a different root than this one, returns
|
||||
/// [`MerkleError::ConflictingRoots`] with a two-item [`Vec`]. The first item is the root hash
|
||||
/// the `mutations` were computed against, and the second item is the actual current root of
|
||||
/// this tree.
|
||||
fn apply_mutations(
|
||||
&mut self,
|
||||
mutations: MutationSet<DEPTH, Self::Key, Self::Value>,
|
||||
) -> Result<(), MerkleError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
use NodeMutation::*;
|
||||
let MutationSet {
|
||||
old_root,
|
||||
node_mutations,
|
||||
new_pairs,
|
||||
new_root,
|
||||
} = mutations;
|
||||
|
||||
// Guard against accidentally trying to apply mutations that were computed against a
|
||||
// different tree, including a stale version of this tree.
|
||||
if old_root != self.root() {
|
||||
return Err(MerkleError::ConflictingRoots(vec![old_root, self.root()]));
|
||||
}
|
||||
|
||||
for (index, mutation) in node_mutations {
|
||||
match mutation {
|
||||
Removal => self.remove_inner_node(index),
|
||||
Addition(node) => self.insert_inner_node(index, node),
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in new_pairs {
|
||||
self.insert_value(key, value);
|
||||
}
|
||||
|
||||
self.set_root(new_root);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// REQUIRED METHODS
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -160,12 +307,34 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||
/// Inserts a leaf node, and returns the value at the key if already exists
|
||||
fn insert_value(&mut self, key: Self::Key, value: Self::Value) -> Option<Self::Value>;
|
||||
|
||||
/// Returns the value at the specified key. Recall that by definition, any key that hasn't been
|
||||
/// updated is associated with [`Self::EMPTY_VALUE`].
|
||||
fn get_value(&self, key: &Self::Key) -> Self::Value;
|
||||
|
||||
/// Returns the leaf at the specified index.
|
||||
fn get_leaf(&self, key: &Self::Key) -> Self::Leaf;
|
||||
|
||||
/// Returns the hash of a leaf
|
||||
fn hash_leaf(leaf: &Self::Leaf) -> RpoDigest;
|
||||
|
||||
/// Returns what a leaf would look like if a key-value pair were inserted into the tree, without
|
||||
/// mutating the tree itself. The existing leaf can be empty.
|
||||
///
|
||||
/// To get a prospective leaf based on the current state of the tree, use `self.get_leaf(key)`
|
||||
/// as the argument for `existing_leaf`. The return value from this function can be chained back
|
||||
/// into this function as the first argument to continue making prospective changes.
|
||||
///
|
||||
/// # Invariants
|
||||
/// Because this method is for a prospective key-value insertion into a specific leaf,
|
||||
/// `existing_leaf` must have the same leaf index as `key` (as determined by
|
||||
/// [`SparseMerkleTree::key_to_leaf_index()`]), or the result will be meaningless.
|
||||
fn construct_prospective_leaf(
|
||||
&self,
|
||||
existing_leaf: Self::Leaf,
|
||||
key: &Self::Key,
|
||||
value: &Self::Value,
|
||||
) -> Self::Leaf;
|
||||
|
||||
/// Maps a key to a leaf index
|
||||
fn key_to_leaf_index(key: &Self::Key) -> LeafIndex<DEPTH>;
|
||||
|
||||
@@ -243,3 +412,50 @@ impl<const DEPTH: u8> TryFrom<NodeIndex> for LeafIndex<DEPTH> {
|
||||
Self::new(node_index.value())
|
||||
}
|
||||
}
|
||||
|
||||
// MUTATIONS
|
||||
// ================================================================================================
|
||||
|
||||
/// A change to an inner node of a [`SparseMerkleTree`] that hasn't yet been applied.
|
||||
/// [`MutationSet`] stores this type in relation to a [`NodeIndex`] to keep track of what changes
|
||||
/// need to occur at which node indices.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum NodeMutation {
|
||||
/// Corresponds to [`SparseMerkleTree::remove_inner_node()`].
|
||||
Removal,
|
||||
/// Corresponds to [`SparseMerkleTree::insert_inner_node()`].
|
||||
Addition(InnerNode),
|
||||
}
|
||||
|
||||
/// Represents a group of prospective mutations to a `SparseMerkleTree`, created by
|
||||
/// `SparseMerkleTree::compute_mutations()`, and that can be applied with
|
||||
/// `SparseMerkleTree::apply_mutations()`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct MutationSet<const DEPTH: u8, K, V> {
|
||||
/// The root of the Merkle tree this MutationSet is for, recorded at the time
|
||||
/// [`SparseMerkleTree::compute_mutations()`] was called. Exists to guard against applying
|
||||
/// mutations to the wrong tree or applying stale mutations to a tree that has since changed.
|
||||
old_root: RpoDigest,
|
||||
/// The set of nodes that need to be removed or added. The "effective" node at an index is the
|
||||
/// Merkle tree's existing node at that index, with the [`NodeMutation`] in this map at that
|
||||
/// index overlayed, if any. Each [`NodeMutation::Addition`] corresponds to a
|
||||
/// [`SparseMerkleTree::insert_inner_node()`] call, and each [`NodeMutation::Removal`]
|
||||
/// corresponds to a [`SparseMerkleTree::remove_inner_node()`] call.
|
||||
node_mutations: BTreeMap<NodeIndex, NodeMutation>,
|
||||
/// The set of top-level key-value pairs we're prospectively adding to the tree, including
|
||||
/// adding empty values. The "effective" value for a key is the value in this BTreeMap, falling
|
||||
/// back to the existing value in the Merkle tree. Each entry corresponds to a
|
||||
/// [`SparseMerkleTree::insert_value()`] call.
|
||||
new_pairs: BTreeMap<K, V>,
|
||||
/// The calculated root for the Merkle tree, given these mutations. Publicly retrievable with
|
||||
/// [`MutationSet::root()`]. Corresponds to a [`SparseMerkleTree::set_root()`]. call.
|
||||
new_root: RpoDigest,
|
||||
}
|
||||
|
||||
impl<const DEPTH: u8, K, V> MutationSet<DEPTH, K, V> {
|
||||
/// Queries the root that was calculated during `SparseMerkleTree::compute_mutations()`. See
|
||||
/// that method for more information.
|
||||
pub fn root(&self) -> RpoDigest {
|
||||
self.new_root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::{
|
||||
super::ValuePath, EmptySubtreeRoots, InnerNode, InnerNodeInfo, LeafIndex, MerkleError,
|
||||
MerklePath, NodeIndex, RpoDigest, SparseMerkleTree, Word, EMPTY_WORD, SMT_MAX_DEPTH,
|
||||
SMT_MIN_DEPTH,
|
||||
MerklePath, MutationSet, NodeIndex, RpoDigest, SparseMerkleTree, Word, EMPTY_WORD,
|
||||
SMT_MAX_DEPTH, SMT_MIN_DEPTH,
|
||||
};
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -157,6 +158,12 @@ impl<const DEPTH: u8> SimpleSmt<DEPTH> {
|
||||
<Self as SparseMerkleTree<DEPTH>>::open(self, key)
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the SMT is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
debug_assert_eq!(self.leaves.is_empty(), self.root == Self::EMPTY_ROOT);
|
||||
self.root == Self::EMPTY_ROOT
|
||||
}
|
||||
|
||||
// ITERATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -187,6 +194,48 @@ impl<const DEPTH: u8> SimpleSmt<DEPTH> {
|
||||
<Self as SparseMerkleTree<DEPTH>>::insert(self, key, value)
|
||||
}
|
||||
|
||||
/// Computes what changes are necessary to insert the specified key-value pairs into this
|
||||
/// Merkle tree, allowing for validation before applying those changes.
|
||||
///
|
||||
/// This method returns a [`MutationSet`], which contains all the information for inserting
|
||||
/// `kv_pairs` into this Merkle tree already calculated, including the new root hash, which can
|
||||
/// be queried with [`MutationSet::root()`]. Once a mutation set is returned,
|
||||
/// [`SimpleSmt::apply_mutations()`] can be called in order to commit these changes to the
|
||||
/// Merkle tree, or [`drop()`] to discard them.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use miden_crypto::{hash::rpo::RpoDigest, Felt, Word};
|
||||
/// # use miden_crypto::merkle::{LeafIndex, SimpleSmt, EmptySubtreeRoots, SMT_DEPTH};
|
||||
/// let mut smt: SimpleSmt<3> = SimpleSmt::new().unwrap();
|
||||
/// let pair = (LeafIndex::default(), Word::default());
|
||||
/// let mutations = smt.compute_mutations(vec![pair]);
|
||||
/// assert_eq!(mutations.root(), *EmptySubtreeRoots::entry(3, 0));
|
||||
/// smt.apply_mutations(mutations);
|
||||
/// assert_eq!(smt.root(), *EmptySubtreeRoots::entry(3, 0));
|
||||
/// ```
|
||||
pub fn compute_mutations(
|
||||
&self,
|
||||
kv_pairs: impl IntoIterator<Item = (LeafIndex<DEPTH>, Word)>,
|
||||
) -> MutationSet<DEPTH, LeafIndex<DEPTH>, Word> {
|
||||
<Self as SparseMerkleTree<DEPTH>>::compute_mutations(self, kv_pairs)
|
||||
}
|
||||
|
||||
/// Apply the prospective mutations computed with [`SimpleSmt::compute_mutations()`] to this
|
||||
/// tree.
|
||||
///
|
||||
/// # Errors
|
||||
/// If `mutations` was computed on a tree with a different root than this one, returns
|
||||
/// [`MerkleError::ConflictingRoots`] with a two-item [`alloc::vec::Vec`]. The first item is the
|
||||
/// root hash the `mutations` were computed against, and the second item is the actual
|
||||
/// current root of this tree.
|
||||
pub fn apply_mutations(
|
||||
&mut self,
|
||||
mutations: MutationSet<DEPTH, LeafIndex<DEPTH>, Word>,
|
||||
) -> Result<(), MerkleError> {
|
||||
<Self as SparseMerkleTree<DEPTH>>::apply_mutations(self, mutations)
|
||||
}
|
||||
|
||||
/// Inserts a subtree at the specified index. The depth at which the subtree is inserted is
|
||||
/// computed as `DEPTH - SUBTREE_DEPTH`.
|
||||
///
|
||||
@@ -255,6 +304,7 @@ impl<const DEPTH: u8> SparseMerkleTree<DEPTH> for SimpleSmt<DEPTH> {
|
||||
type Opening = ValuePath;
|
||||
|
||||
const EMPTY_VALUE: Self::Value = EMPTY_WORD;
|
||||
const EMPTY_ROOT: RpoDigest = *EmptySubtreeRoots::entry(DEPTH, 0);
|
||||
|
||||
fn root(&self) -> RpoDigest {
|
||||
self.root
|
||||
@@ -265,11 +315,10 @@ impl<const DEPTH: u8> SparseMerkleTree<DEPTH> for SimpleSmt<DEPTH> {
|
||||
}
|
||||
|
||||
fn get_inner_node(&self, index: NodeIndex) -> InnerNode {
|
||||
self.inner_nodes.get(&index).cloned().unwrap_or_else(|| {
|
||||
let node = EmptySubtreeRoots::entry(DEPTH, index.depth() + 1);
|
||||
|
||||
InnerNode { left: *node, right: *node }
|
||||
})
|
||||
self.inner_nodes
|
||||
.get(&index)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| EmptySubtreeRoots::get_inner_node(DEPTH, index.depth()))
|
||||
}
|
||||
|
||||
fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) {
|
||||
@@ -288,6 +337,10 @@ impl<const DEPTH: u8> SparseMerkleTree<DEPTH> for SimpleSmt<DEPTH> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value(&self, key: &LeafIndex<DEPTH>) -> Word {
|
||||
self.get_leaf(key)
|
||||
}
|
||||
|
||||
fn get_leaf(&self, key: &LeafIndex<DEPTH>) -> Word {
|
||||
let leaf_pos = key.value();
|
||||
match self.leaves.get(&leaf_pos) {
|
||||
@@ -301,6 +354,15 @@ impl<const DEPTH: u8> SparseMerkleTree<DEPTH> for SimpleSmt<DEPTH> {
|
||||
leaf.into()
|
||||
}
|
||||
|
||||
fn construct_prospective_leaf(
|
||||
&self,
|
||||
_existing_leaf: Word,
|
||||
_key: &LeafIndex<DEPTH>,
|
||||
value: &Word,
|
||||
) -> Word {
|
||||
*value
|
||||
}
|
||||
|
||||
fn key_to_leaf_index(key: &LeafIndex<DEPTH>) -> LeafIndex<DEPTH> {
|
||||
*key
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{
|
||||
super::{MerkleError, RpoDigest, SimpleSmt},
|
||||
NodeIndex,
|
||||
@@ -10,7 +12,6 @@ use crate::{
|
||||
},
|
||||
Word, EMPTY_WORD,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// TEST DATA
|
||||
// ================================================================================================
|
||||
@@ -443,6 +444,23 @@ fn test_simplesmt_set_subtree_entire_tree() {
|
||||
assert_eq!(tree.root(), *EmptySubtreeRoots::entry(DEPTH, 0));
|
||||
}
|
||||
|
||||
/// Tests that `EMPTY_ROOT` constant generated in the `SimpleSmt` equals to the root of the empty
|
||||
/// tree of depth 64
|
||||
#[test]
|
||||
fn test_simplesmt_check_empty_root_constant() {
|
||||
// get the root of the empty tree of depth 64
|
||||
let empty_root_64_depth = EmptySubtreeRoots::empty_hashes(64)[0];
|
||||
assert_eq!(empty_root_64_depth, SimpleSmt::<64>::EMPTY_ROOT);
|
||||
|
||||
// get the root of the empty tree of depth 32
|
||||
let empty_root_32_depth = EmptySubtreeRoots::empty_hashes(32)[0];
|
||||
assert_eq!(empty_root_32_depth, SimpleSmt::<32>::EMPTY_ROOT);
|
||||
|
||||
// get the root of the empty tree of depth 0
|
||||
let empty_root_1_depth = EmptySubtreeRoots::empty_hashes(1)[0];
|
||||
assert_eq!(empty_root_1_depth, SimpleSmt::<1>::EMPTY_ROOT);
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ impl<T: KvMap<RpoDigest, StoreNode>> MerkleStore<T> {
|
||||
/// # Errors
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in
|
||||
/// the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the
|
||||
/// store.
|
||||
pub fn get_node(&self, root: RpoDigest, index: NodeIndex) -> Result<RpoDigest, MerkleError> {
|
||||
let mut hash = root;
|
||||
|
||||
@@ -152,8 +152,8 @@ impl<T: KvMap<RpoDigest, StoreNode>> MerkleStore<T> {
|
||||
/// # Errors
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in
|
||||
/// the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the
|
||||
/// store.
|
||||
pub fn get_path(&self, root: RpoDigest, index: NodeIndex) -> Result<ValuePath, MerkleError> {
|
||||
let mut hash = root;
|
||||
let mut path = Vec::with_capacity(index.depth().into());
|
||||
@@ -421,8 +421,8 @@ impl<T: KvMap<RpoDigest, StoreNode>> MerkleStore<T> {
|
||||
/// # Errors
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in
|
||||
/// the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the
|
||||
/// store.
|
||||
pub fn set_node(
|
||||
&mut self,
|
||||
mut root: RpoDigest,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use seq_macro::seq;
|
||||
#[cfg(feature = "std")]
|
||||
use {
|
||||
super::{Deserializable, Serializable},
|
||||
alloc::boxed::Box,
|
||||
std::error::Error,
|
||||
};
|
||||
|
||||
use super::{
|
||||
DefaultMerkleStore as MerkleStore, EmptySubtreeRoots, MerkleError, MerklePath, NodeIndex,
|
||||
@@ -11,13 +17,6 @@ use crate::{
|
||||
Felt, Word, ONE, WORD_SIZE, ZERO,
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use {
|
||||
super::{Deserializable, Serializable},
|
||||
alloc::boxed::Box,
|
||||
std::error::Error,
|
||||
};
|
||||
|
||||
// TEST DATA
|
||||
// ================================================================================================
|
||||
|
||||
@@ -614,7 +613,7 @@ fn node_path_should_be_truncated_by_midtier_insert() {
|
||||
let path = store.get_path(root, index).unwrap().path;
|
||||
assert_eq!(node, result);
|
||||
assert_eq!(path.depth(), depth);
|
||||
assert!(path.verify(index.value(), result, &root));
|
||||
assert!(path.verify(index.value(), result, &root).is_ok());
|
||||
|
||||
// flip the first bit of the key and insert the second node on a different depth
|
||||
let key = key ^ (1 << 63);
|
||||
@@ -627,7 +626,7 @@ fn node_path_should_be_truncated_by_midtier_insert() {
|
||||
let path = store.get_path(root, index).unwrap().path;
|
||||
assert_eq!(node, result);
|
||||
assert_eq!(path.depth(), depth);
|
||||
assert!(path.verify(index.value(), result, &root));
|
||||
assert!(path.verify(index.value(), result, &root).is_ok());
|
||||
|
||||
// attempt to fetch a path of the second node to depth 64
|
||||
// should fail because the previously inserted node will remove its sub-tree from the set
|
||||
|
||||
@@ -7,7 +7,9 @@ pub use winter_utils::Randomizable;
|
||||
use crate::{Felt, FieldElement, Word, ZERO};
|
||||
|
||||
mod rpo;
|
||||
mod rpx;
|
||||
pub use rpo::RpoRandomCoin;
|
||||
pub use rpx::RpxRandomCoin;
|
||||
|
||||
/// Pseudo-random element generator.
|
||||
///
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use rand_core::impls;
|
||||
|
||||
use super::{Felt, FeltRng, FieldElement, RandomCoin, RandomCoinError, RngCore, Word, ZERO};
|
||||
use crate::{
|
||||
hash::rpo::{Rpo256, RpoDigest},
|
||||
utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
|
||||
};
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use rand_core::impls;
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
@@ -20,8 +22,8 @@ const HALF_RATE_WIDTH: usize = (Rpo256::RATE_RANGE.end - Rpo256::RATE_RANGE.star
|
||||
/// described in <https://eprint.iacr.org/2011/499.pdf>.
|
||||
///
|
||||
/// The simplification is related to the following facts:
|
||||
/// 1. A call to the reseed method implies one and only one call to the permutation function.
|
||||
/// This is possible because in our case we never reseed with more than 4 field elements.
|
||||
/// 1. A call to the reseed method implies one and only one call to the permutation function. This
|
||||
/// is possible because in our case we never reseed with more than 4 field elements.
|
||||
/// 2. As a result of the previous point, we don't make use of an input buffer to accumulate seed
|
||||
/// material.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
294
src/rand/rpx.rs
Normal file
294
src/rand/rpx.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use rand_core::impls;
|
||||
|
||||
use super::{Felt, FeltRng, FieldElement, RandomCoin, RandomCoinError, RngCore, Word, ZERO};
|
||||
use crate::{
|
||||
hash::rpx::{Rpx256, RpxDigest},
|
||||
utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
|
||||
};
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
|
||||
const STATE_WIDTH: usize = Rpx256::STATE_WIDTH;
|
||||
const RATE_START: usize = Rpx256::RATE_RANGE.start;
|
||||
const RATE_END: usize = Rpx256::RATE_RANGE.end;
|
||||
const HALF_RATE_WIDTH: usize = (Rpx256::RATE_RANGE.end - Rpx256::RATE_RANGE.start) / 2;
|
||||
|
||||
// RPX RANDOM COIN
|
||||
// ================================================================================================
|
||||
/// A simplified version of the `SPONGE_PRG` reseedable pseudo-random number generator algorithm
|
||||
/// described in <https://eprint.iacr.org/2011/499.pdf>.
|
||||
///
|
||||
/// The simplification is related to the following facts:
|
||||
/// 1. A call to the reseed method implies one and only one call to the permutation function. This
|
||||
/// is possible because in our case we never reseed with more than 4 field elements.
|
||||
/// 2. As a result of the previous point, we don't make use of an input buffer to accumulate seed
|
||||
/// material.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RpxRandomCoin {
|
||||
state: [Felt; STATE_WIDTH],
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl RpxRandomCoin {
|
||||
/// Returns a new [RpxRandomCoin] initialize with the specified seed.
|
||||
pub fn new(seed: Word) -> Self {
|
||||
let mut state = [ZERO; STATE_WIDTH];
|
||||
|
||||
for i in 0..HALF_RATE_WIDTH {
|
||||
state[RATE_START + i] += seed[i];
|
||||
}
|
||||
|
||||
// Absorb
|
||||
Rpx256::apply_permutation(&mut state);
|
||||
|
||||
RpxRandomCoin { state, current: RATE_START }
|
||||
}
|
||||
|
||||
/// Returns an [RpxRandomCoin] instantiated from the provided components.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `current` is smaller than 4 or greater than or equal to 12.
|
||||
pub fn from_parts(state: [Felt; STATE_WIDTH], current: usize) -> Self {
|
||||
assert!(
|
||||
(RATE_START..RATE_END).contains(¤t),
|
||||
"current value outside of valid range"
|
||||
);
|
||||
Self { state, current }
|
||||
}
|
||||
|
||||
/// Returns components of this random coin.
|
||||
pub fn into_parts(self) -> ([Felt; STATE_WIDTH], usize) {
|
||||
(self.state, self.current)
|
||||
}
|
||||
|
||||
/// Fills `dest` with random data.
|
||||
pub fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
<Self as RngCore>::fill_bytes(self, dest)
|
||||
}
|
||||
|
||||
fn draw_basefield(&mut self) -> Felt {
|
||||
if self.current == RATE_END {
|
||||
Rpx256::apply_permutation(&mut self.state);
|
||||
self.current = RATE_START;
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
self.state[self.current - 1]
|
||||
}
|
||||
}
|
||||
|
||||
// RANDOM COIN IMPLEMENTATION
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
impl RandomCoin for RpxRandomCoin {
|
||||
type BaseField = Felt;
|
||||
type Hasher = Rpx256;
|
||||
|
||||
fn new(seed: &[Self::BaseField]) -> Self {
|
||||
let digest: Word = Rpx256::hash_elements(seed).into();
|
||||
Self::new(digest)
|
||||
}
|
||||
|
||||
fn reseed(&mut self, data: RpxDigest) {
|
||||
// Reset buffer
|
||||
self.current = RATE_START;
|
||||
|
||||
// Add the new seed material to the first half of the rate portion of the RPX state
|
||||
let data: Word = data.into();
|
||||
|
||||
self.state[RATE_START] += data[0];
|
||||
self.state[RATE_START + 1] += data[1];
|
||||
self.state[RATE_START + 2] += data[2];
|
||||
self.state[RATE_START + 3] += data[3];
|
||||
|
||||
// Absorb
|
||||
Rpx256::apply_permutation(&mut self.state);
|
||||
}
|
||||
|
||||
fn check_leading_zeros(&self, value: u64) -> u32 {
|
||||
let value = Felt::new(value);
|
||||
let mut state_tmp = self.state;
|
||||
|
||||
state_tmp[RATE_START] += value;
|
||||
|
||||
Rpx256::apply_permutation(&mut state_tmp);
|
||||
|
||||
let first_rate_element = state_tmp[RATE_START].as_int();
|
||||
first_rate_element.trailing_zeros()
|
||||
}
|
||||
|
||||
fn draw<E: FieldElement<BaseField = Felt>>(&mut self) -> Result<E, RandomCoinError> {
|
||||
let ext_degree = E::EXTENSION_DEGREE;
|
||||
let mut result = vec![ZERO; ext_degree];
|
||||
for r in result.iter_mut().take(ext_degree) {
|
||||
*r = self.draw_basefield();
|
||||
}
|
||||
|
||||
let result = E::slice_from_base_elements(&result);
|
||||
Ok(result[0])
|
||||
}
|
||||
|
||||
fn draw_integers(
|
||||
&mut self,
|
||||
num_values: usize,
|
||||
domain_size: usize,
|
||||
nonce: u64,
|
||||
) -> Result<Vec<usize>, RandomCoinError> {
|
||||
assert!(domain_size.is_power_of_two(), "domain size must be a power of two");
|
||||
assert!(num_values < domain_size, "number of values must be smaller than domain size");
|
||||
|
||||
// absorb the nonce
|
||||
let nonce = Felt::new(nonce);
|
||||
self.state[RATE_START] += nonce;
|
||||
Rpx256::apply_permutation(&mut self.state);
|
||||
|
||||
// reset the buffer
|
||||
self.current = RATE_START;
|
||||
|
||||
// determine how many bits are needed to represent valid values in the domain
|
||||
let v_mask = (domain_size - 1) as u64;
|
||||
|
||||
// draw values from PRNG until we get as many unique values as specified by num_queries
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..1000 {
|
||||
// get the next pseudo-random field element
|
||||
let value = self.draw_basefield().as_int();
|
||||
|
||||
// use the mask to get a value within the range
|
||||
let value = (value & v_mask) as usize;
|
||||
|
||||
values.push(value);
|
||||
if values.len() == num_values {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if values.len() < num_values {
|
||||
return Err(RandomCoinError::FailedToDrawIntegers(num_values, values.len(), 1000));
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
}
|
||||
|
||||
// FELT RNG IMPLEMENTATION
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
impl FeltRng for RpxRandomCoin {
|
||||
fn draw_element(&mut self) -> Felt {
|
||||
self.draw_basefield()
|
||||
}
|
||||
|
||||
fn draw_word(&mut self) -> Word {
|
||||
let mut output = [ZERO; 4];
|
||||
for o in output.iter_mut() {
|
||||
*o = self.draw_basefield();
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// RNGCORE IMPLEMENTATION
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
impl RngCore for RpxRandomCoin {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.draw_basefield().as_int() as u32
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
impls::next_u64_via_u32(self)
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
impls::fill_bytes_via_next(self, dest)
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
impl Serializable for RpxRandomCoin {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
self.state.iter().for_each(|v| v.write_into(target));
|
||||
// casting to u8 is OK because `current` is always between 4 and 12.
|
||||
target.write_u8(self.current as u8);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for RpxRandomCoin {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let state = [
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
Felt::read_from(source)?,
|
||||
];
|
||||
let current = source.read_u8()? as usize;
|
||||
if !(RATE_START..RATE_END).contains(¤t) {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"current value outside of valid range".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(Self { state, current })
|
||||
}
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Deserializable, FeltRng, RpxRandomCoin, Serializable, ZERO};
|
||||
use crate::ONE;
|
||||
|
||||
#[test]
|
||||
fn test_feltrng_felt() {
|
||||
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||
let output = rpxcoin.draw_element();
|
||||
|
||||
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||
let expected = rpxcoin.draw_basefield();
|
||||
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_feltrng_word() {
|
||||
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||
let output = rpxcoin.draw_word();
|
||||
|
||||
let mut rpocoin = RpxRandomCoin::new([ZERO; 4]);
|
||||
let mut expected = [ZERO; 4];
|
||||
for o in expected.iter_mut() {
|
||||
*o = rpocoin.draw_basefield();
|
||||
}
|
||||
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_feltrng_serialization() {
|
||||
let coin1 = RpxRandomCoin::from_parts([ONE; 12], 5);
|
||||
|
||||
let bytes = coin1.to_bytes();
|
||||
let coin2 = RpxRandomCoin::read_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(coin1, coin2);
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ impl<K: Ord + Clone, V: Clone> KvMap<K, V> for BTreeMap<K, V> {
|
||||
/// The [RecordingMap] is composed of three parts:
|
||||
/// - `data`: which contains the current set of key-value pairs in the map.
|
||||
/// - `updates`: which tracks keys for which values have been changed since the map was
|
||||
/// instantiated. updates include both insertions, removals and updates of values under existing
|
||||
/// keys.
|
||||
/// instantiated. updates include both insertions, removals and updates of values under existing
|
||||
/// keys.
|
||||
/// - `trace`: which contains the key-value pairs from the original data which have been accesses
|
||||
/// since the map was instantiated.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
@@ -126,11 +126,10 @@ impl<K: Ord + Clone, V: Clone> KvMap<K, V> for RecordingMap<K, V> {
|
||||
///
|
||||
/// If the key is part of the initial data set, the key access is recorded.
|
||||
fn get(&self, key: &K) -> Option<&V> {
|
||||
self.data.get(key).map(|value| {
|
||||
self.data.get(key).inspect(|&value| {
|
||||
if !self.updates.contains(key) {
|
||||
self.trace.borrow_mut().insert(key.clone(), value.clone());
|
||||
}
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,11 +154,10 @@ impl<K: Ord + Clone, V: Clone> KvMap<K, V> for RecordingMap<K, V> {
|
||||
/// returned.
|
||||
fn insert(&mut self, key: K, value: V) -> Option<V> {
|
||||
let new_update = self.updates.insert(key.clone());
|
||||
self.data.insert(key.clone(), value).map(|old_value| {
|
||||
self.data.insert(key.clone(), value).inspect(|old_value| {
|
||||
if new_update {
|
||||
self.trace.borrow_mut().insert(key, old_value.clone());
|
||||
}
|
||||
old_value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,12 +165,11 @@ impl<K: Ord + Clone, V: Clone> KvMap<K, V> for RecordingMap<K, V> {
|
||||
///
|
||||
/// If the key exists in the data set, the old value is returned.
|
||||
fn remove(&mut self, key: &K) -> Option<V> {
|
||||
self.data.remove(key).map(|old_value| {
|
||||
self.data.remove(key).inspect(|old_value| {
|
||||
let new_update = self.updates.insert(key.clone());
|
||||
if new_update {
|
||||
self.trace.borrow_mut().insert(key.clone(), old_value.clone());
|
||||
}
|
||||
old_value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -328,7 +325,8 @@ mod tests {
|
||||
let mut map = RecordingMap::new(ITEMS.to_vec());
|
||||
assert!(map.iter().all(|(x, y)| ITEMS.contains(&(*x, *y))));
|
||||
|
||||
// when inserting entry with key that already exists the iterator should return the new value
|
||||
// when inserting entry with key that already exists the iterator should return the new
|
||||
// value
|
||||
let new_value = 5;
|
||||
map.insert(4, new_value);
|
||||
assert_eq!(map.iter().count(), ITEMS.len());
|
||||
|
||||
@@ -58,17 +58,17 @@ impl Display for HexParseError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
HexParseError::InvalidLength { expected, actual } => {
|
||||
write!(f, "Hex encoded RpoDigest must have length 66, including the 0x prefix. expected {expected} got {actual}")
|
||||
}
|
||||
write!(f, "Expected hex data to have length {expected}, including the 0x prefix. Got {actual}")
|
||||
},
|
||||
HexParseError::MissingPrefix => {
|
||||
write!(f, "Hex encoded RpoDigest must start with 0x prefix")
|
||||
}
|
||||
write!(f, "Hex encoded data must start with 0x prefix")
|
||||
},
|
||||
HexParseError::InvalidChar => {
|
||||
write!(f, "Hex encoded RpoDigest must contain characters [a-zA-Z0-9]")
|
||||
}
|
||||
write!(f, "Hex encoded data must contain characters [a-zA-Z0-9]")
|
||||
},
|
||||
HexParseError::OutOfRange => {
|
||||
write!(f, "Hex encoded values of an RpoDigest must be inside the field modulus")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user