Merge pull request #536 from pinkforest/monorepo-t1
Pulls ed25519-dalek and x25519-dalek in. We're now one happy monorepo.
This commit is contained in:
commit
f789810e33
.github/workflows
CODE_OF_CONDUCT.mdCONTRIBUTING.mdCargo.tomlREADME.mdcurve25519-dalek
CHANGELOG.mdCargo.tomlLICENSEMakefileREADME.md
benches
build.rsdocs
src
backend
constants.rsdiagnostics.rsedwards.rsfield.rslib.rsmacros.rsmontgomery.rsristretto.rsscalar.rstraits.rswindow.rstests
vendor
ed25519-dalek
.gitignore.travis.ymlCHANGELOG.mdCargo.lockCargo.tomlLICENSEREADME.mdTESTVECTORSVALIDATIONVECTORS
benches
docs/assets
src
tests
44
.github/workflows/cross.yml
vendored
Normal file
44
.github/workflows/cross.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Cross
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
|
||||
test-cross:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# ARM32
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
rust: stable
|
||||
|
||||
# ARM64
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rust: stable
|
||||
|
||||
# PPC32
|
||||
- target: powerpc-unknown-linux-gnu
|
||||
rust: stable
|
||||
|
||||
# TODO: We only test x/ed/curve for cross as derive is platform specifics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: ${{ matrix.deps }}
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: RustCrypto/actions/cross-install@master
|
||||
- run: cross test -p curve25519-dalek --release --target ${{ matrix.target }}
|
||||
- run: cross test -p ed25519-dalek --release --target ${{ matrix.target }}
|
||||
- run: cross test -p x25519-dalek --release --target ${{ matrix.target }}
|
117
.github/workflows/curve25519-dalek.yml
vendored
Normal file
117
.github/workflows/curve25519-dalek.yml
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
name: curve25519 Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
paths: 'curve25519-dalek/**'
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
paths: 'curve25519-dalek/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: curve25519-dalek
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
|
||||
test-fiat:
|
||||
name: Test fiat backend
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
|
||||
run: cargo test --target ${{ matrix.target }}
|
||||
|
||||
test-serial:
|
||||
name: Test serial backend
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_backend="serial"'
|
||||
run: cargo test --target ${{ matrix.target }}
|
||||
|
||||
build-script:
|
||||
name: Test Build Script
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu,i686-unknown-linux-gnu
|
||||
- run: bash tests/build_tests.sh
|
||||
|
||||
test-simd-nightly:
|
||||
name: Test simd backend (nightly)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- env:
|
||||
# This will:
|
||||
# 1) build all of the x86_64 SIMD code,
|
||||
# 2) run all of the SIMD-specific tests that the test runner supports,
|
||||
# 3) run all of the normal tests using the best available SIMD backend.
|
||||
# This should automatically pick up the simd backend in a x84_64 runner
|
||||
RUSTFLAGS: '-C target_cpu=native'
|
||||
run: cargo test --target x86_64-unknown-linux-gnu
|
||||
|
||||
test-simd-stable:
|
||||
name: Test simd backend (stable)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- env:
|
||||
# This will run AVX2-specific tests and run all of the normal tests
|
||||
# with the AVX2 backend, even if the runner supports AVX512.
|
||||
# This should automatically pick up the simd backend in a x86_64 runner
|
||||
# It should pick AVX2 due to stable toolchain used since AVX512 requires nigthly
|
||||
RUSTFLAGS: '-C target_feature=+avx2'
|
||||
run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize --target x86_64-unknown-linux-gnu
|
||||
|
||||
msrv:
|
||||
name: Current MSRV is 1.60.0
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# First run `cargo +nightly -Z minimal-verisons check` in order to get a
|
||||
# Cargo.lock with the oldest possible deps
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo -Z minimal-versions check --no-default-features --features serde
|
||||
# Now check that `cargo build` works with respect to the oldest possible
|
||||
# deps and the stated MSRV
|
||||
- uses: dtolnay/rust-toolchain@1.60.0
|
||||
- run: cargo build --no-default-features --features serde
|
||||
# Also make sure the AVX2 build works
|
||||
- run: cargo build --target x86_64-unknown-linux-gnu
|
36
.github/workflows/ed25519-dalek.yml
vendored
Normal file
36
.github/workflows/ed25519-dalek.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: ed25519 Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
paths: 'ed25519-dalek/**'
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
paths: 'ed25519-dalek/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ed25519-dalek
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
RUSTDOCFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
|
||||
msrv:
|
||||
name: Current MSRV is 1.60.0
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# First delete the checked-in `Cargo.lock`. We're going to regenerate it
|
||||
- run: rm Cargo.lock
|
||||
# Now run `cargo +nightly -Z minimal-verisons check` in order to get a
|
||||
# Cargo.lock with the oldest possible deps
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo -Z minimal-versions check --no-default-features --features serde
|
||||
# Now check that `cargo build` works with respect to the oldest possible
|
||||
# deps and the stated MSRV
|
||||
- uses: dtolnay/rust-toolchain@1.60.0
|
||||
- run: cargo build
|
35
.github/workflows/no_std.yml
vendored
Normal file
35
.github/workflows/no_std.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: no_std
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
|
||||
build-nostd:
|
||||
name: Build on no_std target (thumbv7em-none-eabi)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- crate: curve25519-dalek
|
||||
- crate: ed25519-dalek
|
||||
- crate: x25519-dalek
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: thumbv7em-none-eabi
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
# No default features build
|
||||
- name: no_std / no feat ${{ matrix.crate }}
|
||||
run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --no-default-features
|
||||
- name: no_std / cargo hack ${{ matrix.crate }}
|
||||
run: cargo hack build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std,getrandom
|
230
.github/workflows/rust.yml
vendored
230
.github/workflows/rust.yml
vendored
@ -1,230 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
test-auto:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features alloc
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features digest
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features precomputed-tables
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features rand_core
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features serde
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features --features zeroize
|
||||
- run: cargo test --target ${{ matrix.target }}
|
||||
- run: cargo test --target ${{ matrix.target }} --features digest
|
||||
- run: cargo test --target ${{ matrix.target }} --features rand_core
|
||||
- run: cargo test --target ${{ matrix.target }} --features serde
|
||||
|
||||
test-fiat:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
|
||||
run: cargo test --target ${{ matrix.target }}
|
||||
|
||||
test-serial:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_backend="serial"'
|
||||
run: cargo test --target ${{ matrix.target }}
|
||||
|
||||
build-script:
|
||||
name: Test Build Script
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu,i686-unknown-linux-gnu
|
||||
- run: bash tests/build_tests.sh
|
||||
|
||||
build-nostd:
|
||||
name: Build on no_std target (thumbv7em-none-eabi)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: thumbv7em-none-eabi
|
||||
- run: cargo build --target thumbv7em-none-eabi --release --no-default-features
|
||||
- run: cargo build --target thumbv7em-none-eabi --release
|
||||
- run: cargo build --target thumbv7em-none-eabi --release --features serde
|
||||
|
||||
test-simd-nightly:
|
||||
name: Test simd backend (nightly)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- env:
|
||||
# This will:
|
||||
# 1) build all of the x86_64 SIMD code,
|
||||
# 2) run all of the SIMD-specific tests that the test runner supports,
|
||||
# 3) run all of the normal tests using the best available SIMD backend.
|
||||
# This should automatically pick up the simd backend in a x84_64 runner
|
||||
RUSTFLAGS: '-C target_cpu=native'
|
||||
run: cargo test --target x86_64-unknown-linux-gnu
|
||||
|
||||
test-simd-stable:
|
||||
name: Test simd backend (stable)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- env:
|
||||
# This will run AVX2-specific tests and run all of the normal tests
|
||||
# with the AVX2 backend, even if the runner supports AVX512.
|
||||
# This should automatically pick up the simd backend in a x86_64 runner
|
||||
# It should pick AVX2 due to stable toolchain used since AVX512 requires nigthly
|
||||
RUSTFLAGS: '-C target_feature=+avx2'
|
||||
run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize --target x86_64-unknown-linux-gnu
|
||||
|
||||
build-docs:
|
||||
name: Build docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: make doc
|
||||
- run: make doc-internal
|
||||
|
||||
cross:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# ARM32
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
rust: stable
|
||||
|
||||
# ARM64
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rust: stable
|
||||
|
||||
# PPC32
|
||||
- target: powerpc-unknown-linux-gnu
|
||||
rust: stable
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: ${{ matrix.deps }}
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: RustCrypto/actions/cross-install@master
|
||||
- run: cross test --release --target ${{ matrix.target }}
|
||||
|
||||
nightly:
|
||||
name: Test nightly compiler
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo test
|
||||
|
||||
clippy:
|
||||
name: Check that clippy is happy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- run: cargo clippy --target x86_64-unknown-linux-gnu
|
||||
|
||||
rustfmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
msrv:
|
||||
name: Current MSRV is 1.60.0
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# First run `cargo +nightly -Z minimal-verisons check` in order to get a
|
||||
# Cargo.lock with the oldest possible deps
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo -Z minimal-versions check --no-default-features --features serde
|
||||
# Now check that `cargo build` works with respect to the oldest possible
|
||||
# deps and the stated MSRV
|
||||
- uses: dtolnay/rust-toolchain@1.60.0
|
||||
- run: cargo build --no-default-features --features serde
|
||||
# Also make sure the AVX2 build works
|
||||
- run: cargo build --target x86_64-unknown-linux-gnu
|
||||
|
||||
bench:
|
||||
name: Check that benchmarks compile
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Build u32 bench
|
||||
env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_bits="32"'
|
||||
run: cargo build --benches
|
||||
- name: Build u64 bench
|
||||
env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_bits="64"'
|
||||
run: cargo build --benches
|
||||
- name: Build default (host native) bench
|
||||
run: cargo build --benches
|
88
.github/workflows/workspace.yml
vendored
Normal file
88
.github/workflows/workspace.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
name: All
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
test-stable:
|
||||
name: Test 32/64 bit stable
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# 32-bit target
|
||||
- target: i686-unknown-linux-gnu
|
||||
deps: sudo apt update && sudo apt install gcc-multilib
|
||||
|
||||
# 64-bit target
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: ${{ matrix.deps }}
|
||||
- run: cargo test --target ${{ matrix.target }} --no-default-features
|
||||
- run: cargo test --target ${{ matrix.target }}
|
||||
- run: cargo test --target ${{ matrix.target }} --all-features
|
||||
|
||||
test-nightly:
|
||||
name: Test Nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo test
|
||||
|
||||
bench:
|
||||
name: Check that benchmarks compile
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Build u32 bench
|
||||
env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_bits="32"'
|
||||
run: cargo build --benches
|
||||
- name: Build u64 bench
|
||||
env:
|
||||
RUSTFLAGS: '--cfg curve25519_dalek_bits="64"'
|
||||
run: cargo build --benches
|
||||
- name: Build default (host native) bench
|
||||
run: cargo build --benches
|
||||
|
||||
clippy:
|
||||
name: Check that clippy is happy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- run: cargo clippy --target x86_64-unknown-linux-gnu
|
||||
|
||||
rustfmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
doc:
|
||||
name: Check docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- run: cargo doc --all-features
|
36
.github/workflows/x25519-dalek.yml
vendored
Normal file
36
.github/workflows/x25519-dalek.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: x25519 Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
paths: 'x25519-dalek/**'
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
paths: 'x25519-dalek/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: x25519-dalek
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: '-D warnings'
|
||||
RUSTDOCFLAGS: '-D warnings'
|
||||
|
||||
jobs:
|
||||
|
||||
msrv:
|
||||
name: Current MSRV is 1.60.0
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# First delete the checked-in `Cargo.lock`. We're going to regenerate it
|
||||
- run: rm Cargo.lock
|
||||
# Now run `cargo +nightly -Z minimal-verisons check` in order to get a
|
||||
# Cargo.lock with the oldest possible deps
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo -Z minimal-versions check --no-default-features --features serde
|
||||
# Now check that `cargo build` works with respect to the oldest possible
|
||||
# deps and the stated MSRV
|
||||
- uses: dtolnay/rust-toolchain@1.60.0
|
||||
- run: cargo build
|
@ -1,8 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html),
|
||||
with the following additional clauses:
|
||||
|
||||
* We respect the rights to privacy and anonymity for contributors and people in
|
||||
the community. If someone wishes to contribute under a pseudonym different to
|
||||
their primary identity, that wish is to be respected by all contributors.
|
@ -12,7 +12,7 @@ Patches are welcomed as pull requests on
|
||||
email (preferably sent to all of the authors listed in `Cargo.toml`).
|
||||
|
||||
All issues on curve25519-dalek are mentored, if you want help with a bug just
|
||||
ask @isislovecruft or @hdevalence.
|
||||
ask @rozbb or @tarcieri.
|
||||
|
||||
Some issues are easier than others. The `easy` label can be used to find the
|
||||
easy issues. If you want to work on an issue, please leave a comment so that we
|
||||
|
77
Cargo.toml
77
Cargo.toml
@ -1,73 +1,12 @@
|
||||
[package]
|
||||
name = "curve25519-dalek"
|
||||
# Before incrementing:
|
||||
# - update CHANGELOG
|
||||
# - update README if required by semver
|
||||
# - if README was updated, also update module documentation in src/lib.rs
|
||||
version = "4.0.0-rc.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.60.0"
|
||||
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
|
||||
"Henry de Valence <hdevalence@hdevalence.ca>"]
|
||||
readme = "README.md"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/dalek-cryptography/curve25519-dalek"
|
||||
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
|
||||
documentation = "https://docs.rs/curve25519-dalek"
|
||||
categories = ["cryptography", "no-std"]
|
||||
keywords = ["cryptography", "crypto", "ristretto", "curve25519", "ristretto255"]
|
||||
description = "A pure-Rust implementation of group operations on ristretto255 and Curve25519"
|
||||
exclude = [
|
||||
"**/.gitignore",
|
||||
".gitignore",
|
||||
[workspace]
|
||||
members = [
|
||||
"curve25519-dalek",
|
||||
"curve25519-dalek-derive",
|
||||
"ed25519-dalek",
|
||||
"x25519-dalek"
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
|
||||
"--cfg", "docsrs",
|
||||
]
|
||||
features = ["serde", "rand_core", "digest", "legacy_compatibility"]
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
bincode = "1"
|
||||
criterion = { version = "0.4.0", features = ["html_reports"] }
|
||||
hex = "0.4.2"
|
||||
rand = "0.8"
|
||||
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
||||
|
||||
[build-dependencies]
|
||||
platforms = "3.0.2"
|
||||
rustc_version = "0.4.0"
|
||||
|
||||
[[bench]]
|
||||
name = "dalek_benchmarks"
|
||||
harness = false
|
||||
required-features = ["alloc", "rand_core"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
rand_core = { version = "0.6.4", default-features = false, optional = true }
|
||||
digest = { version = "0.10", default-features = false, optional = true }
|
||||
subtle = { version = "2.3.0", default-features = false }
|
||||
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
|
||||
zeroize = { version = "1", default-features = false, optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
cpufeatures = "0.2.6"
|
||||
|
||||
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
|
||||
fiat-crypto = "0.1.19"
|
||||
|
||||
[features]
|
||||
default = ["alloc", "precomputed-tables", "zeroize"]
|
||||
alloc = ["zeroize?/alloc"]
|
||||
precomputed-tables = []
|
||||
legacy_compatibility = []
|
||||
|
||||
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
|
||||
curve25519-dalek-derive = { version = "0.1", path = "curve25519-dalek-derive" }
|
||||
resolver = "2"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
|
||||
|
322
README.md
322
README.md
@ -1,6 +1,3 @@
|
||||
|
||||
# curve25519-dalek [](https://crates.io/crates/curve25519-dalek) [](https://docs.rs/curve25519-dalek) [](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/rust.yml)
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="dalek-cryptography logo: a dalek with edwards curves as sparkles coming out of its radar-schnozzley blaster thingies"
|
||||
@ -8,317 +5,28 @@
|
||||
src="https://cdn.jsdelivr.net/gh/dalek-cryptography/curve25519-dalek/docs/assets/dalek-logo-clear.png"/>
|
||||
</p>
|
||||
|
||||
**A pure-Rust implementation of group operations on Ristretto and Curve25519.**
|
||||
# Dalek elliptic curve cryptography
|
||||
|
||||
`curve25519-dalek` is a library providing group operations on the Edwards and
|
||||
Montgomery forms of Curve25519, and on the prime-order Ristretto group.
|
||||
This repo contains pure-Rust crates for elliptic curve cryptography:
|
||||
|
||||
`curve25519-dalek` is not intended to provide implementations of any particular
|
||||
crypto protocol. Rather, implementations of those protocols (such as
|
||||
[`x25519-dalek`][x25519-dalek] and [`ed25519-dalek`][ed25519-dalek]) should use
|
||||
`curve25519-dalek` as a library.
|
||||
|
||||
`curve25519-dalek` is intended to provide a clean and safe _mid-level_ API for use
|
||||
implementing a wide range of ECC-based crypto protocols, such as key agreement,
|
||||
signatures, anonymous credentials, rangeproofs, and zero-knowledge proof
|
||||
systems.
|
||||
| Crate | Description | Crates.io | Docs | CI |
|
||||
-------------------------------------------|----------------|-----------|------|-------
|
||||
| [`curve25519-dalek`](./curve25519-dalek) | A library for arithmetic over the Curve25519 and Ristretto elliptic curves and their associated scalars. | [](https://crates.io/crates/curve25519-dalek) | [](https://docs.rs/curve25519-dalek) | [](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/rust.yml) |
|
||||
| [`ed25519-dalek`](./ed25519-dalek) | An implementation of the EdDSA digital signature scheme over Curve25519. | [](https://crates.io/crates/ed25519-dalek) | [](https://docs.rs/ed25519-dalek) | [](https://github.com/dalek-cryptography/ed25519-dalek/actions/workflows/rust.yml) |
|
||||
| [`x25519-dalek`](./x25519-dalek) | An implementation of elliptic curve Diffie-Hellman key exchange over Curve25519. | [](https://crates.io/crates/x25519-dalek) | [](https://docs.rs/x25519-dalek) | [](https://travis-ci.org/dalek-cryptography/x25519-dalek) |
|
||||
|
||||
In particular, `curve25519-dalek` implements Ristretto, which constructs a
|
||||
prime-order group from a non-prime-order Edwards curve. This provides the
|
||||
speed and safety benefits of Edwards curve arithmetic, without the pitfalls of
|
||||
cofactor-related abstraction mismatches.
|
||||
|
||||
# Use
|
||||
|
||||
## Stable
|
||||
|
||||
To import `curve25519-dalek`, add the following to the dependencies section of
|
||||
your project's `Cargo.toml`:
|
||||
```toml
|
||||
curve25519-dalek = "3"
|
||||
```
|
||||
|
||||
## Beta
|
||||
|
||||
To use the latest prerelease (see changes [below](#breaking-changes-in-400)),
|
||||
use the following line in your project's `Cargo.toml`:
|
||||
```toml
|
||||
curve25519-dalek = "4.0.0-rc.3"
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default? | Description |
|
||||
| :--- | :---: | :--- |
|
||||
| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. |
|
||||
| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. |
|
||||
| `precomputed-tables` | ✓ | Includes precomputed basepoint multiplication tables. This speeds up `EdwardsPoint::mul_base` and `RistrettoPoint::mul_base` by ~4x, at the cost of ~30KB added to the code size. |
|
||||
| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
|
||||
| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
|
||||
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
|
||||
| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. |
|
||||
|
||||
To disable the default features when using `curve25519-dalek` as a dependency,
|
||||
add `default-features = false` to the dependency in your `Cargo.toml`. To
|
||||
disable it when running `cargo`, add the `--no-default-features` CLI flag.
|
||||
|
||||
## Major Version API Changes
|
||||
|
||||
Breaking changes for each major version release can be found in
|
||||
[`CHANGELOG.md`](CHANGELOG.md), under the "Breaking changes" subheader. The
|
||||
latest breaking changes in high level are below:
|
||||
|
||||
### Breaking changes in 4.0.0
|
||||
|
||||
* Update the MSRV from 1.41 to 1.60
|
||||
* Provide SemVer policy
|
||||
* Make `digest` and `rand_core` optional features
|
||||
* Remove `std` and `nightly` features
|
||||
* Replace backend selection - See [CHANGELOG.md](CHANGELOG.md) and [backends](#backends)
|
||||
* Replace methods `Scalar::{zero, one}` with constants `Scalar::{ZERO, ONE}`
|
||||
* `Scalar::from_canonical_bytes` now returns `CtOption`
|
||||
* `Scalar::is_canonical` now returns `Choice`
|
||||
* Remove `Scalar::from_bytes_clamped` and `Scalar::reduce`
|
||||
* Deprecate and feature-gate `Scalar::from_bits` behind `legacy_compatibility`
|
||||
* Deprecate `EdwardsPoint::hash_from_bytes` and rename it
|
||||
`EdwardsPoint::nonspec_map_to_curve`
|
||||
* Require including a new trait, `use curve25519_dalek::traits::BasepointTable`
|
||||
whenever using `EdwardsBasepointTable` or `RistrettoBasepointTable`
|
||||
|
||||
This release also does a lot of dependency updates and relaxations to unblock upstream build issues.
|
||||
|
||||
# Backends
|
||||
|
||||
Curve arithmetic is implemented and used by one of the following backends:
|
||||
|
||||
| Backend | Selection | Implementation | Bits / Word sizes |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `serial` | Automatic | An optimized, non-parllel implementation | `32` and `64` |
|
||||
| `fiat` | Manual | Formally verified field arithmetic from [fiat-crypto] | `32` and `64` |
|
||||
| `simd` | Automatic | Intel AVX2 / AVX512 IFMA accelerated backend | `64` only |
|
||||
|
||||
At runtime, `curve25519-dalek` selects an arithmetic backend from the set of backends it was compiled to support. For Intel x86-64 targets, unless otherwise specified, it will build itself with `simd` support, and default to `serial` at runtime if the appropriate CPU features aren't detected. See [SIMD backend] for more details.
|
||||
|
||||
In the future, `simd` backend may be extended to cover more instruction sets. This change will be non-breaking as this is considered as implementation detail.
|
||||
|
||||
## Manual Backend Override
|
||||
|
||||
You can force the crate to compile with specific backend support, e.g., `serial` for x86-64 targets to save code size, or `fiat` to force the runtime to use verified code. To do this, set the environment variable:
|
||||
```sh
|
||||
RUSTFLAGS='--cfg curve25519_dalek_backend="BACKEND"'
|
||||
```
|
||||
Equivalently, you can write to
|
||||
`~/.cargo/config`:
|
||||
```toml
|
||||
[build]
|
||||
rustflags = ['--cfg=curve25519_dalek_backend="BACKEND"']
|
||||
```
|
||||
More info [here](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags).
|
||||
|
||||
Note for contributors: The target backends are not entirely independent of each
|
||||
other. The [SIMD backend] directly depends on parts of the serial backend to
|
||||
function.
|
||||
|
||||
## Bits / Word size
|
||||
|
||||
`curve25519-dalek` will automatically choose the word size for the `fiat` and
|
||||
`serial` backends, based on the build target.
|
||||
For example, building for a 64-bit machine, the default 64 bit word size is
|
||||
automatically chosen when either the `serial` or `fiat` backend is selected.
|
||||
|
||||
In some targets it might be required to override the word size for better
|
||||
performance.
|
||||
Backend word size can be overridden for `serial` and `fiat` by setting the
|
||||
environment variable:
|
||||
```sh
|
||||
RUSTFLAGS='--cfg curve25519_dalek_bits="SIZE"'
|
||||
```
|
||||
`SIZE` is `32` or `64`. As in the above section, this can also be placed
|
||||
in `~/.cargo/config`.
|
||||
|
||||
Note: The [SIMD backend] requires a word size of 64 bits. Attempting to set bits=32 and backend=`simd` will yield a compile error.
|
||||
|
||||
### Cross-compilation
|
||||
|
||||
Because backend selection is done by target, cross-compiling will select the correct word size automatically. For example, if a x86-64 Linux machine runs the following commands, `curve25519-dalek` will be compiled with the 32-bit `serial` backend.
|
||||
```console
|
||||
$ sudo apt install gcc-multilib # (or whatever package manager you use)
|
||||
$ rustup target add i686-unknown-linux-gnu
|
||||
$ cargo build --target i686-unknown-linux-gnu
|
||||
```
|
||||
|
||||
## SIMD backend
|
||||
|
||||
The specific SIMD backend (AVX512 / AVX2 / `serial` default) is selected automatically at runtime, depending on the currently available CPU features, and whether Rust nightly is being used for compilation. The precise conditions are specified below.
|
||||
|
||||
For a given CPU feature, you can also specify an appropriate `-C target_feature` to build a binary which assumes the required SIMD instructions are always available. Don't do this if you don't have a good reason.
|
||||
|
||||
| Backend | `RUSTFLAGS` | Requires nightly? |
|
||||
| :--- | :--- | :--- |
|
||||
| avx2 | `-C target_feature=+avx2` | no |
|
||||
| avx512 | `-C target_feature=+avx512ifma,+avx512vl` | yes |
|
||||
|
||||
If compiled on a non-nightly compiler, `curve25519-dalek` will not include AVX512 code, and therefore will never select it at runtime.
|
||||
|
||||
# Documentation
|
||||
|
||||
The semver-stable, public-facing `curve25519-dalek` API is documented [here][docs].
|
||||
|
||||
## Building Docs Locally
|
||||
|
||||
The `curve25519-dalek` documentation requires a custom HTML header to include
|
||||
KaTeX for math support. Unfortunately `cargo doc` does not currently support
|
||||
this, but docs can be built using
|
||||
```sh
|
||||
make doc
|
||||
```
|
||||
for regular docs, and
|
||||
```sh
|
||||
make doc-internal
|
||||
```
|
||||
for docs that include private items.
|
||||
|
||||
# Maintenance Policies
|
||||
|
||||
All on-by-default features of this library are covered by
|
||||
[semantic versioning][semver] (SemVer). SemVer exemptions are outlined below
|
||||
for MSRV and public API.
|
||||
|
||||
## Minimum Supported Rust Version
|
||||
|
||||
| Releases | MSRV |
|
||||
| :--- |:-------|
|
||||
| 4.x | 1.60.0 |
|
||||
| 3.x | 1.41.0 |
|
||||
|
||||
From 4.x and on, MSRV changes will be accompanied by a minor version bump.
|
||||
|
||||
## Public API SemVer Exemptions
|
||||
|
||||
Breaking changes to SemVer exempted components affecting the public API will be accompanied by
|
||||
_some_ version bump. Below are the specific policies:
|
||||
|
||||
| Releases | Public API Component(s) | Policy |
|
||||
| :--- | :--- | :--- |
|
||||
| 4.x | Dependencies `digest` and `rand_core` | Minor SemVer bump |
|
||||
|
||||
# Safety
|
||||
|
||||
The `curve25519-dalek` types are designed to make illegal states
|
||||
unrepresentable. For example, any instance of an `EdwardsPoint` is
|
||||
guaranteed to hold a point on the Edwards curve, and any instance of a
|
||||
`RistrettoPoint` is guaranteed to hold a valid point in the Ristretto
|
||||
group.
|
||||
|
||||
All operations are implemented using constant-time logic (no
|
||||
secret-dependent branches, no secret-dependent memory accesses),
|
||||
unless specifically marked as being variable-time code.
|
||||
We believe that our constant-time logic is lowered to constant-time
|
||||
assembly, at least on `x86_64` targets.
|
||||
|
||||
As an additional guard against possible future compiler optimizations,
|
||||
the `subtle` crate places an optimization barrier before every
|
||||
conditional move or assignment. More details can be found in [the
|
||||
documentation for the `subtle` crate][subtle_doc].
|
||||
|
||||
Some functionality (e.g., multiscalar multiplication or batch
|
||||
inversion) requires heap allocation for temporary buffers. All
|
||||
heap-allocated buffers of potentially secret data are explicitly
|
||||
zeroed before release.
|
||||
|
||||
However, we do not attempt to zero stack data, for two reasons.
|
||||
First, it's not possible to do so correctly: we don't have control
|
||||
over stack allocations, so there's no way to know how much data to
|
||||
wipe. Second, because `curve25519-dalek` provides a mid-level API,
|
||||
the correct place to start zeroing stack data is likely not at the
|
||||
entrypoints of `curve25519-dalek` functions, but at the entrypoints of
|
||||
functions in other crates.
|
||||
|
||||
The implementation is memory-safe, and contains no significant
|
||||
`unsafe` code. The SIMD backend uses `unsafe` internally to call SIMD
|
||||
intrinsics. These are marked `unsafe` only because invoking them on an
|
||||
inappropriate CPU would cause `SIGILL`, but the entire backend is only
|
||||
invoked when the appropriate CPU features are detected at runtime, or
|
||||
when the whole program is compiled with the appropriate `target_feature`s.
|
||||
|
||||
# Performance
|
||||
|
||||
Benchmarks are run using [`criterion.rs`][criterion]:
|
||||
|
||||
```sh
|
||||
cargo bench --features "rand_core"
|
||||
export RUSTFLAGS='-C target_cpu=native'
|
||||
cargo +nightly bench --features "rand_core"
|
||||
```
|
||||
|
||||
Performance is a secondary goal behind correctness, safety, and
|
||||
clarity, but we aim to be competitive with other implementations.
|
||||
|
||||
# FFI
|
||||
|
||||
Unfortunately, we have no plans to add FFI to `curve25519-dalek` directly. The
|
||||
reason is that we use Rust features to provide an API that maintains safety
|
||||
invariants, which are not possible to maintain across an FFI boundary. For
|
||||
instance, as described in the _Safety_ section above, invalid points are
|
||||
impossible to construct, and this would not be the case if we exposed point
|
||||
operations over FFI.
|
||||
|
||||
However, `curve25519-dalek` is designed as a *mid-level* API, aimed at
|
||||
implementing other, higher-level primitives. Instead of providing FFI at the
|
||||
mid-level, our suggestion is to implement the higher-level primitive (a
|
||||
signature, PAKE, ZKP, etc) in Rust, using `curve25519-dalek` as a dependency,
|
||||
and have that crate provide a minimal, byte-buffer-oriented FFI specific to
|
||||
that primitive.
|
||||
There is also the [`curve25519-dalek-derive`](./curve25519-dalek-derive) crate, which is just a helper crate with some macros that make curve25519-dalek easier to write.
|
||||
|
||||
# Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md][contributing].
|
||||
Please see [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
||||
|
||||
# About
|
||||
# Code of Conduct
|
||||
|
||||
**SPOILER ALERT:** *The Twelfth Doctor's first encounter with the Daleks is in
|
||||
his second full episode, "Into the Dalek". A beleaguered ship of the "Combined
|
||||
Galactic Resistance" has discovered a broken Dalek that has turned "good",
|
||||
desiring to kill all other Daleks. The Doctor, Clara and a team of soldiers
|
||||
are miniaturized and enter the Dalek, which the Doctor names Rusty. They
|
||||
repair the damage, but accidentally restore it to its original nature, causing
|
||||
it to go on the rampage and alert the Dalek fleet to the whereabouts of the
|
||||
rebel ship. However, the Doctor manages to return Rusty to its previous state
|
||||
by linking his mind with the Dalek's: Rusty shares the Doctor's view of the
|
||||
universe's beauty, but also his deep hatred of the Daleks. Rusty destroys the
|
||||
other Daleks and departs the ship, determined to track down and bring an end
|
||||
to the Dalek race.*
|
||||
We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html),
|
||||
with the following additional clauses:
|
||||
|
||||
`curve25519-dalek` is authored by Isis Agora Lovecruft and Henry de Valence.
|
||||
|
||||
Portions of this library were originally a port of [Adam Langley's
|
||||
Golang ed25519 library](https://github.com/agl/ed25519), which was in
|
||||
turn a port of the reference `ref10` implementation. Most of this code,
|
||||
including the 32-bit field arithmetic, has since been rewritten.
|
||||
|
||||
The fast `u32` and `u64` scalar arithmetic was implemented by Andrew Moon, and
|
||||
the addition chain for scalar inversion was provided by Brian Smith. The
|
||||
optimised batch inversion was contributed by Sean Bowe and Daira Hopwood.
|
||||
|
||||
The `no_std` and `zeroize` support was contributed by Tony Arcieri.
|
||||
|
||||
The formally verified `fiat_backend` integrates Rust code generated by the
|
||||
[Fiat Crypto project](https://github.com/mit-plv/fiat-crypto) and was
|
||||
contributed by François Garillot.
|
||||
|
||||
Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg,
|
||||
Pratyush Mishra, Michael Rosenberg, @pinkforest, and countless others for their
|
||||
contributions.
|
||||
|
||||
[ed25519-dalek]: https://github.com/dalek-cryptography/ed25519-dalek
|
||||
[x25519-dalek]: https://github.com/dalek-cryptography/x25519-dalek
|
||||
[docs]: https://docs.rs/curve25519-dalek/
|
||||
[contributing]: https://github.com/dalek-cryptography/curve25519-dalek/blob/master/CONTRIBUTING.md
|
||||
[criterion]: https://github.com/japaric/criterion.rs
|
||||
[parallel_doc]: https://docs.rs/curve25519-dalek/latest/curve25519_dalek/backend/vector/index.html
|
||||
[subtle_doc]: https://docs.rs/subtle
|
||||
[fiat-crypto]: https://github.com/mit-plv/fiat-crypto
|
||||
[semver]: https://semver.org/spec/v2.0.0.html
|
||||
[rngcorestd]: https://github.com/rust-random/rand/tree/7aa25d577e2df84a5156f824077bb7f6bdf28d97/rand_core#crate-features
|
||||
[zeroize-trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
|
||||
[SIMD backend]: #simd-backend
|
||||
* We respect the rights to privacy and anonymity for contributors and people in
|
||||
the community. If someone wishes to contribute under a pseudonym different to
|
||||
their primary identity, that wish is to be respected by all contributors.
|
||||
|
70
curve25519-dalek/Cargo.toml
Normal file
70
curve25519-dalek/Cargo.toml
Normal file
@ -0,0 +1,70 @@
|
||||
[package]
|
||||
name = "curve25519-dalek"
|
||||
# Before incrementing:
|
||||
# - update CHANGELOG
|
||||
# - update README if required by semver
|
||||
# - if README was updated, also update module documentation in src/lib.rs
|
||||
version = "4.0.0-rc.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.60.0"
|
||||
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
|
||||
"Henry de Valence <hdevalence@hdevalence.ca>"]
|
||||
readme = "README.md"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/dalek-cryptography/curve25519-dalek"
|
||||
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
|
||||
documentation = "https://docs.rs/curve25519-dalek"
|
||||
categories = ["cryptography", "no-std"]
|
||||
keywords = ["cryptography", "crypto", "ristretto", "curve25519", "ristretto255"]
|
||||
description = "A pure-Rust implementation of group operations on ristretto255 and Curve25519"
|
||||
exclude = [
|
||||
"**/.gitignore",
|
||||
".gitignore",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
|
||||
"--cfg", "docsrs",
|
||||
]
|
||||
features = ["serde", "rand_core", "digest", "legacy_compatibility"]
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
bincode = "1"
|
||||
criterion = { version = "0.4.0", features = ["html_reports"] }
|
||||
hex = "0.4.2"
|
||||
rand = "0.8"
|
||||
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
||||
|
||||
[build-dependencies]
|
||||
platforms = "3.0.2"
|
||||
rustc_version = "0.4.0"
|
||||
|
||||
[[bench]]
|
||||
name = "dalek_benchmarks"
|
||||
harness = false
|
||||
required-features = ["alloc", "rand_core"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
rand_core = { version = "0.6.4", default-features = false, optional = true }
|
||||
digest = { version = "0.10", default-features = false, optional = true }
|
||||
subtle = { version = "2.3.0", default-features = false }
|
||||
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
|
||||
zeroize = { version = "1", default-features = false, optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
cpufeatures = "0.2.6"
|
||||
|
||||
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
|
||||
fiat-crypto = "0.1.19"
|
||||
|
||||
[features]
|
||||
default = ["alloc", "precomputed-tables", "zeroize"]
|
||||
alloc = ["zeroize?/alloc"]
|
||||
precomputed-tables = []
|
||||
legacy_compatibility = []
|
||||
|
||||
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
|
||||
curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" }
|
324
curve25519-dalek/README.md
Normal file
324
curve25519-dalek/README.md
Normal file
@ -0,0 +1,324 @@
|
||||
|
||||
# curve25519-dalek [](https://crates.io/crates/curve25519-dalek) [](https://docs.rs/curve25519-dalek) [](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/rust.yml)
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="dalek-cryptography logo: a dalek with edwards curves as sparkles coming out of its radar-schnozzley blaster thingies"
|
||||
width="200px"
|
||||
src="https://cdn.jsdelivr.net/gh/dalek-cryptography/curve25519-dalek/docs/assets/dalek-logo-clear.png"/>
|
||||
</p>
|
||||
|
||||
**A pure-Rust implementation of group operations on Ristretto and Curve25519.**
|
||||
|
||||
`curve25519-dalek` is a library providing group operations on the Edwards and
|
||||
Montgomery forms of Curve25519, and on the prime-order Ristretto group.
|
||||
|
||||
`curve25519-dalek` is not intended to provide implementations of any particular
|
||||
crypto protocol. Rather, implementations of those protocols (such as
|
||||
[`x25519-dalek`][x25519-dalek] and [`ed25519-dalek`][ed25519-dalek]) should use
|
||||
`curve25519-dalek` as a library.
|
||||
|
||||
`curve25519-dalek` is intended to provide a clean and safe _mid-level_ API for use
|
||||
implementing a wide range of ECC-based crypto protocols, such as key agreement,
|
||||
signatures, anonymous credentials, rangeproofs, and zero-knowledge proof
|
||||
systems.
|
||||
|
||||
In particular, `curve25519-dalek` implements Ristretto, which constructs a
|
||||
prime-order group from a non-prime-order Edwards curve. This provides the
|
||||
speed and safety benefits of Edwards curve arithmetic, without the pitfalls of
|
||||
cofactor-related abstraction mismatches.
|
||||
|
||||
# Use
|
||||
|
||||
## Stable
|
||||
|
||||
To import `curve25519-dalek`, add the following to the dependencies section of
|
||||
your project's `Cargo.toml`:
|
||||
```toml
|
||||
curve25519-dalek = "3"
|
||||
```
|
||||
|
||||
## Beta
|
||||
|
||||
To use the latest prerelease (see changes [below](#breaking-changes-in-400)),
|
||||
use the following line in your project's `Cargo.toml`:
|
||||
```toml
|
||||
curve25519-dalek = "4.0.0-rc.3"
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default? | Description |
|
||||
| :--- | :---: | :--- |
|
||||
| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. |
|
||||
| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. |
|
||||
| `precomputed-tables` | ✓ | Includes precomputed basepoint multiplication tables. This speeds up `EdwardsPoint::mul_base` and `RistrettoPoint::mul_base` by ~4x, at the cost of ~30KB added to the code size. |
|
||||
| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
|
||||
| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
|
||||
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
|
||||
| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. |
|
||||
|
||||
To disable the default features when using `curve25519-dalek` as a dependency,
|
||||
add `default-features = false` to the dependency in your `Cargo.toml`. To
|
||||
disable it when running `cargo`, add the `--no-default-features` CLI flag.
|
||||
|
||||
## Major Version API Changes
|
||||
|
||||
Breaking changes for each major version release can be found in
|
||||
[`CHANGELOG.md`](CHANGELOG.md), under the "Breaking changes" subheader. The
|
||||
latest breaking changes in high level are below:
|
||||
|
||||
### Breaking changes in 4.0.0
|
||||
|
||||
* Update the MSRV from 1.41 to 1.60
|
||||
* Provide SemVer policy
|
||||
* Make `digest` and `rand_core` optional features
|
||||
* Remove `std` and `nightly` features
|
||||
* Replace backend selection - See [CHANGELOG.md](CHANGELOG.md) and [backends](#backends)
|
||||
* Replace methods `Scalar::{zero, one}` with constants `Scalar::{ZERO, ONE}`
|
||||
* `Scalar::from_canonical_bytes` now returns `CtOption`
|
||||
* `Scalar::is_canonical` now returns `Choice`
|
||||
* Remove `Scalar::from_bytes_clamped` and `Scalar::reduce`
|
||||
* Deprecate and feature-gate `Scalar::from_bits` behind `legacy_compatibility`
|
||||
* Deprecate `EdwardsPoint::hash_from_bytes` and rename it
|
||||
`EdwardsPoint::nonspec_map_to_curve`
|
||||
* Require including a new trait, `use curve25519_dalek::traits::BasepointTable`
|
||||
whenever using `EdwardsBasepointTable` or `RistrettoBasepointTable`
|
||||
|
||||
This release also does a lot of dependency updates and relaxations to unblock upstream build issues.
|
||||
|
||||
# Backends
|
||||
|
||||
Curve arithmetic is implemented and used by one of the following backends:
|
||||
|
||||
| Backend | Selection | Implementation | Bits / Word sizes |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `serial` | Automatic | An optimized, non-parllel implementation | `32` and `64` |
|
||||
| `fiat` | Manual | Formally verified field arithmetic from [fiat-crypto] | `32` and `64` |
|
||||
| `simd` | Automatic | Intel AVX2 / AVX512 IFMA accelerated backend | `64` only |
|
||||
|
||||
At runtime, `curve25519-dalek` selects an arithmetic backend from the set of backends it was compiled to support. For Intel x86-64 targets, unless otherwise specified, it will build itself with `simd` support, and default to `serial` at runtime if the appropriate CPU features aren't detected. See [SIMD backend] for more details.
|
||||
|
||||
In the future, `simd` backend may be extended to cover more instruction sets. This change will be non-breaking as this is considered as implementation detail.
|
||||
|
||||
## Manual Backend Override
|
||||
|
||||
You can force the crate to compile with specific backend support, e.g., `serial` for x86-64 targets to save code size, or `fiat` to force the runtime to use verified code. To do this, set the environment variable:
|
||||
```sh
|
||||
RUSTFLAGS='--cfg curve25519_dalek_backend="BACKEND"'
|
||||
```
|
||||
Equivalently, you can write to
|
||||
`~/.cargo/config`:
|
||||
```toml
|
||||
[build]
|
||||
rustflags = ['--cfg=curve25519_dalek_backend="BACKEND"']
|
||||
```
|
||||
More info [here](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags).
|
||||
|
||||
Note for contributors: The target backends are not entirely independent of each
|
||||
other. The [SIMD backend] directly depends on parts of the serial backend to
|
||||
function.
|
||||
|
||||
## Bits / Word size
|
||||
|
||||
`curve25519-dalek` will automatically choose the word size for the `fiat` and
|
||||
`serial` backends, based on the build target.
|
||||
For example, building for a 64-bit machine, the default 64 bit word size is
|
||||
automatically chosen when either the `serial` or `fiat` backend is selected.
|
||||
|
||||
In some targets it might be required to override the word size for better
|
||||
performance.
|
||||
Backend word size can be overridden for `serial` and `fiat` by setting the
|
||||
environment variable:
|
||||
```sh
|
||||
RUSTFLAGS='--cfg curve25519_dalek_bits="SIZE"'
|
||||
```
|
||||
`SIZE` is `32` or `64`. As in the above section, this can also be placed
|
||||
in `~/.cargo/config`.
|
||||
|
||||
Note: The [SIMD backend] requires a word size of 64 bits. Attempting to set bits=32 and backend=`simd` will yield a compile error.
|
||||
|
||||
### Cross-compilation
|
||||
|
||||
Because backend selection is done by target, cross-compiling will select the correct word size automatically. For example, if a x86-64 Linux machine runs the following commands, `curve25519-dalek` will be compiled with the 32-bit `serial` backend.
|
||||
```console
|
||||
$ sudo apt install gcc-multilib # (or whatever package manager you use)
|
||||
$ rustup target add i686-unknown-linux-gnu
|
||||
$ cargo build --target i686-unknown-linux-gnu
|
||||
```
|
||||
|
||||
## SIMD backend
|
||||
|
||||
The specific SIMD backend (AVX512 / AVX2 / `serial` default) is selected automatically at runtime, depending on the currently available CPU features, and whether Rust nightly is being used for compilation. The precise conditions are specified below.
|
||||
|
||||
For a given CPU feature, you can also specify an appropriate `-C target_feature` to build a binary which assumes the required SIMD instructions are always available. Don't do this if you don't have a good reason.
|
||||
|
||||
| Backend | `RUSTFLAGS` | Requires nightly? |
|
||||
| :--- | :--- | :--- |
|
||||
| avx2 | `-C target_feature=+avx2` | no |
|
||||
| avx512 | `-C target_feature=+avx512ifma,+avx512vl` | yes |
|
||||
|
||||
If compiled on a non-nightly compiler, `curve25519-dalek` will not include AVX512 code, and therefore will never select it at runtime.
|
||||
|
||||
# Documentation
|
||||
|
||||
The semver-stable, public-facing `curve25519-dalek` API is documented [here][docs].
|
||||
|
||||
## Building Docs Locally
|
||||
|
||||
The `curve25519-dalek` documentation requires a custom HTML header to include
|
||||
KaTeX for math support. Unfortunately `cargo doc` does not currently support
|
||||
this, but docs can be built using
|
||||
```sh
|
||||
make doc
|
||||
```
|
||||
for regular docs, and
|
||||
```sh
|
||||
make doc-internal
|
||||
```
|
||||
for docs that include private items.
|
||||
|
||||
# Maintenance Policies
|
||||
|
||||
All on-by-default features of this library are covered by
|
||||
[semantic versioning][semver] (SemVer). SemVer exemptions are outlined below
|
||||
for MSRV and public API.
|
||||
|
||||
## Minimum Supported Rust Version
|
||||
|
||||
| Releases | MSRV |
|
||||
| :--- |:-------|
|
||||
| 4.x | 1.60.0 |
|
||||
| 3.x | 1.41.0 |
|
||||
|
||||
From 4.x and on, MSRV changes will be accompanied by a minor version bump.
|
||||
|
||||
## Public API SemVer Exemptions
|
||||
|
||||
Breaking changes to SemVer exempted components affecting the public API will be accompanied by
|
||||
_some_ version bump. Below are the specific policies:
|
||||
|
||||
| Releases | Public API Component(s) | Policy |
|
||||
| :--- | :--- | :--- |
|
||||
| 4.x | Dependencies `digest` and `rand_core` | Minor SemVer bump |
|
||||
|
||||
# Safety
|
||||
|
||||
The `curve25519-dalek` types are designed to make illegal states
|
||||
unrepresentable. For example, any instance of an `EdwardsPoint` is
|
||||
guaranteed to hold a point on the Edwards curve, and any instance of a
|
||||
`RistrettoPoint` is guaranteed to hold a valid point in the Ristretto
|
||||
group.
|
||||
|
||||
All operations are implemented using constant-time logic (no
|
||||
secret-dependent branches, no secret-dependent memory accesses),
|
||||
unless specifically marked as being variable-time code.
|
||||
We believe that our constant-time logic is lowered to constant-time
|
||||
assembly, at least on `x86_64` targets.
|
||||
|
||||
As an additional guard against possible future compiler optimizations,
|
||||
the `subtle` crate places an optimization barrier before every
|
||||
conditional move or assignment. More details can be found in [the
|
||||
documentation for the `subtle` crate][subtle_doc].
|
||||
|
||||
Some functionality (e.g., multiscalar multiplication or batch
|
||||
inversion) requires heap allocation for temporary buffers. All
|
||||
heap-allocated buffers of potentially secret data are explicitly
|
||||
zeroed before release.
|
||||
|
||||
However, we do not attempt to zero stack data, for two reasons.
|
||||
First, it's not possible to do so correctly: we don't have control
|
||||
over stack allocations, so there's no way to know how much data to
|
||||
wipe. Second, because `curve25519-dalek` provides a mid-level API,
|
||||
the correct place to start zeroing stack data is likely not at the
|
||||
entrypoints of `curve25519-dalek` functions, but at the entrypoints of
|
||||
functions in other crates.
|
||||
|
||||
The implementation is memory-safe, and contains no significant
|
||||
`unsafe` code. The SIMD backend uses `unsafe` internally to call SIMD
|
||||
intrinsics. These are marked `unsafe` only because invoking them on an
|
||||
inappropriate CPU would cause `SIGILL`, but the entire backend is only
|
||||
invoked when the appropriate CPU features are detected at runtime, or
|
||||
when the whole program is compiled with the appropriate `target_feature`s.
|
||||
|
||||
# Performance
|
||||
|
||||
Benchmarks are run using [`criterion.rs`][criterion]:
|
||||
|
||||
```sh
|
||||
cargo bench --features "rand_core"
|
||||
export RUSTFLAGS='-C target_cpu=native'
|
||||
cargo +nightly bench --features "rand_core"
|
||||
```
|
||||
|
||||
Performance is a secondary goal behind correctness, safety, and
|
||||
clarity, but we aim to be competitive with other implementations.
|
||||
|
||||
# FFI
|
||||
|
||||
Unfortunately, we have no plans to add FFI to `curve25519-dalek` directly. The
|
||||
reason is that we use Rust features to provide an API that maintains safety
|
||||
invariants, which are not possible to maintain across an FFI boundary. For
|
||||
instance, as described in the _Safety_ section above, invalid points are
|
||||
impossible to construct, and this would not be the case if we exposed point
|
||||
operations over FFI.
|
||||
|
||||
However, `curve25519-dalek` is designed as a *mid-level* API, aimed at
|
||||
implementing other, higher-level primitives. Instead of providing FFI at the
|
||||
mid-level, our suggestion is to implement the higher-level primitive (a
|
||||
signature, PAKE, ZKP, etc) in Rust, using `curve25519-dalek` as a dependency,
|
||||
and have that crate provide a minimal, byte-buffer-oriented FFI specific to
|
||||
that primitive.
|
||||
|
||||
# Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md][contributing].
|
||||
|
||||
# About
|
||||
|
||||
**SPOILER ALERT:** *The Twelfth Doctor's first encounter with the Daleks is in
|
||||
his second full episode, "Into the Dalek". A beleaguered ship of the "Combined
|
||||
Galactic Resistance" has discovered a broken Dalek that has turned "good",
|
||||
desiring to kill all other Daleks. The Doctor, Clara and a team of soldiers
|
||||
are miniaturized and enter the Dalek, which the Doctor names Rusty. They
|
||||
repair the damage, but accidentally restore it to its original nature, causing
|
||||
it to go on the rampage and alert the Dalek fleet to the whereabouts of the
|
||||
rebel ship. However, the Doctor manages to return Rusty to its previous state
|
||||
by linking his mind with the Dalek's: Rusty shares the Doctor's view of the
|
||||
universe's beauty, but also his deep hatred of the Daleks. Rusty destroys the
|
||||
other Daleks and departs the ship, determined to track down and bring an end
|
||||
to the Dalek race.*
|
||||
|
||||
`curve25519-dalek` is authored by Isis Agora Lovecruft and Henry de Valence.
|
||||
|
||||
Portions of this library were originally a port of [Adam Langley's
|
||||
Golang ed25519 library](https://github.com/agl/ed25519), which was in
|
||||
turn a port of the reference `ref10` implementation. Most of this code,
|
||||
including the 32-bit field arithmetic, has since been rewritten.
|
||||
|
||||
The fast `u32` and `u64` scalar arithmetic was implemented by Andrew Moon, and
|
||||
the addition chain for scalar inversion was provided by Brian Smith. The
|
||||
optimised batch inversion was contributed by Sean Bowe and Daira Hopwood.
|
||||
|
||||
The `no_std` and `zeroize` support was contributed by Tony Arcieri.
|
||||
|
||||
The formally verified `fiat_backend` integrates Rust code generated by the
|
||||
[Fiat Crypto project](https://github.com/mit-plv/fiat-crypto) and was
|
||||
contributed by François Garillot.
|
||||
|
||||
Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg,
|
||||
Pratyush Mishra, Michael Rosenberg, @pinkforest, and countless others for their
|
||||
contributions.
|
||||
|
||||
[ed25519-dalek]: https://github.com/dalek-cryptography/ed25519-dalek
|
||||
[x25519-dalek]: https://github.com/dalek-cryptography/x25519-dalek
|
||||
[docs]: https://docs.rs/curve25519-dalek/
|
||||
[contributing]: https://github.com/dalek-cryptography/curve25519-dalek/blob/master/CONTRIBUTING.md
|
||||
[criterion]: https://github.com/japaric/criterion.rs
|
||||
[parallel_doc]: https://docs.rs/curve25519-dalek/latest/curve25519_dalek/backend/vector/index.html
|
||||
[subtle_doc]: https://docs.rs/subtle
|
||||
[fiat-crypto]: https://github.com/mit-plv/fiat-crypto
|
||||
[semver]: https://semver.org/spec/v2.0.0.html
|
||||
[rngcorestd]: https://github.com/rust-random/rand/tree/7aa25d577e2df84a5156f824077bb7f6bdf28d97/rand_core#crate-features
|
||||
[zeroize-trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
|
||||
[SIMD backend]: #simd-backend
|
0
src/backend/serial/scalar_mul/straus.rs → curve25519-dalek/src/backend/serial/scalar_mul/straus.rs
0
src/backend/serial/scalar_mul/straus.rs → curve25519-dalek/src/backend/serial/scalar_mul/straus.rs
0
src/backend/vector/scalar_mul/straus.rs → curve25519-dalek/src/backend/vector/scalar_mul/straus.rs
0
src/backend/vector/scalar_mul/straus.rs → curve25519-dalek/src/backend/vector/scalar_mul/straus.rs
12
ed25519-dalek/.gitignore
vendored
Normal file
12
ed25519-dalek/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
target
|
||||
|
||||
.cargo
|
||||
|
||||
*~
|
||||
\#*
|
||||
.\#*
|
||||
*.swp
|
||||
*.orig
|
||||
*.bak
|
||||
|
||||
*.s
|
33
ed25519-dalek/.travis.yml
Normal file
33
ed25519-dalek/.travis.yml
Normal file
@ -0,0 +1,33 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
||||
env:
|
||||
- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES=''
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# We use the 64-bit optimised curve backend by default, so also test with
|
||||
# the 32-bit backend (this also exercises testing with `no_std`):
|
||||
- rust: nightly
|
||||
env: TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u32_backend alloc'
|
||||
# Also test the batch feature:
|
||||
- rust: nightly
|
||||
env: TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u64_backend alloc batch'
|
||||
# Test any nightly gated features on nightly:
|
||||
- rust: nightly
|
||||
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly'
|
||||
# Test serde support on stable, assuming that if it works there it'll work everywhere:
|
||||
- rust: stable
|
||||
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='serde'
|
||||
|
||||
script:
|
||||
- cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
rooms:
|
||||
- dalek-cryptography:Xxv9WotKYWdSoKlgKNqXiHoD#dalek-bots
|
41
ed25519-dalek/CHANGELOG.md
Normal file
41
ed25519-dalek/CHANGELOG.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Entries are listed in reverse chronological order per undeprecated major series.
|
||||
|
||||
# 2.x series
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Bump MSRV from 1.41 to 1.60.0
|
||||
* Bump Rust edition
|
||||
* Bump `signature` dependency to 2.0
|
||||
* Make `digest` an optional dependency
|
||||
* Make `zeroize` an optional dependency
|
||||
* Make `rand_core` an optional dependency
|
||||
* [curve25519 backends] are now automatically selected
|
||||
* [curve25519 backends] are now overridable via cfg instead of using additive features
|
||||
* Make all batch verification deterministic remove `batch_deterministic` (PR [#256](https://github.com/dalek-cryptography/ed25519-dalek/pull/256))
|
||||
* Rename `Keypair` → `SigningKey` and `PublicKey` → `VerifyingKey`
|
||||
* Remove default-public `ExpandedSecretKey` API (PR [#205](https://github.com/dalek-cryptography/ed25519-dalek/pull/205))
|
||||
* Make `hazmat` feature to expose `ExpandedSecretKey`, `raw_sign()`, `raw_sign_prehashed()`, `raw_verify()`, and `raw_verify_prehashed()`
|
||||
|
||||
[curve25519 backends]: https://github.com/dalek-cryptography/curve25519-dalek/#backends
|
||||
|
||||
### Other changes
|
||||
|
||||
* Add `Context` type for prehashed signing
|
||||
* Add `VerifyingKey::{verify_prehash_strict, is_weak}`
|
||||
* Add `pkcs` feature to support PKCS #8 (de)serialization of `SigningKey` and `VerifyingKey`
|
||||
* Add `fast` feature to include basepoint tables
|
||||
* Add tests for validation criteria
|
||||
* Impl `DigestSigner`/`DigestVerifier` for `SigningKey`/`VerifyingKey`, respectively
|
||||
* Impl `Hash` for `VerifyingKey`
|
||||
* Impl `Clone`, `Drop`, and `ZeroizeOnDrop` for `SigningKey`
|
||||
* Remove `rand` dependency
|
||||
* Improve key deserialization diagnostics
|
1012
ed25519-dalek/Cargo.lock
generated
Normal file
1012
ed25519-dalek/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
75
ed25519-dalek/Cargo.toml
Normal file
75
ed25519-dalek/Cargo.toml
Normal file
@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.0.0-rc.3"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"isis lovecruft <isis@patternsinthevoid.net>",
|
||||
"Tony Arcieri <bascule@gmail.com>",
|
||||
"Michael Rosenberg <michael@mrosenberg.pub>"
|
||||
]
|
||||
readme = "README.md"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/dalek-cryptography/ed25519-dalek"
|
||||
documentation = "https://docs.rs/ed25519-dalek"
|
||||
keywords = ["cryptography", "ed25519", "curve25519", "signature", "ECC"]
|
||||
categories = ["cryptography", "no-std"]
|
||||
description = "Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust."
|
||||
exclude = [ ".gitignore", "TESTVECTORS", "VALIDATIONVECTORS", "res/*" ]
|
||||
rust-version = "1.60"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
|
||||
"--cfg", "docsrs",
|
||||
]
|
||||
features = ["batch", "digest", "hazmat", "pem", "serde"]
|
||||
|
||||
[dependencies]
|
||||
curve25519-dalek = { version = "=4.0.0-rc.3", path = "../curve25519-dalek", default-features = false, features = ["digest"] }
|
||||
ed25519 = { version = ">=2.2, <2.3", default-features = false }
|
||||
signature = { version = ">=2.0, <2.1", optional = true, default-features = false }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
|
||||
# optional features
|
||||
merlin = { version = "3", default-features = false, optional = true }
|
||||
rand_core = { version = "0.6.4", default-features = false, optional = true }
|
||||
serde = { version = "1.0", default-features = false, optional = true }
|
||||
zeroize = { version = "1.5", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
curve25519-dalek = { version = "=4.0.0-rc.3", path = "../curve25519-dalek", default-features = false, features = ["digest", "rand_core"] }
|
||||
blake2 = "0.10"
|
||||
sha3 = "0.10"
|
||||
hex = "0.4"
|
||||
bincode = "1.0"
|
||||
serde_json = "1.0"
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
hex-literal = "0.3"
|
||||
rand = "0.8"
|
||||
rand_core = { version = "0.6.4", default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = { version = "0.5" }
|
||||
|
||||
[[bench]]
|
||||
name = "ed25519_benchmarks"
|
||||
harness = false
|
||||
required-features = ["rand_core"]
|
||||
|
||||
[features]
|
||||
default = ["fast", "std", "zeroize"]
|
||||
alloc = ["curve25519-dalek/alloc", "ed25519/alloc", "serde?/alloc", "zeroize/alloc"]
|
||||
std = ["alloc", "ed25519/std", "serde?/std", "sha2/std"]
|
||||
|
||||
asm = ["sha2/asm"]
|
||||
batch = ["alloc", "merlin", "rand_core"]
|
||||
fast = ["curve25519-dalek/precomputed-tables"]
|
||||
digest = ["signature/digest"]
|
||||
# Exposes the hazmat module
|
||||
hazmat = []
|
||||
# Turns off stricter checking for scalar malleability in signatures
|
||||
legacy_compatibility = ["curve25519-dalek/legacy_compatibility"]
|
||||
pkcs8 = ["ed25519/pkcs8"]
|
||||
pem = ["alloc", "ed25519/pem", "pkcs8"]
|
||||
rand_core = ["dep:rand_core"]
|
||||
serde = ["dep:serde", "ed25519/serde"]
|
||||
zeroize = ["dep:zeroize", "curve25519-dalek/zeroize"]
|
28
ed25519-dalek/LICENSE
Normal file
28
ed25519-dalek/LICENSE
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2017-2019 isis agora lovecruft. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
188
ed25519-dalek/README.md
Normal file
188
ed25519-dalek/README.md
Normal file
@ -0,0 +1,188 @@
|
||||
# ed25519-dalek [](https://crates.io/crates/ed25519-dalek) [](https://docs.rs/ed25519-dalek) [](https://github.com/dalek-cryptography/ed25519-dalek/actions/workflows/rust.yml)
|
||||
|
||||
Fast and efficient Rust implementation of ed25519 key generation, signing, and
|
||||
verification.
|
||||
|
||||
# Use
|
||||
|
||||
## Stable
|
||||
|
||||
To import `ed25519-dalek`, add the following to the dependencies section of
|
||||
your project's `Cargo.toml`:
|
||||
```toml
|
||||
ed25519-dalek = "1"
|
||||
```
|
||||
|
||||
## Beta
|
||||
|
||||
To use the latest prerelease (see changes [below](#breaking-changes-in-200)),
|
||||
use the following line in your project's `Cargo.toml`:
|
||||
```toml
|
||||
ed25519-dalek = "2.0.0-rc.3"
|
||||
```
|
||||
|
||||
# Feature Flags
|
||||
|
||||
This crate is `#[no_std]` compatible with `default-features = false`.
|
||||
|
||||
| Feature | Default? | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `alloc` | ✓ | When `pkcs8` is enabled, implements `EncodePrivateKey`/`EncodePublicKey` for `SigningKey`/`VerifyingKey`, respectively. |
|
||||
| `std` | ✓ | Implements `std::error::Error` for `SignatureError`. Also enables `alloc`. |
|
||||
| `zeroize` | ✓ | Implements `Zeroize` and `ZeroizeOnDrop` for `SigningKey` |
|
||||
| `rand_core` | | Enables `SigningKey::generate` |
|
||||
| `batch` | | Enables `verify_batch` for verifying many signatures quickly. Also enables `rand_core`. |
|
||||
| `digest` | | Enables `Context`, `SigningKey::{with_context, sign_prehashed}` and `VerifyingKey::{with_context, verify_prehashed, verify_prehashed_strict}` for Ed25519ph prehashed signatures |
|
||||
| `asm` | | Enables assembly optimizations in the SHA-512 compression functions |
|
||||
| `pkcs8` | | Enables [PKCS#8](https://en.wikipedia.org/wiki/PKCS_8) serialization/deserialization for `SigningKey` and `VerifyingKey` |
|
||||
| `pem` | | Enables PEM serialization support for PKCS#8 private keys and SPKI public keys. Also enables `alloc`. |
|
||||
| `legacy_compatibility` | | **Unsafe:** Disables certain signature checks. See [below](#malleability-and-the-legacy_compatibility-feature) |
|
||||
| `hazmat` | | **Unsafe:** Exposes the `hazmat` module for raw signing/verifying. Misuse of these functions will expose the private key, as in the [signing oracle attack](https://github.com/MystenLabs/ed25519-unsafe-libs). |
|
||||
|
||||
# Major Changes
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past version of this crate.
|
||||
|
||||
## Breaking Changes in 2.0.0
|
||||
|
||||
* Bump MSRV from 1.41 to 1.60.0
|
||||
* Bump Rust edition
|
||||
* Bump `signature` dependency to 2.0
|
||||
* Make `digest` an optional dependency
|
||||
* Make `zeroize` an optional dependency
|
||||
* Make `rand_core` an optional dependency
|
||||
* Adopt [curve25519-backend selection](https://github.com/dalek-cryptography/curve25519-dalek/#backends) over features
|
||||
* Make all batch verification deterministic remove `batch_deterministic` ([#256](https://github.com/dalek-cryptography/ed25519-dalek/pull/256))
|
||||
* Remove `ExpandedSecretKey` API ((#205)[https://github.com/dalek-cryptography/ed25519-dalek/pull/205])
|
||||
* Rename `Keypair` → `SigningKey` and `PublicKey` → `VerifyingKey`
|
||||
* Make `hazmat` feature to expose, `ExpandedSecretKey`, `raw_sign()`, `raw_sign_prehashed()`, `raw_verify()`, and `raw_verify_prehashed()`
|
||||
|
||||
# Documentation
|
||||
|
||||
Documentation is available [here](https://docs.rs/ed25519-dalek).
|
||||
|
||||
# Compatibility Policies
|
||||
|
||||
All on-by-default features of this library are covered by [semantic versioning](https://semver.org/spec/v2.0.0.html) (SemVer).
|
||||
SemVer exemptions are outlined below for MSRV and public API.
|
||||
|
||||
## Minimum Supported Rust Version
|
||||
|
||||
| Releases | MSRV |
|
||||
| :--- | :--- |
|
||||
| 2.x | 1.60 |
|
||||
| 1.x | 1.41 |
|
||||
|
||||
From 2.x and on, MSRV changes will be accompanied by a minor version bump.
|
||||
|
||||
## Public API SemVer Exemptions
|
||||
|
||||
Breaking changes to SemVer-exempted components affecting the public API will be accompanied by some version bump.
|
||||
|
||||
Below are the specific policies:
|
||||
|
||||
| Releases | Public API Component(s) | Policy |
|
||||
| :--- | :--- | :--- |
|
||||
| 2.x | Dependencies `digest`, `pkcs8` and `rand_core` | Minor SemVer bump |
|
||||
|
||||
# Safety
|
||||
|
||||
`ed25519-dalek` is designed to prevent misuse. Signing is constant-time, all signing keys are zeroed when they go out of scope (unless `zeroize` is disabled), detached public keys [cannot](https://github.com/MystenLabs/ed25519-unsafe-libs/blob/main/README.md) be used for signing, and extra functions like [`VerifyingKey::verify_strict`](#weak-key-forgery-and-verify_strict) are made available to avoid known gotchas.
|
||||
|
||||
Further, this crate has no—and in fact forbids—unsafe code. You can opt in to using some highly optimized unsafe code that resides in `curve25519-dalek`, though. See [below](#microarchitecture-specific-backends) for more information on backend selection.
|
||||
|
||||
# Performance
|
||||
|
||||
Performance is a secondary goal behind correctness, safety, and clarity, but we
|
||||
aim to be competitive with other implementations.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Benchmarks are run using [criterion.rs](https://github.com/japaric/criterion.rs):
|
||||
|
||||
```sh
|
||||
cargo bench --features "batch"
|
||||
# Uses avx2 or ifma only if compiled for an appropriate target.
|
||||
export RUSTFLAGS='-C target_cpu=native'
|
||||
cargo +nightly bench --features "batch"
|
||||
```
|
||||
|
||||
On an Intel 10700K running at stock comparing between the `curve25519-dalek` backends.
|
||||
|
||||
| Benchmark | u64 | simd +avx2 | fiat |
|
||||
| :--- | :---- | :--- | :--- |
|
||||
| signing | 15.017 µs | 13.906 µs -7.3967% | 15.877 μs +5.7268% |
|
||||
| signature verification | 40.144 µs | 25.963 µs -35.603% | 42.118 μs +4.9173% |
|
||||
| strict signature verification | 41.334 µs | 27.874 µs -32.660% | 43.985 μs +6.4136% |
|
||||
| batch signature verification/4 | 109.44 µs | 81.778 µs -25.079% | 117.80 μs +7.6389% |
|
||||
| batch signature verification/8 | 182.75 µs | 138.40 µs -23.871% | 195.86 μs +7.1737% |
|
||||
| batch signature verification/16 | 328.67 µs | 251.39 µs -23.744% | 351.55 μs +6.9614% |
|
||||
| batch signature verification/32 | 619.49 µs | 477.36 µs -23.053% | 669.41 μs +8.0582% |
|
||||
| batch signature verification/64 | 1.2136 ms | 936.85 µs -22.543% | 1.3028 ms +7.3500% |
|
||||
| batch signature verification/96 | 1.8677 ms | 1.2357 ms -33.936% | 2.0552 ms +10.039% |
|
||||
| batch signature verification/128| 2.3281 ms | 1.5795 ms -31.996% | 2.5596 ms +9.9437% |
|
||||
| batch signature verification/256| 4.1868 ms | 2.8864 ms -31.061% | 4.6494 μs +11.049% |
|
||||
| keypair generation | 13.973 µs | 13.108 µs -6.5062% | 15.099 μs +8.0584% |
|
||||
|
||||
## Batch Performance
|
||||
|
||||
If your protocol or application is able to batch signatures for verification,
|
||||
the [`verify_batch`][func_verify_batch] function has greatly improved performance.
|
||||
|
||||
As you can see, there's an optimal batch size for each machine, so you'll likely
|
||||
want to test the benchmarks on your target CPU to discover the best size.
|
||||
|
||||
## (Micro)Architecture Specific Backends
|
||||
|
||||
A _backend_ refers to an implementation of elliptic curve and scalar arithmetic. Different backends have different use cases. For example, if you demand formally verified code, you want to use the `fiat` backend (as it was generated from [Fiat Crypto][fiat]).
|
||||
|
||||
Backend selection details and instructions can be found in the [curve25519-dalek docs](https://github.com/dalek-cryptography/curve25519-dalek#backends).
|
||||
|
||||
# Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
# Batch Signature Verification
|
||||
|
||||
The standard variants of batch signature verification (i.e. many signatures made with potentially many different public keys over potentially many different messages) is available via the `batch` feature. It uses deterministic randomness, i.e., it hashes the inputs (using [`merlin`](https://merlin.cool/), which handles transcript item separation) and uses the result to generate random coefficients. Batch verification requires allocation, so this won't function in heapless settings.
|
||||
|
||||
# Validation Criteria
|
||||
|
||||
The _validation criteria_ of a signature scheme are the criteria that signatures and public keys must satisfy in order to be accepted. Unfortunately, Ed25519 has some underspecified parts, leading to different validation criteria across implementations. For a very good overview of this, see [Henry's post][validation].
|
||||
|
||||
In this section, we mention some specific details about our validation criteria, and how to navigate them.
|
||||
|
||||
## Malleability and the `legacy_compatibility` Feature
|
||||
|
||||
A signature scheme is considered to produce _malleable signatures_ if a passive attacker with knowledge of a public key _A_, message _m_, and valid signature _σ'_ can produce a distinct _σ'_ such that _σ'_ is a valid signature of _m_ with respect to _A_. A scheme is only malleable if the attacker can do this _without_ knowledge of the private key corresponding to _A_.
|
||||
|
||||
`ed25519-dalek` is not a malleable signature scheme.
|
||||
|
||||
Some other Ed25519 implementations are malleable, though, such as [libsodium with `ED25519_COMPAT` enabled](https://github.com/jedisct1/libsodium/blob/24211d370a9335373f0715664271dfe203c7c2cd/src/libsodium/crypto_sign/ed25519/ref10/open.c#L30), [ed25519-donna](https://github.com/floodyberry/ed25519-donna/blob/8757bd4cd209cb032853ece0ce413f122eef212c/ed25519.c#L100), [NaCl's ref10 impl](https://github.com/floodyberry/ed25519-donna/blob/8757bd4cd209cb032853ece0ce413f122eef212c/fuzz/ed25519-ref10.c#L4627), and probably a lot more.
|
||||
If you need to interoperate with such implementations and accept otherwise invalid signatures, you can enable the `legacy_compatibility` flag. **Do not enable `legacy_compatibility`** if you don't have to, because it will make your signatures malleable.
|
||||
|
||||
Note: [CIRCL](https://github.com/cloudflare/circl/blob/fa6e0cca79a443d7be18ed241e779adf9ed2a301/sign/ed25519/ed25519.go#L358) has no scalar range check at all. We do not have a feature flag for interoperating with the larger set of RFC-disallowed signatures that CIRCL accepts.
|
||||
|
||||
## Weak key Forgery and `verify_strict()`
|
||||
|
||||
A _signature forgery_ is what it sounds like: it's when an attacker, given a public key _A_, creates a signature _σ_ and message _m_ such that _σ_ is a valid signature of _m_ with respect to _A_. Since this is the core security definition of any signature scheme, Ed25519 signatures cannot be forged.
|
||||
|
||||
However, there's a much looser kind of forgery that Ed25519 permits, which we call _weak key forgery_. An attacker can produce a special public key _A_ (which we call a _weak_ public key) and a signature _σ_ such that _σ_ is a valid signature of _any_ message _m_, with respect to _A_, with high probability. This attack is acknowledged in the [Ed25519 paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf), and caused an exploitable bug in the Scuttlebutt protocol ([paper](https://eprint.iacr.org/2019/526.pdf), section 7.1). The [`VerifyingKey::verify()`][method_verify] function permits weak keys.
|
||||
|
||||
We provide [`VerifyingKey::verify_strict`][method_verify_strict] (and [`verify_strict_prehashed`][method_verify_strict_ph]) to help users avoid these scenarios. These functions perform an extra check on _A_, ensuring it's not a weak public key. In addition, we provide the [`VerifyingKey::is_weak`][method_is_weak] to allow users to perform this check before attempting signature verification.
|
||||
|
||||
## Batch verification
|
||||
|
||||
As mentioned above, weak public keys can be used to produce signatures for unknown messages with high probability. This means that sometimes a weak forgery attempt will fail. In fact, it can fail up to 7/8 of the time. If you call `verify()` twice on the same failed forgery, it will return an error both times, as expected. However, if you call `verify_batch()` twice on two distinct otherwise-valid batches, both of which contain the failed forgery, there's a 21% chance that one fails and the other succeeds.
|
||||
|
||||
Why is this? It's because `verify_batch()` does not do the weak key testing of `verify_strict()`, and it multiplies each verification equation by some random coefficient. If the failed forgery gets multiplied by 8, then the weak key (which is a low-order point) becomes 0, and the verification equation on the attempted forgery will succeed.
|
||||
|
||||
Since `verify_batch()` is intended to be high-throughput, we think it's best not to put weak key checks in it. If you want to prevent weird behavior due to weak public keys in your batches, you should call [`VerifyingKey::is_weak`][method_is_weak] on the inputs in advance.
|
||||
|
||||
[fiat]: https://github.com/mit-plv/fiat-crypto
|
||||
[validation]: https://hdevalence.ca/blog/2020-10-04-its-25519am
|
||||
[func_verify_batch]: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/fn.verify_batch.html
|
||||
[method_verify]: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.VerifyingKey.html#method.verify
|
||||
[method_verify_strict]: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.VerifyingKey.html#method.verify_strict
|
||||
[method_verify_strict_ph]: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.VerifyingKey.html#method.verify_strict_prehashed
|
||||
[method_is_weak]: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.VerifyingKey.html#method.is_weak
|
128
ed25519-dalek/TESTVECTORS
Normal file
128
ed25519-dalek/TESTVECTORS
Normal file
@ -0,0 +1,128 @@
|
||||
9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b:
|
||||
4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072:
|
||||
c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82:
|
||||
0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b:
|
||||
6df9340c138cc188b5fe4464ebaa3f7fc206a2d55c3434707e74c9fc04e20ebbc0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:c0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:5f4c8989:124f6fc6b0d100842769e71bd530664d888df8507df6c56dedfdb509aeb93416e26b918d38aa06305df3095697c18b2aa832eaa52edc0ae49fbae5a85e150c075f4c8989:
|
||||
b780381a65edf8b78f6945e8dbec7941ac049fd4c61040cf0c324357975a293ce253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:e253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:18b6bec097:b2fc46ad47af464478c199e1f8be169f1be6327c7f9a0a6689371ca94caf04064a01b22aff1520abd58951341603faed768cf78ce97ae7b038abfe456aa17c0918b6bec097:
|
||||
78ae9effe6f245e924a7be63041146ebc670dbd3060cba67fbc6216febc44546fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:89010d855972:6ed629fc1d9ce9e1468755ff636d5a3f40a5d9c91afd93b79d241830f7e5fa29854b8f20cc6eecbb248dbd8d16d14e99752194e4904d09c74d639518839d230089010d855972:
|
||||
691865bfc82a1e4b574eecde4c7519093faf0cf867380234e3664645c61c5f7998a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:98a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:b4a8f381e70e7a:6e0af2fe55ae377a6b7a7278edfb419bd321e06d0df5e27037db8812e7e3529810fa5552f6c0020985ca17a0e02e036d7b222a24f99b77b75fdd16cb05568107b4a8f381e70e7a:
|
||||
3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:4284abc51bb67235:d6addec5afb0528ac17bb178d3e7f2887f9adbb1ad16e110545ef3bc57f9de2314a5c8388f723b8907be0f3ac90c6259bbe885ecc17645df3db7d488f805fa084284abc51bb67235:
|
||||
edc6f5fbdd1cee4d101c063530a30490b221be68c036f5b07d0f953b745df192c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:672bf8965d04bc5146:2c76a04af2391c147082e33faacdbe56642a1e134bd388620b852b901a6bc16ff6c9cc9404c41dea12ed281da067a1513866f9d964f8bdd24953856c50042901672bf8965d04bc5146:
|
||||
4e7d21fb3b1897571a445833be0f9fd41cd62be3aa04040f8934e1fcbdcacd4531b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:31b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:33d7a786aded8c1bf691:28e4598c415ae9de01f03f9f3fab4e919e8bf537dd2b0cdf6e79b9e6559c9409d9151a4c40f083193937627c369488259e99da5a9f0a87497fa6696a5dd6ce0833d7a786aded8c1bf691:
|
||||
a980f892db13c99a3e8971e965b2ff3d41eafd54093bc9f34d1fd22d84115bb644b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:44b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:3486f68848a65a0eb5507d:77d389e599630d934076329583cd4105a649a9292abc44cd28c40000c8e2f5ac7660a81c85b72af8452d7d25c070861dae91601c7803d656531650dd4e5c41003486f68848a65a0eb5507d:
|
||||
5b5a619f8ce1c66d7ce26e5a2ae7b0c04febcd346d286c929e19d0d5973bfef96fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:6fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:5a8d9d0a22357e6655f9c785:0f9ad9793033a2fa06614b277d37381e6d94f65ac2a5a94558d09ed6ce922258c1a567952e863ac94297aec3c0d0c8ddf71084e504860bb6ba27449b55adc40e5a8d9d0a22357e6655f9c785:
|
||||
940c89fe40a81dafbdb2416d14ae469119869744410c3303bfaa0241dac57800a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:b87d3813e03f58cf19fd0b6395:d8bb64aad8c9955a115a793addd24f7f2b077648714f49c4694ec995b330d09d640df310f447fd7b6cb5c14f9fe9f490bcf8cfadbfd2169c8ac20d3b8af49a0cb87d3813e03f58cf19fd0b6395:
|
||||
9acad959d216212d789a119252ebfe0c96512a23c73bd9f3b202292d6916a738cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:55c7fa434f5ed8cdec2b7aeac173:6ee3fe81e23c60eb2312b2006b3b25e6838e02106623f844c44edb8dafd66ab0671087fd195df5b8f58a1d6e52af42908053d55c7321010092748795ef94cf0655c7fa434f5ed8cdec2b7aeac173:
|
||||
d5aeee41eeb0e9d1bf8337f939587ebe296161e6bf5209f591ec939e1440c300fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:0a688e79be24f866286d4646b5d81c:f68d04847e5b249737899c014d31c805c5007a62c0a10d50bb1538c5f35503951fbc1e08682f2cc0c92efe8f4985dec61dcbd54d4b94a22547d24451271c8b000a688e79be24f866286d4646b5d81c:
|
||||
0a47d10452ae2febec518a1c7c362890c3fc1a49d34b03b6467d35c904a8362d34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:c942fa7ac6b23ab7ff612fdc8e68ef39:2a3d27dc40d0a8127949a3b7f908b3688f63b7f14f651aacd715940bdbe27a0809aac142f47ab0e1e44fa490ba87ce5392f33a891539caf1ef4c367cae54500cc942fa7ac6b23ab7ff612fdc8e68ef39:
|
||||
f8148f7506b775ef46fdc8e8c756516812d47d6cfbfa318c27c9a22641e56f170445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:0445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:7368724a5b0efb57d28d97622dbde725af:3653ccb21219202b8436fb41a32ba2618c4a133431e6e63463ceb3b6106c4d56e1d2ba165ba76eaad3dc39bffb130f1de3d8e6427db5b71938db4e272bc3e20b7368724a5b0efb57d28d97622dbde725af:
|
||||
77f88691c4eff23ebb7364947092951a5ff3f10785b417e918823a552dab7c7574d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:74d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:bd8e05033f3a8bcdcbf4beceb70901c82e31:fbe929d743a03c17910575492f3092ee2a2bf14a60a3fcacec74a58c7334510fc262db582791322d6c8c41f1700adb80027ecabc14270b703444ae3ee7623e0abd8e05033f3a8bcdcbf4beceb70901c82e31:
|
||||
ab6f7aee6a0837b334ba5eb1b2ad7fcecfab7e323cab187fe2e0a95d80eff1325b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:5b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:8171456f8b907189b1d779e26bc5afbb08c67a:73bca64e9dd0db88138eedfafcea8f5436cfb74bfb0e7733cf349baa0c49775c56d5934e1d38e36f39b7c5beb0a836510c45126f8ec4b6810519905b0ca07c098171456f8b907189b1d779e26bc5afbb08c67a:
|
||||
8d135de7c8411bbdbd1b31e5dc678f2ac7109e792b60f38cd24936e8a898c32d1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:8ba6a4c9a15a244a9c26bb2a59b1026f21348b49:a1adc2bc6a2d980662677e7fdff6424de7dba50f5795ca90fdf3e96e256f3285cac71d3360482e993d0294ba4ec7440c61affdf35fe83e6e04263937db93f1058ba6a4c9a15a244a9c26bb2a59b1026f21348b49:
|
||||
0e765d720e705f9366c1ab8c3fa84c9a44370c06969f803296884b2846a652a47fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:7fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:1d566a6232bbaab3e6d8804bb518a498ed0f904986:bb61cf84de61862207c6a455258bc4db4e15eea0317ff88718b882a06b5cf6ec6fd20c5a269e5d5c805bafbcc579e2590af414c7c227273c102a10070cdfe80f1d566a6232bbaab3e6d8804bb518a498ed0f904986:
|
||||
db36e326d676c2d19cc8fe0c14b709202ecfc761d27089eb6ea4b1bb021ecfa748359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:48359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:1b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4:b6dcd09989dfbac54322a3ce87876e1d62134da998c79d24b50bd7a6a797d86a0e14dc9d7491d6c14a673c652cfbec9f962a38c945da3b2f0879d0b68a9213001b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4:
|
||||
c89955e0f7741d905df0730b3dc2b0ce1a13134e44fef3d40d60c020ef19df77fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:507c94c8820d2a5793cbf3442b3d71936f35fe3afef316:7ef66e5e86f2360848e0014e94880ae2920ad8a3185a46b35d1e07dea8fa8ae4f6b843ba174d99fa7986654a0891c12a794455669375bf92af4cc2770b579e0c507c94c8820d2a5793cbf3442b3d71936f35fe3afef316:
|
||||
4e62627fc221142478aee7f00781f817f662e3b75db29bb14ab47cf8e84104d6b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:d3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5:836afa764d9c48aa4770a4388b654e97b3c16f082967febca27f2fc47ddfd9244b03cfc729698acf5109704346b60b230f255430089ddc56912399d1122de70ad3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5:
|
||||
6b83d7da8908c3e7205b39864b56e5f3e17196a3fc9c2f5805aad0f5554c142dd0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:d0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:6ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c:16e462a29a6dd498685a3718b3eed00cc1598601ee47820486032d6b9acc9bf89f57684e08d8c0f05589cda2882a05dc4c63f9d0431d6552710812433003bc086ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c:
|
||||
19a91fe23a4e9e33ecc474878f57c64cf154b394203487a7035e1ad9cd697b0d2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c:881f5b8c5a030df0f75b6634b070dd27bd1ee3c08738ae349338b3ee6469bbf9760b13578a237d5182535ede121283027a90b5f865d63a6537dca07b44049a0f82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c:
|
||||
1d5b8cb6215c18141666baeefcf5d69dad5bea9a3493dddaa357a4397a13d4de94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:a9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0:3acd39bec8c3cd2b44299722b5850a0400c1443590fd4861d59aae7496acb3df73fc3fdf7969ae5f50ba47dddc435246e5fd376f6b891cd4c2caf5d614b6170ca9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0:
|
||||
6a91b3227c472299089bdce9356e726a40efd840f11002708b7ee55b64105ac29d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:9d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:5cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc:f5875423781b66216cb5e8998de5d9ffc29d1d67107054ace3374503a9c3ef811577f269de81296744bd706f1ac478caf09b54cdf871b3f802bd57f9a6cb91015cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc:
|
||||
93eaa854d791f05372ce72b94fc6503b2ff8ae6819e6a21afe825e27ada9e4fb16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:32fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5:d834197c1a3080614e0a5fa0aaaa808824f21c38d692e6ffbd200f7dfb3c8f44402a7382180b98ad0afc8eec1a02acecf3cb7fde627b9f18111f260ab1db9a0732fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5:
|
||||
941cac69fb7b1815c57bb987c4d6c2ad2c35d5f9a3182a79d4ba13eab253a8ad23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa:0f8fad1e6bde771b4f5420eac75c378bae6db5ac6650cd2bc210c1823b432b48e016b10595458ffab92f7a8989b293ceb8dfed6c243a2038fc06652aaaf16f02bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa:
|
||||
1acdbb793b0384934627470d795c3d1dd4d79cea59ef983f295b9b59179cbb283f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:3f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:7cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56:be71ef4806cb041d885effd9e6b0fbb73d65d7cdec47a89c8a994892f4e55a568c4cc78d61f901e80dbb628b86a23ccd594e712b57fa94c2d67ec266348785077cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56:
|
||||
8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3:04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790ba750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3:
|
||||
f2ab396fe8906e3e5633e99cabcd5b09df0859b516230b1e0450b580b65f616c8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187:a06a23d982d81ab883aae230adbc368a6a9977f003cebb00d4c2e4018490191a84d3a282fdbfb2fc88046e62de43e15fb575336b3c8b77d19ce6a009ce51f50c5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187:
|
||||
550a41c013f79bab8f06e43ad1836d51312736a9713806fafe6645219eaa1f9daf6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:af6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:8bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27:16dc1e2b9fa909eefdc277ba16ebe207b8da5e91143cde78c5047a89f681c33c4e4e3428d5c928095903a811ec002d52a39ed7f8b3fe1927200c6dd0b9ab3e048bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27:
|
||||
19ac3e272438c72ddf7b881964867cb3b31ff4c793bb7ea154613c1db068cb7ef85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:f85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:95872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645:ea855d781cbea4682e350173cb89e8619ccfddb97cdce16f9a2f6f6892f46dbe68e04b12b8d88689a7a31670cdff409af98a93b49a34537b6aa009d2eb8b470195872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645:
|
||||
ca267de96c93c238fafb1279812059ab93ac03059657fd994f8fa5a09239c821017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:e05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531:ac957f82335aa7141e96b59d63e3ccee95c3a2c47d026540c2af42dc9533d5fd81827d1679ad187aeaf37834915e75b147a9286806c8017516ba43dd051a5e0ce05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531:
|
||||
3dff5e899475e7e91dd261322fab09980c52970de1da6e2e201660cc4fce7032f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382:5efe7a92ff9623089b3e3b78f352115366e26ba3fb1a416209bc029e9cadccd9f4affa333555a8f3a35a9d0f7c34b292cae77ec96fa3adfcaadee2d9ced8f805938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382:
|
||||
9a6b847864e70cfe8ba6ab22fa0ca308c0cc8bec7141fbcaa3b81f5d1e1cfcfc34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da:2ab255169c489c54c732232e37c87349d486b1eba20509dbabe7fed329ef08fd75ba1cd145e67b2ea26cb5cc51cab343eeb085fe1fd7b0ec4c6afcd9b979f905838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da:
|
||||
575be07afca5d063c238cd9b8028772cc49cda34471432a2e166e096e2219efc94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:33e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e:58271d44236f3b98c58fd7ae0d2f49ef2b6e3affdb225aa3ba555f0e11cc53c23ad19baf24346590d05d7d5390582082cf94d39cad6530ab93d13efb3927950633e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e:
|
||||
15ffb45514d43444d61fcb105e30e135fd268523dda20b82758b1794231104411772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:1772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e:6828cd7624e793b8a4ceb96d3c2a975bf773e5ff6645f353614058621e58835289e7f31f42dfe6af6d736f2644511e320c0fa698582a79778d18730ed3e8cb08da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e:
|
||||
fe0568642943b2e1afbfd1f10fe8df87a4236bea40dce742072cb21886eec1fa299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d:d59e6dfcc6d7e3e2c58dec81e985d245e681acf6594a23c59214f7bed8015d813c7682b60b3583440311e72a8665ba2c96dec23ce826e160127e18132b030404c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d:
|
||||
5ecb16c2df27c8cf58e436a9d3affbd58e9538a92659a0f97c4c4f994635a8cada768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:da768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9:1c723a20c6772426a670e4d5c4a97c6ebe9147f71bb0a415631e44406e290322e4ca977d348fe7856a8edc235d0fe95f7ed91aefddf28a77e2c7dbfd8f552f0a56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9:
|
||||
d599d637b3c30a82a9984e2f758497d144de6f06b9fba04dd40fd949039d7c846791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:6791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e:ebf10d9ac7c96108140e7def6fe9533d727646ff5b3af273c1df95762a66f32b65a09634d013f54b5dd6011f91bc336ca8b355ce33f8cfbec2535a4c427f8205a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e:
|
||||
30ab8232fa7018f0ce6c39bd8f782fe2e159758bb0f2f4386c7f28cfd2c85898ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc:9af885344cc7239498f712df80bc01b80638291ed4a1d28baa5545017a72e2f65649ccf9603da6eb5bfab9f5543a6ca4a7af3866153c76bf66bf95def615b00c63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc:
|
||||
0ddcdc872c7b748d40efe96c2881ae189d87f56148ed8af3ebbbc80324e38bdd588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:65641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12:c179c09456e235fe24105afa6e8ec04637f8f943817cd098ba95387f9653b2add181a31447d92d1a1ddf1ceb0db62118de9dffb7dcd2424057cbdff5d41d040365641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12:
|
||||
89f0d68299ba0a5a83f248ae0c169f8e3849a9b47bd4549884305c9912b46603aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05:2c691fa8d487ce20d5d2fa41559116e0bbf4397cf5240e152556183541d66cf753582401a4388d390339dbef4d384743caa346f55f8daba68ba7b9131a8a6e0b4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05:
|
||||
0a3c1844e2db070fb24e3c95cb1cc6714ef84e2ccd2b9dd2f1460ebf7ecf13b172e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:72e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:4c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957:87f7fdf46095201e877a588fe3e5aaf476bd63138d8a878b89d6ac60631b3458b9d41a3c61a588e1db8d29a5968981b018776c588780922f5aa732ba6379dd054c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957:
|
||||
c8d7a8818b98dfdb20839c871cb5c48e9e9470ca3ad35ba2613a5d3199c8ab2390d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:90d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff:fa2e994421aef1d5856674813d05cbd2cf84ef5eb424af6ecd0dc6fdbdc2fe605fe985883312ecf34f59bfb2f1c9149e5b9cc9ecda05b2731130f3ed28ddae0b783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff:
|
||||
b482703612d0c586f76cfcb21cfd2103c957251504a8c0ac4c86c9c6f3e429fffd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:fd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:29d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b:58832bdeb26feafc31b46277cf3fb5d7a17dfb7ccd9b1f58ecbe6feb979666828f239ba4d75219260ecac0acf40f0e5e2590f4caa16bbbcd8a155d347967a60729d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b:
|
||||
84e50dd9a0f197e3893c38dbd91fafc344c1776d3a400e2f0f0ee7aa829eb8a22c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:2c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123:69e6a4491a63837316e86a5f4ba7cd0d731ecc58f1d0a264c67c89befdd8d3829d8de13b33cc0bf513931715c7809657e2bfb960e5c764c971d733746093e500f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123:
|
||||
b322d46577a2a991a4d1698287832a39c487ef776b4bff037a05c7f1812bdeeceb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:eb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:19f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5:c7b55137317ca21e33489ff6a9bfab97c855dc6f85684a70a9125a261b56d5e6f149c5774d734f2d8debfc77b721896a8267c23768e9badb910eef83ec25880219f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5:
|
||||
960cab5034b9838d098d2dcbf4364bec16d388f6376d73a6273b70f82bbc98c05e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:5e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308:27d4c3a1811ef9d4360b3bdd133c2ccc30d02c2f248215776cb07ee4177f9b13fc42dd70a6c2fed8f225c7663c7f182e7ee8eccff20dc7b0e1d5834ec5b1ea01f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308:
|
||||
eb77b2638f23eebc82efe45ee9e5a0326637401e663ed029699b21e6443fb48e9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:99e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab:18dc56d7bd9acd4f4daa78540b4ac8ff7aa9815f45a0bba370731a14eaabe96df8b5f37dbf8eae4cb15a64b244651e59d6a3d6761d9e3c50f2d0cbb09c05ec0699e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab:
|
||||
b625aa89d3f7308715427b6c39bbac58effd3a0fb7316f7a22b99ee5922f2dc965a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:65a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:e07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a:01bb901d83b8b682d3614af46a807ba2691358feb775325d3423f549ff0aa5757e4e1a74e9c70f9721d8f354b319d4f4a1d91445c870fd0ffb94fed64664730de07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a:
|
||||
b1c9f8bd03fe82e78f5c0fb06450f27dacdf716434db268275df3e1dc177af427fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:7fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2:4b229951ef262f16978f7914bc672e7226c5f8379d2778c5a2dc0a2650869f7acfbd0bcd30fdb0619bb44fc1ae5939b87cc318133009c20395b6c7eb98107701331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2:
|
||||
6d8cdb2e075f3a2f86137214cb236ceb89a6728bb4a200806bf3557fb78fac6957a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:57a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf:a6cbc947f9c87d1455cf1a708528c090f11ecee4855d1dbaadf47454a4de55fa4ce84b36d73a5b5f8f59298ccf21992df492ef34163d87753b7e9d32f2c3660b7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf:
|
||||
47adc6d6bf571ee9570ca0f75b604ac43e303e4ab339ca9b53cacc5be45b2ccba3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:a3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:ce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739:4e8c318343c306adbba60c92b75cb0569b9219d8a86e5d57752ed235fc109a43c2cf4e942cacf297279fbb28675347e08027722a4eb7395e00a17495d32edf0bce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739:
|
||||
3c19b50b0fe47961719c381d0d8da9b9869d312f13e3298b97fb22f0af29cbbe0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:8ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f:efbd41f26a5d62685516f882b6ec74e0d5a71830d203c231248f26e99a9c6578ec900d68cdb8fa7216ad0d24f9ecbc9ffa655351666582f626645395a31fa7048ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f:
|
||||
34e1e9d539107eb86b393a5ccea1496d35bc7d5e9a8c5159d957e4e5852b3eb00ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:0ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0:32d22904d3e7012d6f5a441b0b4228064a5cf95b723a66b048a087ecd55920c31c204c3f2006891a85dd1932e3f1d614cfd633b5e63291c6d8166f3011431e09a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0:
|
||||
49dd473ede6aa3c866824a40ada4996c239a20d84c9365e4f0a4554f8031b9cf788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c:d2fde02791e720852507faa7c3789040d9ef86646321f313ac557f4002491542dd67d05c6990cdb0d495501fbc5d5188bfbb84dc1bf6098bee0603a47fc2690f3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c:
|
||||
331c64da482b6b551373c36481a02d8136ecadbb01ab114b4470bf41607ac57152a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:52a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:20e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3:22c99aa946ead39ac7997562810c01c20b46bd610645bd2d56dcdcbaacc5452c74fbf4b8b1813b0e94c30d808ce5498e61d4f7ccbb4cc5f04dfc6140825a960020e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3:
|
||||
5c0b96f2af8712122cf743c8f8dc77b6cd5570a7de13297bb3dde1886213cce20510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:0510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575:06e5d8436ac7705b3a90f1631cdd38ec1a3fa49778a9b9f2fa5ebea4e7d560ada7dd26ff42fafa8ba420323742761aca6904940dc21bbef63ff72daab45d430b54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575:
|
||||
bf5ba5d6a49dd5ef7b4d5d7d3e4ecc505c01f6ccee4c54b5ef7b40af6a4541401be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:1be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:16152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64:279cace6fdaf3945e3837df474b28646143747632bede93e7a66f5ca291d2c24978512ca0cb8827c8c322685bd605503a5ec94dbae61bbdcae1e49650602bc0716152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64:
|
||||
65de297b70cbe80980500af0561a24db50001000125f4490366d8300d3128592ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b:7a9b736b01cc92a3349f1a3c32dbd91959825394ff443c567405e899c8185ce8fad9500e1fce89d95a6253c00477435acf04bff993de1b00495def0834ee1f07131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b:
|
||||
0826e7333324e7ec8c764292f6015d4670e9b8d7c4a89e8d909e8ef435d18d15ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:7f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5:4bac7fabec8724d81ab09ae130874d70b5213492104372f601ae5abb10532799373c4dad215876441f474e2c006be37c3c8f5f6f017d0870414fd276a8f428087f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5:
|
||||
00ad6227977b5f38ccda994d928bba9086d2daeb013f8690db986648b90c1d4591a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:91a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:cb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336:dc501db79fd782bc88cae792557d5d273f9ba560c7d90037fe84ac879d684f612a77452c4443e95c07b8be192c35769b17bbdfca42280de796d92119d833670dcb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336:
|
||||
1521c6dbd6f724de73eaf7b56264f01035c04e01c1f3eb3cbe83efd26c439ada2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:3e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6:a817ed23ec398a128601c1832dc6af7643bf3a5f517bcc579450fdb4759028f4966164125f6ebd0d6bf86ff298a39c766d0c21fdb0cbfdf81cd0eb1f03cd8a083e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6:
|
||||
17e5f0a8f34751babc5c723ecf339306992f39ea065ac140fcbc397d2dd32c4b4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7:efe2cb63fe7b4fc98946dc82fb6998e741ed9ce6b9c1a93bb45bc0a7d8396d7405282b43fe363ba5b23589f8e1fae130e157ce888cd72d053d0cc19d257a4300c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7:
|
||||
0cd7aa7d605e44d5ffb97966b2cb93c189e4c5a85db87fad7ab8d62463c59b594889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:4889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:28a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557:bf9115fd3d02706e398d4bf3b02a82674ff3041508fd39d29f867e501634b9261f516a794f98738d7c7013a3f2f858ffdd08047fb6bf3dddfb4b4f4cbeef300328a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557:
|
||||
33371d9e892f9875052ac8e325ba505e7477c1ace24ba7822643d43d0acef3de35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:27a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0:985ca446ddc007827cc8f2852cbd8115ef8c5975e9d7ce96d74dfed859aa14a4c15254006bea5e08359efe2625d715e0897ee5a16f151203be5010418637de0527a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0:
|
||||
beedb8073df58f8c1bffbdbd77ec7decb2c82a9babecefc0331507bdc2c2a7e7b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:35ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab:8c890cccadc7760e1e82e43c44b3dc0b685a48b479ae13cc0a6b0557d0fb1cbabba63d2a96843412ea8d36c50acbf52b92cfb2dce49dc48af6ddcf8ee47a860835ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab:
|
||||
9184ef618816832592bc8eb35f4ffd4ff98dfbf7776c90f2aad212ce7e03351e687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc:b3c24e75132c563475422d5ea412b5c1e8e6e5ea1c08ead1393c412da134c9a1638284ea7e2ca032fe3d3e32a9066a8c8839903f6ef46e966bb5e492d8c2aa00729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc:
|
||||
354e13152ee1fe748a1252204c6527bdc1b1eb2eb53678150e6359924708d812d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:8e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a:de2b46e65f3decef34332e500f2e11306fbdcf1be85a1c1ee68ba3045dcec2c7be608d22927da1f44c0e2083ae622cf3c29d893887994efcfa2ca594f5051f038e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a:
|
||||
7ff62d4b3c4d99d342d4bb401d726b21e99f4ef592149fc311b68761f5567ff67fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:7fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a:058f79927fbf6178724815c7b11c63baaa90bcc15d7272be082f8a9141861c816433055f6cf6491424853f9ec78bb91ace913a93411b4e5ed58bc4ba5715c60a99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a:
|
||||
6cabadd03f8a2e6ebab96a74f80e18164e4d1b6baa678f5a82e25604af989aaf2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e:4e65c6c1d493045e8a9250e397c1d1d30ffed24db66a8961aa458f8f0fcb760c39fe8657d7ab8f84000b96d519717cff71f926522c1efec7f8b2624eae55f60c279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e:
|
||||
0fa0c32c3ae34be51b92f91945405981a8e202488558a8e220c288c7d6a5532dd6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:d6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:53f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a:7e9ab85ee94fe4b35dcb545329a0ef25923de5c9dc23e7df1a7e77ab0dcfb89e03f4e785ca6429cb2b0df50da6230f733f00f33a45c4e576cd40bdb84f1ae00153f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a:
|
||||
7b06f88026fa86f39fce2426f67cc5996bedd0cfc4b5ebb1b5e3edbb47e080aa3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:71175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944:42f133e34e3eb7032a133ed781537ec62e44a5ce8381e5e0bf9e13a914a4b2c757811d6d3b1e86672424ea4230d10f7c610abb7069e61e319b4066a2bd7bc90071175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944:
|
||||
c3f5e149968a24f4de9119531975f443015ccca305d7119ed4749e8bf6d94fc739aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:39aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4:5fa2b531677b00b85b0a313cbd479f55f4ab3ec5cfce5e454d2b74176ccc3399c899f9d6b51ed4c1e76185ac9fe730c4b4014044f7041185bc3c85722eb2ea02c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4:
|
||||
42305c9302f45ea6f87e26e2208fd94b3c4ad037b1b6c83cf6677aa1096a013c3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:d110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b:18d05e5d01668e83f40fa3bbee28b388acf318d1b0b5ad668c672f345c8eda14c2f884cd2a9039459ce0810bc5b580fe70d3964a43edb49e73a6ff914bbf040cd110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b:
|
||||
c57a43dcd7bab8516009546918d71ad459b7345efdca8d4f19929875c839d7222083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:2083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d:1edef9bc036971f1fa88edf45393c802e6c1a1631c8a06871a09a320821dce40beca97e53a0361a955a4c6d60b8ca8e400c81340911ccb4f56284041cdbb1804a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d:
|
||||
2dddb6b8fd04fa90ece1a709f8418f2e5d0c9c43afe7cfce19e6ad15a73476f78059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:8059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38:c634ea7bf72e895a2e796e2834201415b8b45e05e045559284eb9052c0e84f62a5a9f0c9764f7576788c7228b19ef517c195497325a48a9344b147c12fd75509474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38:
|
||||
5547f1004baedfce5cfc0850b05302374aad24f6163994ecd751df3af3c106207ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:7ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6:29df3ad589009c667baa5e72dabb4e53cb7876de4e7efe5cc21ead7fa878db57f97c1103ddb39a861eb88653c1d4ec3b4306e4584b47b8bc90423119e7e4af00a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6:
|
||||
3dd7203c237aefe9e38a201ff341490179905f9f100828da18fcbe58768b5760f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631:4c036935a96abc0d050d907bedbe9946fb97439f039c742e051ccf09add7df44d17da98c2ca01bdc2424da1e4debf347f8fff48ac8030d2cc07f9575c044be04db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631:
|
||||
282775df9ebbd7c5a65f3a2b096e36ee64a8f8ea719da77758739e4e7476111da2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:a2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:14cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979:15763973859402907d8dcb86adc24a2a168ba3abf2246173d6348afed51ef60b0c0edeff4e10bcef4c6e5778c8bc1f5e9ee0237373445b455155d23de127a20214cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979:
|
||||
4730a5cf9772d7d6665ba787bea4c95252e6ecd63ec62390547bf100c0a46375f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e:552c7347bdfe131646ce0932d82a36d2c1b76d7c30ee890e0592e19f9d18b9a56f48d7a9b68c017da6b550c943af4a907baf317e419fbbc96f6cf4bfad42de00e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e:
|
||||
2770aadd1d123e9547832dfb2a837eba089179ef4f23abc4a53f2a714e423ee23c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:3c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64:f267715e9a84c7314f2d5869ef4ab8d2149a13f7e8e1c728c423906293b49ce6283454dd1c7b04741df2eabedc4d6ab1397dc95a679df04d2c17d66c79bb7601a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64:
|
||||
4fdab7c1600e70114b11f533242376af7614b4d5da046ac4bedea21d8a361598a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a:5075c090cfbeb6b01802af7f4da5aa4f434d5ee2f3530eebb75c85e08621f83edc08aa96693894a4277633ba81e19e9e55af5c495daa5e1a6f8cbb79c01c7207da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a:
|
||||
264504604e70d72dc4474dbb34913e9c0f806dfe18c7879a41762a9e4390ec61eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa:eea439a00f7e459b402b835150a779eed171ab971bd1b58dcc7f9386dadd583de8dc69e267121dde41f0f9493d450b16219cdf3c22f09482ce402fe17ca49e08901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa:
|
||||
2ca7447a3668b748b1fd3d52d2080d30e34d397bb2846caf8f659ac168788ca5ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95:f93ada15ae9cd2b54f26f86f0c28392aed5eb6b6b44d01a4e33a54e7da37c38e8d53366f73fd85be642e4ec81236d163f0d025e76c8bbdd65d43df49f09c1f01a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95:
|
||||
494ea9bcce26885b7d17d1fc114448f239f0ce46e5f247b4c999fa86296924726901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:6901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:3badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7:548a093a680361b7dc56f14503b55eeec3b3f4fd4ca99d6aedce0830f7f4ae2f7328539b34c48fc9760922333dae9c7c017e7db73b8faa6c06be05e347992b063badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7:
|
||||
00d735ebaee75dd579a40dfd82508274d01a1572df99b811d5b01190d82192e4ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:59c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308:dcdc54611937d2bd06cacd9818b3be15ce7425427a75f50d197a337a3b8ba6714ef48866f243bd5ac7415e914517a2c1c5a953f432b99db0e620d64f74eb850559c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308:
|
||||
8c34b905440b61911d1d8137c53d46a1a76d4609af973e18eb4c5709295627bbb69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:b69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da:3e0b72073dc9375eedcca6c4fc1cd315938a050c92716bd2284f4629a962beec0b7d7cf16ab923d58f5b90d3901a8e5c75c8f17dab9998e007d8c49511973d0e30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da:
|
||||
77a83e18c9f000eeff7deeac959ecba2206c0aa39d2f0e2aed5729482a7a022962b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:62b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12:1eaad8420ac12c99ac1ff4476678e3cbbe94da6a797f174664d5ee0f641433fb1e7cb2f5613e10805df8654cd8e0d45d96230932bc7f20b04eae836435134309f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12:
|
||||
73b03373ef1fd849005ecd6270dd9906f19f4439e40376cdbc520902bc976812663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e:a40abe98fc69da8a1ff9ff5c2cca93632e975980ee8b82c3c376022d6524ab736d01b072f2b681b5f1cd3ea067012ed6d074e949c42327a366caa9e4750a3c08d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e:
|
||||
eab179e41ed5c889ffe6aabdc054faf1307c395e46e313e17a14fe01023ffa3086f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:86f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd:143cb28027c2f82e375e5f340e7fe6e60ce7bd51000b49c74168af85e26ed2ed630ed2672090164cc54b052da694ebdd21a21b3053f4dcfd7895ea5f6c8aa80d971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd:
|
||||
fbf146ebd51075570ec51ac410ae9f391db75b610ada6362b4dbd949656cfb66be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:cd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627:6768006fe0f201b217dd10eb05d4b82adcfeb2ecfc8373c3308f4150394811eb60491881a2e53d1289d96478e18a64c34b2a19832cdccfd96a2e4a0c469fdc0bcd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627:
|
||||
dff0eb6b426dea2fd33c1d3fc24df9b31b486facb7edb8502954a3e8da99d9fdc245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:c245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1:6b48b10f545ddb7a89cd5829f4e5b20146cf6bc96e550d06f65de8bdae7ccdded26cd630f86c9266bccf88e924033e04f83a54f8290d7f734cf8673cca8f9703e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1:
|
||||
9f32958c7679b90fd5036056a75ec2eb2f56ec1effc7c012461dc89a3a1674201d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:1d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a:9881a5763bdb259a3fefbba3d957162d6c70b804fa94ab613406a6ec42505b8789465ca1a9a33e1895988842270c55e5bdd5483f6b17b31781b593507a6c1808a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a:
|
||||
f86d6f766f88b00717b7d6327eb26cf3ceeba5385184426f9cfd8295e2421ff2cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:da8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc:ec61c0b292203a8f1d87235ede92b74723c8d23408423773ae50b1e9bc4464e03e446da9dce4c39f6dd159bea26c009ed00120bc36d4a247dc0d24bcefcc110cda8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc:
|
||||
a5b34cefab9479df8389d7e6f6c146aa8affb0bec837f78af64624a145cc344e7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412:2fbd899d72b6d39e4f45b8b62cbbd5f3c0acb1ad8540913fa585877e91ccfef7bee50a4b0f9fedf5cc1e0d1953ad399c8389a93391e1b7c929af6d6f3b796c08e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412:
|
||||
ad75c9ce299c4d59393367d77a4c9f8df8dcec765c6dbd25b527fb7669913604b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:62fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86:6b7ef27bcfbf2b714985033764fccff555e3f5bc44610d6c8c62117cb3831a07f4a8bddb0eaed1d46b0289b15de1aa4dcc17d71be96a09e66ba4dc4627c7870562fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86:
|
||||
1ced574529b9b416977e92eb39448a8717cac2934a243a5c44fb44b73ccc16da85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f:e0303aefe08a77738dcc657afbb9b835ed279613a53c73fdc5ddbfb350e5cff4d6c9bb43dc07c95bf4e23b64c40f8804c7169952e3c8d59a7197241bfed0740f1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f:
|
||||
f0790d93e2d3b84f61ef4c807147aba410e415e72b71b0d61d01026fed99da3defdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:efdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:7973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f:08773a6a78762cbb1e25fcbb29139941bdf16f4e09a1fa08fc701f32f933edd74c0ae983c12a0a5b020b6bcf44bb719dde8ed0781a8298265640e1608c98b3017973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f:
|
||||
4cb9df7ce6fae9d62ba09e8eb70e4c969bdeafcb5ec7d7024326e6603b0621bf018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:14627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e:e33c07836c537d6bfbd0f4592d6e35b163499ba78dc7ffcec565d04f9a7db781943e29e6ce76763e9baddf57437fd9c6b03239a6e6850e4502a356c2e12c370514627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e:
|
||||
a136e009d53e5ef59d0946bc175663a86bc0fcd29eadd95cfc9d266037b1e4fb9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e:bc094ba91c115dee15d753361a75f3f03d6af45c92157e95dbe8d32194b6c5ce72b9dc66f73df12dca0b639f3e791d478616a1f8d7359a42c8eae0dda16b1606a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e:
|
||||
ff0f1c57dd884fbeea6e2917282b79ba67f8a6851267b9f4636dafda33bd2b5bfef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:fef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a:d5008486726cce330a29dd7e4d7474d735798201afd1206feb869a112e5b43523c06976761be3cf9b2716378273c94f93572a7d2b8982634e0755c632b449008522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a:
|
||||
0bc6af64de5709d3dbc28f7ef6d3fe28b6de529f08f5857ccb910695de454f56fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221:dbc7134d1cd6b0813b53352714b6df939498e91cf37c324337d9c088a1b998347d26185b430900412929e4f63e910379fc42e355a4e98f6fee27dafad1957206ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221:
|
||||
2f5e83bd5b412e71ae3e9084cd369efcc79bf6037c4b174dfd6a11fb0f5da218a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:b766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea:9f80922bc8db32d0cc43f9936affebe7b2bc35a5d82277cd187b5d50dc7fc4c4832fffa34e9543806b485c04548e7c75429425e14d55d91fc1052efd8667430bb766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea:
|
||||
722a2da50e42c11a61c9afac7be1a2fed2267d650f8f7d8e5bc706b807c1b91dfd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:fd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd:c2695a57172aaa31bd0890f231ca8eeec0287a87172669a899ad0891cea4c47579b50420e791cdec8c182c8a0e8dde21b2480b0cfd8111e28e5603347a352d04173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd:
|
||||
5fe9c3960ed5bd374cc94d42357e6a24dc7e3060788f726365defacf13cd12da0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77:379f9c54c413af0d192e9bc736b29da9d521e7ba7841d309f9bcc1e742ec4308fe9f7ba51e0b22aed487cb4aa3913b9bebfb3aacd38f4039f9bbbebe1ad80002c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77:
|
||||
ec2fa541ac14b414149c3825eaa7001b795aa1957d4040dda92573904afa7ee471b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:71b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:2749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14:84d18d56f964e3776759bba92c510c2b6d574555c3cddade212da90374554991e7d77e278d63e34693e1958078cc3685f8c41c1f5342e351899638ef612114012749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14:
|
||||
6132692a5ef27bf476b1e991e6c431a8c764f1aebd470282db3321bb7cb09c207a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:7a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:a9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde:eb677f3347e1a1ea929efdf62bf9105a6c8f4993033b4f6d03cb0dbf9c742b270704e383ab7c0676bdb1ad0ce9b16673083c9602ec10ae1dd98e8748b336440ba9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde:
|
||||
f219b2101164aa9723bde3a7346f68a35061c01f9782072580ba32df903ba891f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f:17f0127ca3bafa5f4ee959cd60f772be87a0034961517e39a0a1d0f4b9e26db1336e60c82b352c4cbacdbbd11771c3774f8cc5a1a795d6e4f4ebd51def36770b015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f:
|
||||
fc180035aec0f5ede7bda93bf77ade7a81ed06de07ee2e3aa8576be81608610a4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e:a43a71c3a19c35660dae6f31a254b8c0ea3593fc8fca74d13640012b9e9473d4afe070db01e7fb399bf4ca6070e062180011285a67dd6858b761e46c6bd32004b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e:
|
||||
a2836a65427912122d25dcdfc99d7046fe9b53d5c1bb23617f11890e94ca93ed8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb:e6a9a6b436559a4320c45c0c2c4a2aedecb90d416d52c82680ac7330d062aebef3e9ac9f2c5ffa455c9be113013a2b282e5600fd306435ada83b1e48ba2a3605813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb:
|
||||
f051af426d0c3282fafc8bf912ade1c24211a95ad200e1eef549320e1cb1a252fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:b48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428:b8f713578a64466719aceb432fce302a87cf066bf3e102a350616921a840964bfc7e685d8fd17455ac3eb4861edcb8979d35e3a4bd82a078cd707721d733400eb48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428:
|
||||
a103e92672c65f81ea5da1fff1a4038788479e941d503a756f4a755201a57c1dee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:ee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb:2aa2035c2ce5b5e6ae161e168f3ad0d6592bcf2c4a049d3ed342fceb56be9c7cb372027573ae0178e8878ebefca7b030327b8aad41857de58cb78e1a00cbac05b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb:
|
||||
d47c1b4b9e50cbb71fd07d096d91d87213d44b024373044761c4822f9d9df880f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:88d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0:627e7ca7e34ed6331d62b9541c1ea9a9292be7b0a65d805e266b5122272a82db7d765acc7e2a290d685804922f91ed04a3c382c03ff21a1768f584413c4e5f0088d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0:
|
||||
fc0c32c5eb6c71ea08dc2b300cbcef18fdde3ea20f68f21733237b4ddaab900e47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96:6887c6e2b98a82af5ee3dfa7ca2cb25d9c10745620a82956acba85cb57c8ec24279fa42f092359a1b6bbeafba050f14b6288209e6ef7bc1e0a2b872c1138f305a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96:
|
||||
a8d73d639a23cc6a967ef31bcabb5d063e53e1eab8fcc7cab9bc3a17fde9c2f88daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:8daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:fd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1:c4dcef1a2453939b364b340250c3129431431d5ba3f47670ab07ce680c69bf28b678627c76a6360fc40dc109aa7dea371b825e46134f624572182acf3957e70ffd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1:
|
||||
79c7dcb7d59a8df6b2b2ba0413059d89680995c20e916da01b8f067dc60cdeb4298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b:7b7cbe44c771e4371bae13b0722babcc1064155732962f407cba2acd35381d42210bece822f4681121fd4dab745a1f3077922fba1a78045b712902baccac660e5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b:
|
||||
b9ced0412593fefed95e94ac965e5b23ff9d4b0e797db02bf497994d3b793e60c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b:f1b797ded8a6942b12626848340fb719fcddafd98f33e2992d357bfdd35933c7ac561e5b2f939464338c5666854ca885c4d046eb2c54e48a1b5ed266ad34de05555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b:
|
||||
81da168f02d46bb87cda845da43f8a6cba2c016878d6f49c6f061a60f155a04aaff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:aff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b:4aaca947e3f22cc8b8588ee030ace8f6b5f5711c2974f20cc18c3b655b07a5bc1366b59a1708032d12cae01ab794f8cbcc1a330874a75035db1d69422d2fc00c6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b:
|
||||
af2e60da0f29bb1614fc3f193cc353331986b73f3f9a0aec9421b9473d6a4b6ac8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:c8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:7dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7:50f9f941a8da9f6240f76d2fa3b06dd6b2292ed32d1c05218097d34d8a19dfe553f76ae3c6b4a2ed20852128461540decf418f52d38e64037eec7771bd1afe007dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7:
|
||||
605f90b53d8e4a3b48b97d745439f2a0807d83b8502e8e2979f03e8d376ac9feaa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:aa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:3bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0:dd0212e63288cbe14a4569b4d891da3c7f92727c5e7f9a801cf9d6827085e7095b669d7d45f882ca5f0745dccd24d87a57181320191e5b7a47c3f7f2dccbd7073bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0:
|
||||
9e2c3d189838f4dd52ef0832886874c5ca493983ddadc07cbc570af2ee9d6209f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc:38a31b6b465084738262a26c065fe5d9e2886bf9dd35cde05df9bad0cc7db401c750aa19e66090bce25a3c721201e60502c8c10454346648af065eab0ee7d80f19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc:
|
||||
575f8fb6c7465e92c250caeec1786224bc3eed729e463953a394c9849cba908f71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:2cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553:903b484cb24bc503cdced844614073256c6d5aa45f1f9f62c7f22e5649212bc1d6ef9eaa617b6b835a6de2beff2faac83d37a4a5fc5cc3b556f56edde2651f022cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553:
|
11136
ed25519-dalek/VALIDATIONVECTORS
Normal file
11136
ed25519-dalek/VALIDATIONVECTORS
Normal file
File diff suppressed because it is too large
Load Diff
101
ed25519-dalek/benches/ed25519_benchmarks.rs
Normal file
101
ed25519-dalek/benches/ed25519_benchmarks.rs
Normal file
@ -0,0 +1,101 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2018-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
mod ed25519_benches {
|
||||
use super::*;
|
||||
use ed25519_dalek::Signature;
|
||||
use ed25519_dalek::Signer;
|
||||
use ed25519_dalek::SigningKey;
|
||||
use rand::prelude::ThreadRng;
|
||||
use rand::thread_rng;
|
||||
|
||||
fn sign(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let keypair: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let msg: &[u8] = b"";
|
||||
|
||||
c.bench_function("Ed25519 signing", move |b| b.iter(|| keypair.sign(msg)));
|
||||
}
|
||||
|
||||
fn verify(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let keypair: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let msg: &[u8] = b"";
|
||||
let sig: Signature = keypair.sign(msg);
|
||||
|
||||
c.bench_function("Ed25519 signature verification", move |b| {
|
||||
b.iter(|| keypair.verify(msg, &sig))
|
||||
});
|
||||
}
|
||||
|
||||
fn verify_strict(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let keypair: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let msg: &[u8] = b"";
|
||||
let sig: Signature = keypair.sign(msg);
|
||||
|
||||
c.bench_function("Ed25519 strict signature verification", move |b| {
|
||||
b.iter(|| keypair.verify_strict(msg, &sig))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
fn verify_batch_signatures(c: &mut Criterion) {
|
||||
use ed25519_dalek::verify_batch;
|
||||
|
||||
static BATCH_SIZES: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256];
|
||||
|
||||
// Benchmark batch verification for all the above batch sizes
|
||||
let mut group = c.benchmark_group("Ed25519 batch signature verification");
|
||||
for size in BATCH_SIZES {
|
||||
let name = format!("size={size}");
|
||||
group.bench_function(name, |b| {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let keypairs: Vec<SigningKey> = (0..size)
|
||||
.map(|_| SigningKey::generate(&mut csprng))
|
||||
.collect();
|
||||
let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let messages: Vec<&[u8]> = (0..size).map(|_| msg).collect();
|
||||
let signatures: Vec<Signature> =
|
||||
keypairs.iter().map(|key| key.sign(&msg)).collect();
|
||||
let verifying_keys: Vec<_> =
|
||||
keypairs.iter().map(|key| key.verifying_key()).collect();
|
||||
|
||||
b.iter(|| verify_batch(&messages[..], &signatures[..], &verifying_keys[..]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If the above function isn't defined, make a placeholder function
|
||||
#[cfg(not(feature = "batch"))]
|
||||
fn verify_batch_signatures(_: &mut Criterion) {}
|
||||
|
||||
fn key_generation(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
|
||||
c.bench_function("Ed25519 keypair generation", move |b| {
|
||||
b.iter(|| SigningKey::generate(&mut csprng))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = ed25519_benches;
|
||||
config = Criterion::default();
|
||||
targets =
|
||||
sign,
|
||||
verify,
|
||||
verify_strict,
|
||||
verify_batch_signatures,
|
||||
key_generation,
|
||||
}
|
||||
}
|
||||
|
||||
criterion::criterion_main!(ed25519_benches::ed25519_benches);
|
BIN
ed25519-dalek/docs/assets/ed25519-malleability.png
Normal file
BIN
ed25519-dalek/docs/assets/ed25519-malleability.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 43 KiB |
12
ed25519-dalek/docs/assets/rustdoc-include-katex-header.html
Normal file
12
ed25519-dalek/docs/assets/rustdoc-include-katex-header.html
Normal file
@ -0,0 +1,12 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.3/dist/katex.min.css" integrity="sha384-Juol1FqnotbkyZUT5Z7gUPjQ9gzlwCENvUZTpQBAPxtusdwFLRy382PSDx5UUJ4/" crossorigin="anonymous">
|
||||
|
||||
<!-- The loading of KaTeX is deferred to speed up page rendering -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.3/dist/katex.min.js" integrity="sha384-97gW6UIJxnlKemYavrqDHSX3SiygeOwIZhwyOKRfSaf0JWKRVj9hLASHgFTzT+0O" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- To automatically render math in text elements, include the auto-render extension: -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.3/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"
|
||||
onload="renderMathInElement(document.body);"></script>
|
||||
|
||||
<style>
|
||||
.katex { font-size: 1em !important; }
|
||||
</style>
|
242
ed25519-dalek/src/batch.rs
Normal file
242
ed25519-dalek/src/batch.rs
Normal file
@ -0,0 +1,242 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! Batch signature verification.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::iter::once;
|
||||
|
||||
use curve25519_dalek::constants;
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use curve25519_dalek::traits::VartimeMultiscalarMul;
|
||||
|
||||
pub use curve25519_dalek::digest::Digest;
|
||||
|
||||
use merlin::Transcript;
|
||||
|
||||
use rand_core::RngCore;
|
||||
|
||||
use sha2::Sha512;
|
||||
|
||||
use crate::errors::InternalError;
|
||||
use crate::errors::SignatureError;
|
||||
use crate::signature::InternalSignature;
|
||||
use crate::VerifyingKey;
|
||||
|
||||
/// An implementation of `rand_core::RngCore` which does nothing. This is necessary because merlin
|
||||
/// demands an `Rng` as input to `TranscriptRngBuilder::finalize()`. Using this with `finalize()`
|
||||
/// yields a PRG whose input is the hashed transcript.
|
||||
struct ZeroRng;
|
||||
|
||||
impl rand_core::RngCore for ZeroRng {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
rand_core::impls::next_u32_via_fill(self)
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
rand_core::impls::next_u64_via_fill(self)
|
||||
}
|
||||
|
||||
/// A no-op function which leaves the destination bytes for randomness unchanged.
|
||||
///
|
||||
/// In this case, the internal merlin code is initialising the destination
|
||||
/// by doing `[0u8; …]`, which means that when we call
|
||||
/// `merlin::TranscriptRngBuilder.finalize()`, rather than rekeying the
|
||||
/// STROBE state based on external randomness, we're doing an
|
||||
/// `ENC_{state}(00000000000000000000000000000000)` operation, which is
|
||||
/// identical to the STROBE `MAC` operation.
|
||||
fn fill_bytes(&mut self, _dest: &mut [u8]) {}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// `TranscriptRngBuilder::finalize()` requires a `CryptoRng`
|
||||
impl rand_core::CryptoRng for ZeroRng {}
|
||||
|
||||
// We write our own gen() function so we don't need to pull in the rand crate
|
||||
fn gen_u128<R: RngCore>(rng: &mut R) -> u128 {
|
||||
let mut buf = [0u8; 16];
|
||||
rng.fill_bytes(&mut buf);
|
||||
u128::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Verify a batch of `signatures` on `messages` with their respective `verifying_keys`.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `messages` is a slice of byte slices, one per signed message.
|
||||
/// * `signatures` is a slice of `Signature`s.
|
||||
/// * `verifying_keys` is a slice of `VerifyingKey`s.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A `Result` whose `Ok` value is an empty tuple and whose `Err` value is a
|
||||
/// `SignatureError` containing a description of the internal error which
|
||||
/// occurred.
|
||||
///
|
||||
/// ## On Deterministic Nonces
|
||||
///
|
||||
/// The nonces for batch signature verification are derived purely from the inputs to this function
|
||||
/// themselves.
|
||||
///
|
||||
/// In any sigma protocol it is wise to include as much context pertaining
|
||||
/// to the public state in the protocol as possible, to avoid malleability
|
||||
/// attacks where an adversary alters publics in an algebraic manner that
|
||||
/// manages to satisfy the equations for the protocol in question.
|
||||
///
|
||||
/// For ed25519 batch verification we include the following as scalars in the protocol transcript:
|
||||
///
|
||||
/// * All of the computed `H(R||A||M)`s to the protocol transcript, and
|
||||
/// * All of the `s` components of each signature.
|
||||
///
|
||||
/// The former, while not quite as elegant as adding the `R`s, `A`s, and
|
||||
/// `M`s separately, saves us a bit of context hashing since the
|
||||
/// `H(R||A||M)`s need to be computed for the verification equation anyway.
|
||||
///
|
||||
/// The latter prevents a malleability attack wherein an adversary, without access
|
||||
/// to the signing key(s), can take any valid signature, `(s,R)`, and swap
|
||||
/// `s` with `s' = -z1`. This doesn't constitute a signature forgery, merely
|
||||
/// a vulnerability, as the resulting signature will not pass single
|
||||
/// signature verification. (Thanks to Github users @real_or_random and
|
||||
/// @jonasnick for pointing out this malleability issue.)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ed25519_dalek::{
|
||||
/// verify_batch, SigningKey, VerifyingKey, Signer, Signature,
|
||||
/// };
|
||||
/// use rand::rngs::OsRng;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_keys: Vec<_> = (0..64).map(|_| SigningKey::generate(&mut csprng)).collect();
|
||||
/// let msg: &[u8] = b"They're good dogs Brant";
|
||||
/// let messages: Vec<_> = (0..64).map(|_| msg).collect();
|
||||
/// let signatures: Vec<_> = signing_keys.iter().map(|key| key.sign(&msg)).collect();
|
||||
/// let verifying_keys: Vec<_> = signing_keys.iter().map(|key| key.verifying_key()).collect();
|
||||
///
|
||||
/// let result = verify_batch(&messages, &signatures, &verifying_keys);
|
||||
/// assert!(result.is_ok());
|
||||
/// # }
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_batch(
|
||||
messages: &[&[u8]],
|
||||
signatures: &[ed25519::Signature],
|
||||
verifying_keys: &[VerifyingKey],
|
||||
) -> Result<(), SignatureError> {
|
||||
// Return an Error if any of the vectors were not the same size as the others.
|
||||
if signatures.len() != messages.len()
|
||||
|| signatures.len() != verifying_keys.len()
|
||||
|| verifying_keys.len() != messages.len()
|
||||
{
|
||||
return Err(InternalError::ArrayLength {
|
||||
name_a: "signatures",
|
||||
length_a: signatures.len(),
|
||||
name_b: "messages",
|
||||
length_b: messages.len(),
|
||||
name_c: "verifying_keys",
|
||||
length_c: verifying_keys.len(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Make a transcript which logs all inputs to this function
|
||||
let mut transcript: Transcript = Transcript::new(b"ed25519 batch verification");
|
||||
|
||||
// We make one optimization in the transcript: since we will end up computing H(R || A || M)
|
||||
// for each (R, A, M) triplet, we will feed _that_ into our transcript rather than each R, A, M
|
||||
// individually. Since R and A are fixed-length, this modification is secure so long as SHA-512
|
||||
// is collision-resistant.
|
||||
// It suffices to take `verifying_keys[i].as_bytes()` even though a `VerifyingKey` has two
|
||||
// fields, and `as_bytes()` only returns the bytes of the first. This is because of an
|
||||
// invariant guaranteed by `VerifyingKey`: the second field is always the (unique)
|
||||
// decompression of the first. Thus, the serialized first field is a unique representation of
|
||||
// the entire `VerifyingKey`.
|
||||
let hrams: Vec<[u8; 64]> = (0..signatures.len())
|
||||
.map(|i| {
|
||||
// Compute H(R || A || M), where
|
||||
// R = sig.R
|
||||
// A = verifying key
|
||||
// M = msg
|
||||
let mut h: Sha512 = Sha512::default();
|
||||
h.update(signatures[i].r_bytes());
|
||||
h.update(verifying_keys[i].as_bytes());
|
||||
h.update(&messages[i]);
|
||||
*h.finalize().as_ref()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Update transcript with the hashes above. This covers verifying_keys, messages, and the R
|
||||
// half of signatures
|
||||
for hram in hrams.iter() {
|
||||
transcript.append_message(b"hram", hram);
|
||||
}
|
||||
// Update transcript with the rest of the data. This covers the s half of the signatures
|
||||
for sig in signatures {
|
||||
transcript.append_message(b"sig.s", sig.s_bytes());
|
||||
}
|
||||
|
||||
// All function inputs have now been hashed into the transcript. Finalize it and use it as
|
||||
// randomness for the batch verification.
|
||||
let mut rng = transcript.build_rng().finalize(&mut ZeroRng);
|
||||
|
||||
// Convert all signatures to `InternalSignature`
|
||||
let signatures = signatures
|
||||
.iter()
|
||||
.map(InternalSignature::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
// Convert the H(R || A || M) values into scalars
|
||||
let hrams: Vec<Scalar> = hrams
|
||||
.iter()
|
||||
.map(Scalar::from_bytes_mod_order_wide)
|
||||
.collect();
|
||||
|
||||
// Select a random 128-bit scalar for each signature.
|
||||
let zs: Vec<Scalar> = signatures
|
||||
.iter()
|
||||
.map(|_| Scalar::from(gen_u128(&mut rng)))
|
||||
.collect();
|
||||
|
||||
// Compute the basepoint coefficient, ∑ s[i]z[i] (mod l)
|
||||
let B_coefficient: Scalar = signatures
|
||||
.iter()
|
||||
.map(|sig| sig.s)
|
||||
.zip(zs.iter())
|
||||
.map(|(s, z)| z * s)
|
||||
.sum();
|
||||
|
||||
// Multiply each H(R || A || M) by the random value
|
||||
let zhrams = hrams.iter().zip(zs.iter()).map(|(hram, z)| hram * z);
|
||||
|
||||
let Rs = signatures.iter().map(|sig| sig.R.decompress());
|
||||
let As = verifying_keys.iter().map(|pk| Some(pk.point));
|
||||
let B = once(Some(constants::ED25519_BASEPOINT_POINT));
|
||||
|
||||
// Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0
|
||||
let id = EdwardsPoint::optional_multiscalar_mul(
|
||||
once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams),
|
||||
B.chain(Rs).chain(As),
|
||||
)
|
||||
.ok_or(InternalError::Verify)?;
|
||||
|
||||
if id.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
32
ed25519-dalek/src/constants.rs
Normal file
32
ed25519-dalek/src/constants.rs
Normal file
@ -0,0 +1,32 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! Common constants such as buffer sizes for keypairs and signatures.
|
||||
|
||||
/// The length of a ed25519 `Signature`, in bytes.
|
||||
pub const SIGNATURE_LENGTH: usize = 64;
|
||||
|
||||
/// The length of a ed25519 `SecretKey`, in bytes.
|
||||
pub const SECRET_KEY_LENGTH: usize = 32;
|
||||
|
||||
/// The length of an ed25519 `PublicKey`, in bytes.
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
|
||||
/// The length of an ed25519 `Keypair`, in bytes.
|
||||
pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH;
|
||||
|
||||
/// The length of the "key" portion of an "expanded" ed25519 secret key, in bytes.
|
||||
const EXPANDED_SECRET_KEY_KEY_LENGTH: usize = 32;
|
||||
|
||||
/// The length of the "nonce" portion of an "expanded" ed25519 secret key, in bytes.
|
||||
const EXPANDED_SECRET_KEY_NONCE_LENGTH: usize = 32;
|
||||
|
||||
/// The length of an "expanded" ed25519 key, `ExpandedSecretKey`, in bytes.
|
||||
pub const EXPANDED_SECRET_KEY_LENGTH: usize =
|
||||
EXPANDED_SECRET_KEY_KEY_LENGTH + EXPANDED_SECRET_KEY_NONCE_LENGTH;
|
110
ed25519-dalek/src/context.rs
Normal file
110
ed25519-dalek/src/context.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use crate::{InternalError, SignatureError};
|
||||
|
||||
/// Ed25519 contexts as used by Ed25519ph.
|
||||
///
|
||||
/// Contexts are domain separator strings that can be used to isolate uses of
|
||||
/// the algorithm between different protocols (which is very hard to reliably do
|
||||
/// otherwise) and between different uses within the same protocol.
|
||||
///
|
||||
/// To create a context, call either of the following:
|
||||
///
|
||||
/// - [`SigningKey::with_context`](crate::SigningKey::with_context)
|
||||
/// - [`VerifyingKey::with_context`](crate::VerifyingKey::with_context)
|
||||
///
|
||||
/// For more information, see [RFC8032 § 8.3](https://www.rfc-editor.org/rfc/rfc8032#section-8.3).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
#[cfg_attr(all(feature = "digest", feature = "rand_core"), doc = "```")]
|
||||
#[cfg_attr(
|
||||
any(not(feature = "digest"), not(feature = "rand_core")),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// # fn main() {
|
||||
/// use ed25519_dalek::{Signature, SigningKey, VerifyingKey, Sha512};
|
||||
/// # use curve25519_dalek::digest::Digest;
|
||||
/// # use rand::rngs::OsRng;
|
||||
/// use ed25519_dalek::{DigestSigner, DigestVerifier};
|
||||
///
|
||||
/// # let mut csprng = OsRng;
|
||||
/// # let signing_key = SigningKey::generate(&mut csprng);
|
||||
/// # let verifying_key = signing_key.verifying_key();
|
||||
/// let context_str = b"Local Channel 3";
|
||||
/// let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7");
|
||||
///
|
||||
/// // Signer
|
||||
/// let signing_context = signing_key.with_context(context_str).unwrap();
|
||||
/// let signature = signing_context.sign_digest(prehashed_message.clone());
|
||||
///
|
||||
/// // Verifier
|
||||
/// let verifying_context = verifying_key.with_context(context_str).unwrap();
|
||||
/// let verified: bool = verifying_context
|
||||
/// .verify_digest(prehashed_message, &signature)
|
||||
/// .is_ok();
|
||||
///
|
||||
/// # assert!(verified);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Context<'k, 'v, K> {
|
||||
/// Key this context is being used with.
|
||||
key: &'k K,
|
||||
|
||||
/// Context value: a bytestring no longer than 255 octets.
|
||||
value: &'v [u8],
|
||||
}
|
||||
|
||||
impl<'k, 'v, K> Context<'k, 'v, K> {
|
||||
/// Maximum length of the context value in octets.
|
||||
pub const MAX_LENGTH: usize = 255;
|
||||
|
||||
/// Create a new Ed25519ph context.
|
||||
pub(crate) fn new(key: &'k K, value: &'v [u8]) -> Result<Self, SignatureError> {
|
||||
if value.len() <= Self::MAX_LENGTH {
|
||||
Ok(Self { key, value })
|
||||
} else {
|
||||
Err(SignatureError::from(InternalError::PrehashedContextLength))
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the key.
|
||||
pub fn key(&self) -> &'k K {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Borrow the context string value.
|
||||
pub fn value(&self) -> &'v [u8] {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "digest"))]
|
||||
mod test {
|
||||
use crate::{Signature, SigningKey, VerifyingKey};
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use ed25519::signature::{DigestSigner, DigestVerifier};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
|
||||
#[test]
|
||||
fn context_correctness() {
|
||||
let mut csprng = OsRng;
|
||||
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
|
||||
let context_str = b"Local Channel 3";
|
||||
let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7");
|
||||
|
||||
// Signer
|
||||
let signing_context = signing_key.with_context(context_str).unwrap();
|
||||
let signature: Signature = signing_context.sign_digest(prehashed_message.clone());
|
||||
|
||||
// Verifier
|
||||
let verifying_context = verifying_key.with_context(context_str).unwrap();
|
||||
let verified: bool = verifying_context
|
||||
.verify_digest(prehashed_message, &signature)
|
||||
.is_ok();
|
||||
|
||||
assert!(verified);
|
||||
}
|
||||
}
|
119
ed25519-dalek/src/errors.rs
Normal file
119
ed25519-dalek/src/errors.rs
Normal file
@ -0,0 +1,119 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! Errors which may occur when parsing keys and/or signatures to or from wire formats.
|
||||
|
||||
// rustc seems to think the typenames in match statements (e.g. in
|
||||
// Display) should be snake cased, for some reason.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::fmt;
|
||||
use core::fmt::Display;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
/// Internal errors. Most application-level developers will likely not
|
||||
/// need to pay any attention to these.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum InternalError {
|
||||
PointDecompression,
|
||||
ScalarFormat,
|
||||
/// An error in the length of bytes handed to a constructor.
|
||||
///
|
||||
/// To use this, pass a string specifying the `name` of the type which is
|
||||
/// returning the error, and the `length` in bytes which its constructor
|
||||
/// expects.
|
||||
BytesLength {
|
||||
name: &'static str,
|
||||
length: usize,
|
||||
},
|
||||
/// The verification equation wasn't satisfied
|
||||
Verify,
|
||||
/// Two arrays did not match in size, making the called signature
|
||||
/// verification method impossible.
|
||||
#[cfg(feature = "batch")]
|
||||
ArrayLength {
|
||||
name_a: &'static str,
|
||||
length_a: usize,
|
||||
name_b: &'static str,
|
||||
length_b: usize,
|
||||
name_c: &'static str,
|
||||
length_c: usize,
|
||||
},
|
||||
/// An ed25519ph signature can only take up to 255 octets of context.
|
||||
#[cfg(feature = "digest")]
|
||||
PrehashedContextLength,
|
||||
/// A mismatched (public, secret) key pair.
|
||||
MismatchedKeypair,
|
||||
}
|
||||
|
||||
impl Display for InternalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
InternalError::PointDecompression => write!(f, "Cannot decompress Edwards point"),
|
||||
InternalError::ScalarFormat => write!(f, "Cannot use scalar with high-bit set"),
|
||||
InternalError::BytesLength { name: n, length: l } => {
|
||||
write!(f, "{} must be {} bytes in length", n, l)
|
||||
}
|
||||
InternalError::Verify => write!(f, "Verification equation was not satisfied"),
|
||||
#[cfg(feature = "batch")]
|
||||
InternalError::ArrayLength {
|
||||
name_a: na,
|
||||
length_a: la,
|
||||
name_b: nb,
|
||||
length_b: lb,
|
||||
name_c: nc,
|
||||
length_c: lc,
|
||||
} => write!(
|
||||
f,
|
||||
"Arrays must be the same length: {} has length {},
|
||||
{} has length {}, {} has length {}.",
|
||||
na, la, nb, lb, nc, lc
|
||||
),
|
||||
#[cfg(feature = "digest")]
|
||||
InternalError::PrehashedContextLength => write!(
|
||||
f,
|
||||
"An ed25519ph signature can only take up to 255 octets of context"
|
||||
),
|
||||
InternalError::MismatchedKeypair => write!(f, "Mismatched Keypair detected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for InternalError {}
|
||||
|
||||
/// Errors which may occur while processing signatures and keypairs.
|
||||
///
|
||||
/// This error may arise due to:
|
||||
///
|
||||
/// * Being given bytes with a length different to what was expected.
|
||||
///
|
||||
/// * A problem decompressing `r`, a curve point, in the `Signature`, or the
|
||||
/// curve point for a `PublicKey`.
|
||||
///
|
||||
/// * A problem with the format of `s`, a scalar, in the `Signature`. This
|
||||
/// is only raised if the high-bit of the scalar was set. (Scalars must
|
||||
/// only be constructed from 255-bit integers.)
|
||||
///
|
||||
/// * Failure of a signature to satisfy the verification equation.
|
||||
pub type SignatureError = ed25519::signature::Error;
|
||||
|
||||
impl From<InternalError> for SignatureError {
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn from(_err: InternalError) -> SignatureError {
|
||||
SignatureError::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn from(err: InternalError) -> SignatureError {
|
||||
SignatureError::from_source(err)
|
||||
}
|
||||
}
|
278
ed25519-dalek/src/hazmat.rs
Normal file
278
ed25519-dalek/src/hazmat.rs
Normal file
@ -0,0 +1,278 @@
|
||||
//! Low-level interfaces to ed25519 functions
|
||||
//!
|
||||
//! # ⚠️ Warning: Hazmat
|
||||
//!
|
||||
//! These primitives are easy-to-misuse low-level interfaces.
|
||||
//!
|
||||
//! If you are an end user / non-expert in cryptography, **do not use any of these functions**.
|
||||
//! Failure to use them correctly can lead to catastrophic failures including **full private key
|
||||
//! recovery.**
|
||||
|
||||
// Permit dead code because 1) this module is only public when the `hazmat` feature is set, and 2)
|
||||
// even without `hazmat` we still need this module because this is where `ExpandedSecretKey` is
|
||||
// defined.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::{InternalError, SignatureError};
|
||||
|
||||
use curve25519_dalek::scalar::{clamp_integer, Scalar};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
// These are used in the functions that are made public when the hazmat feature is set
|
||||
use crate::{Signature, VerifyingKey};
|
||||
use curve25519_dalek::digest::{generic_array::typenum::U64, Digest};
|
||||
|
||||
/// Contains the secret scalar and domain separator used for generating signatures.
|
||||
///
|
||||
/// This is used internally for signing.
|
||||
///
|
||||
/// In the usual Ed25519 signing algorithm, `scalar` and `hash_prefix` are defined such that
|
||||
/// `scalar || hash_prefix = H(sk)` where `sk` is the signing key and `H` is SHA-512.
|
||||
/// **WARNING:** Deriving the values for these fields in any other way can lead to full key
|
||||
/// recovery, as documented in [`raw_sign`] and [`raw_sign_prehashed`].
|
||||
///
|
||||
/// Instances of this secret are automatically overwritten with zeroes when they fall out of scope.
|
||||
pub struct ExpandedSecretKey {
|
||||
// `scalar_bytes` and `scalar` are separate, because the public key is computed as an unreduced
|
||||
// scalar multiplication (ie `mul_base_clamped`), whereas the signing operations are done
|
||||
// modulo l.
|
||||
pub(crate) scalar_bytes: [u8; 32],
|
||||
/// The secret scalar used for signing
|
||||
pub scalar: Scalar,
|
||||
/// The domain separator used when hashing the message to generate the pseudorandom `r` value
|
||||
pub hash_prefix: [u8; 32],
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl Drop for ExpandedSecretKey {
|
||||
fn drop(&mut self) {
|
||||
self.scalar.zeroize();
|
||||
self.hash_prefix.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl ZeroizeOnDrop for ExpandedSecretKey {}
|
||||
|
||||
// Some conversion methods for `ExpandedSecretKey`. The signing methods are defined in
|
||||
// `signing.rs`, since we need them even when `not(feature = "hazmat")`
|
||||
impl ExpandedSecretKey {
|
||||
/// Convert this `ExpandedSecretKey` into an array of 64 bytes.
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut bytes: [u8; 64] = [0u8; 64];
|
||||
|
||||
bytes[..32].copy_from_slice(self.scalar.as_bytes());
|
||||
bytes[32..].copy_from_slice(&self.hash_prefix[..]);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Construct an `ExpandedSecretKey` from an array of 64 bytes. In the spec, the bytes are the
|
||||
/// output of a SHA-512 hash. This clamps the first 32 bytes and uses it as a scalar, and uses
|
||||
/// the second 32 bytes as a domain separator for hashing.
|
||||
pub fn from_bytes(bytes: &[u8; 64]) -> Self {
|
||||
// TODO: Use bytes.split_array_ref once it’s in MSRV.
|
||||
let mut scalar_bytes: [u8; 32] = [0u8; 32];
|
||||
let mut hash_prefix: [u8; 32] = [0u8; 32];
|
||||
scalar_bytes.copy_from_slice(&bytes[00..32]);
|
||||
hash_prefix.copy_from_slice(&bytes[32..64]);
|
||||
|
||||
// For signing, we'll need the integer, clamped, and converted to a Scalar. See
|
||||
// PureEdDSA.keygen in RFC 8032 Appendix A.
|
||||
let scalar = Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes));
|
||||
|
||||
ExpandedSecretKey {
|
||||
scalar_bytes,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct an `ExpandedSecretKey` from a slice of 64 bytes.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose error value is an
|
||||
/// `SignatureError` describing the error that occurred, namely that the given slice's length
|
||||
/// is not 64.
|
||||
pub fn from_slice(bytes: &[u8]) -> Result<Self, SignatureError> {
|
||||
// Try to coerce bytes to a [u8; 64]
|
||||
bytes.try_into().map(Self::from_bytes).map_err(|_| {
|
||||
InternalError::BytesLength {
|
||||
name: "ExpandedSecretKey",
|
||||
length: 64,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for ExpandedSecretKey {
|
||||
type Error = SignatureError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::from_slice(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to
|
||||
/// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest =
|
||||
/// Sha512`.
|
||||
///
|
||||
/// # ⚠️ Unsafe
|
||||
///
|
||||
/// Do NOT use this function unless you absolutely must. Using the wrong values in
|
||||
/// `ExpandedSecretKey` can leak your signing key. See
|
||||
/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
|
||||
pub fn raw_sign<CtxDigest>(
|
||||
esk: &ExpandedSecretKey,
|
||||
message: &[u8],
|
||||
verifying_key: &VerifyingKey,
|
||||
) -> Signature
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
esk.raw_sign::<CtxDigest>(message, verifying_key)
|
||||
}
|
||||
|
||||
/// Compute a signature over the given prehashed message, the Ed25519ph algorithm defined in
|
||||
/// [RFC8032 §5.1][rfc8032]. `MsgDigest` is the digest function used to hash the signed message.
|
||||
/// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing.
|
||||
/// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`.
|
||||
///
|
||||
/// # ⚠️ Unsafe
|
||||
//
|
||||
/// Do NOT use this function unless you absolutely must. Using the wrong values in
|
||||
/// `ExpandedSecretKey` can leak your signing key. See
|
||||
/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `esk` is the [`ExpandedSecretKey`] being used for signing
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key.
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
///
|
||||
/// `scalar` and `hash_prefix` are usually selected such that `scalar || hash_prefix = H(sk)` where
|
||||
/// `sk` is the signing key
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the
|
||||
/// `prehashed_message` if the context was 255 bytes or less, otherwise
|
||||
/// a `SignatureError`.
|
||||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
|
||||
esk: &ExpandedSecretKey,
|
||||
prehashed_message: MsgDigest,
|
||||
verifying_key: &VerifyingKey,
|
||||
context: Option<&'a [u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
esk.raw_sign_prehashed::<CtxDigest, MsgDigest>(prehashed_message, verifying_key, context)
|
||||
}
|
||||
|
||||
/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R
|
||||
/// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing.
|
||||
/// According to the Ed25519 spec, `CtxDigest = Sha512`.
|
||||
pub fn raw_verify<CtxDigest>(
|
||||
vk: &VerifyingKey,
|
||||
message: &[u8],
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
vk.raw_verify::<CtxDigest>(message, signature)
|
||||
}
|
||||
|
||||
/// The batched Ed25519 verification check, rejecting non-canonical R values. `MsgDigest` is the
|
||||
/// digest used to hash the signed message. `CtxDigest` is the digest used to calculate the
|
||||
/// pseudorandomness needed for signing. According to the Ed25519 spec, `MsgDigest = CtxDigest =
|
||||
/// Sha512`.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn raw_verify_prehashed<CtxDigest, MsgDigest>(
|
||||
vk: &VerifyingKey,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
vk.raw_verify_prehashed::<CtxDigest, MsgDigest>(prehashed_message, context, signature)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
|
||||
// Pick distinct, non-spec 512-bit hash functions for message and sig-context hashing
|
||||
type CtxDigest = blake2::Blake2b512;
|
||||
type MsgDigest = sha3::Sha3_512;
|
||||
|
||||
impl ExpandedSecretKey {
|
||||
// Make a random expanded secret key for testing purposes. This is NOT how you generate
|
||||
// expanded secret keys IRL. They're the hash of a seed.
|
||||
fn random<R: RngCore + CryptoRng>(mut rng: R) -> Self {
|
||||
let mut bytes = [0u8; 64];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
ExpandedSecretKey::from_bytes(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that raw_sign and raw_verify work when a non-spec CtxDigest is used
|
||||
#[test]
|
||||
fn sign_verify_nonspec() {
|
||||
// Generate the keypair
|
||||
let mut rng = OsRng;
|
||||
let esk = ExpandedSecretKey::random(&mut rng);
|
||||
let vk = VerifyingKey::from(&esk);
|
||||
|
||||
let msg = b"Then one day, a piano fell on my head";
|
||||
|
||||
// Sign and verify
|
||||
let sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
|
||||
raw_verify::<CtxDigest>(&vk, msg, &sig).unwrap();
|
||||
}
|
||||
|
||||
// Check that raw_sign_prehashed and raw_verify_prehashed work when distinct, non-spec
|
||||
// MsgDigest and CtxDigest are used
|
||||
#[cfg(feature = "digest")]
|
||||
#[test]
|
||||
fn sign_verify_prehashed_nonspec() {
|
||||
use curve25519_dalek::digest::Digest;
|
||||
|
||||
// Generate the keypair
|
||||
let mut rng = OsRng;
|
||||
let esk = ExpandedSecretKey::random(&mut rng);
|
||||
let vk = VerifyingKey::from(&esk);
|
||||
|
||||
// Hash the message
|
||||
let msg = b"And then I got trampled by a herd of buffalo";
|
||||
let mut h = MsgDigest::new();
|
||||
h.update(msg);
|
||||
|
||||
let ctx_str = &b"consequences"[..];
|
||||
|
||||
// Sign and verify prehashed
|
||||
let sig = raw_sign_prehashed::<CtxDigest, MsgDigest>(&esk, h.clone(), &vk, Some(ctx_str))
|
||||
.unwrap();
|
||||
raw_verify_prehashed::<CtxDigest, MsgDigest>(&vk, h, Some(ctx_str), &sig).unwrap();
|
||||
}
|
||||
}
|
293
ed25519-dalek/src/lib.rs
Normal file
293
ed25519-dalek/src/lib.rs
Normal file
@ -0,0 +1,293 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! A Rust implementation of ed25519 key generation, signing, and verification.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! Creating an ed25519 signature on a message is simple.
|
||||
//!
|
||||
//! First, we need to generate a `SigningKey`, which includes both public and
|
||||
//! secret halves of an asymmetric key. To do so, we need a cryptographically
|
||||
//! secure pseudorandom number generator (CSPRNG). For this example, we'll use
|
||||
//! the operating system's builtin PRNG:
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! use rand::rngs::OsRng;
|
||||
//! use ed25519_dalek::SigningKey;
|
||||
//! use ed25519_dalek::Signature;
|
||||
//!
|
||||
//! let mut csprng = OsRng;
|
||||
//! let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! We can now use this `signing_key` to sign a message:
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::SigningKey;
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! use ed25519_dalek::{Signature, Signer};
|
||||
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! let signature: Signature = signing_key.sign(message);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! As well as to verify that this is, indeed, a valid signature on
|
||||
//! that `message`:
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer};
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
//! use ed25519_dalek::Verifier;
|
||||
//! assert!(signing_key.verify(message, &signature).is_ok());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Anyone else, given the `public` half of the `signing_key` can also easily
|
||||
//! verify this signature:
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::SigningKey;
|
||||
//! # use ed25519_dalek::Signature;
|
||||
//! # use ed25519_dalek::Signer;
|
||||
//! use ed25519_dalek::{VerifyingKey, Verifier};
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
//!
|
||||
//! let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
//! assert!(verifying_key.verify(message, &signature).is_ok());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Serialisation
|
||||
//!
|
||||
//! `VerifyingKey`s, `SecretKey`s, `SigningKey`s, and `Signature`s can be serialised
|
||||
//! into byte-arrays by calling `.to_bytes()`. It's perfectly acceptable and
|
||||
//! safe to transfer and/or store those bytes. (Of course, never transfer your
|
||||
//! secret key to anyone else, since they will only need the public key to
|
||||
//! verify your signatures!)
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey};
|
||||
//! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
//!
|
||||
//! let verifying_key_bytes: [u8; PUBLIC_KEY_LENGTH] = signing_key.verifying_key().to_bytes();
|
||||
//! let secret_key_bytes: [u8; SECRET_KEY_LENGTH] = signing_key.to_bytes();
|
||||
//! let signing_key_bytes: [u8; KEYPAIR_LENGTH] = signing_key.to_keypair_bytes();
|
||||
//! let signature_bytes: [u8; SIGNATURE_LENGTH] = signature.to_bytes();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! And similarly, decoded from bytes with `::from_bytes()`:
|
||||
//!
|
||||
#![cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
//! # use core::convert::{TryFrom, TryInto};
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey, SecretKey, SignatureError};
|
||||
//! # use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
||||
//! # fn do_test() -> Result<(SigningKey, VerifyingKey, Signature), SignatureError> {
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key_orig: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature_orig: Signature = signing_key_orig.sign(message);
|
||||
//! # let verifying_key_bytes: [u8; PUBLIC_KEY_LENGTH] = signing_key_orig.verifying_key().to_bytes();
|
||||
//! # let signing_key_bytes: [u8; SECRET_KEY_LENGTH] = signing_key_orig.to_bytes();
|
||||
//! # let signature_bytes: [u8; SIGNATURE_LENGTH] = signature_orig.to_bytes();
|
||||
//! #
|
||||
//! let verifying_key: VerifyingKey = VerifyingKey::from_bytes(&verifying_key_bytes)?;
|
||||
//! let signing_key: SigningKey = SigningKey::from_bytes(&signing_key_bytes);
|
||||
//! let signature: Signature = Signature::try_from(&signature_bytes[..])?;
|
||||
//! #
|
||||
//! # Ok((signing_key, verifying_key, signature))
|
||||
//! # }
|
||||
//! # fn main() {
|
||||
//! # do_test();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### PKCS#8 Key Encoding
|
||||
//!
|
||||
//! PKCS#8 is a private key format with support for multiple algorithms.
|
||||
//! It can be encoded as binary (DER) or text (PEM).
|
||||
//!
|
||||
//! You can recognize PEM-encoded PKCS#8 keys by the following:
|
||||
//!
|
||||
//! ```text
|
||||
//! -----BEGIN PRIVATE KEY-----
|
||||
//! ```
|
||||
//!
|
||||
//! To use PKCS#8, you need to enable the `pkcs8` crate feature.
|
||||
//!
|
||||
//! The following traits can be used to decode/encode [`SigningKey`] and
|
||||
//! [`VerifyingKey`] as PKCS#8. Note that [`pkcs8`] is re-exported from the
|
||||
//! toplevel of the crate:
|
||||
//!
|
||||
//! - [`pkcs8::DecodePrivateKey`]: decode private keys from PKCS#8
|
||||
//! - [`pkcs8::EncodePrivateKey`]: encode private keys to PKCS#8
|
||||
//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8
|
||||
//! - [`pkcs8::EncodePublicKey`]: encode public keys to PKCS#8
|
||||
//!
|
||||
//! #### Example
|
||||
//!
|
||||
//! NOTE: this requires the `pem` crate feature.
|
||||
//!
|
||||
#![cfg_attr(feature = "pem", doc = "```")]
|
||||
#![cfg_attr(not(feature = "pem"), doc = "```ignore")]
|
||||
//! use ed25519_dalek::{VerifyingKey, pkcs8::DecodePublicKey};
|
||||
//!
|
||||
//! let pem = "-----BEGIN PUBLIC KEY-----
|
||||
//! MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
|
||||
//! -----END PUBLIC KEY-----";
|
||||
//!
|
||||
//! let verifying_key = VerifyingKey::from_public_key_pem(pem)
|
||||
//! .expect("invalid public key PEM");
|
||||
//! ```
|
||||
//!
|
||||
//! ### Using Serde
|
||||
//!
|
||||
//! If you prefer the bytes to be wrapped in another serialisation format, all
|
||||
//! types additionally come with built-in [serde](https://serde.rs) support by
|
||||
//! building `ed25519-dalek` via:
|
||||
//!
|
||||
//! ```bash
|
||||
//! $ cargo build --features="serde"
|
||||
//! ```
|
||||
//!
|
||||
//! They can be then serialised into any of the wire formats which serde supports.
|
||||
//! For example, using [bincode](https://github.com/TyOverby/bincode):
|
||||
//!
|
||||
#![cfg_attr(all(feature = "rand_core", feature = "serde"), doc = "```")]
|
||||
#![cfg_attr(not(all(feature = "rand_core", feature = "serde")), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer, Verifier, VerifyingKey};
|
||||
//! use bincode::serialize;
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
//! # let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
//! # let verified: bool = verifying_key.verify(message, &signature).is_ok();
|
||||
//!
|
||||
//! let encoded_verifying_key: Vec<u8> = serialize(&verifying_key).unwrap();
|
||||
//! let encoded_signature: Vec<u8> = serialize(&signature).unwrap();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! After sending the `encoded_verifying_key` and `encoded_signature`, the
|
||||
//! recipient may deserialise them and verify:
|
||||
//!
|
||||
#![cfg_attr(all(feature = "rand_core", feature = "serde"), doc = "```")]
|
||||
#![cfg_attr(not(all(feature = "rand_core", feature = "serde")), doc = "```ignore")]
|
||||
//! # fn main() {
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer, Verifier, VerifyingKey};
|
||||
//! # use bincode::serialize;
|
||||
//! use bincode::deserialize;
|
||||
//!
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
//! # let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
//! # let verified: bool = verifying_key.verify(message, &signature).is_ok();
|
||||
//! # let encoded_verifying_key: Vec<u8> = serialize(&verifying_key).unwrap();
|
||||
//! # let encoded_signature: Vec<u8> = serialize(&signature).unwrap();
|
||||
//! let decoded_verifying_key: VerifyingKey = deserialize(&encoded_verifying_key).unwrap();
|
||||
//! let decoded_signature: Signature = deserialize(&encoded_signature).unwrap();
|
||||
//!
|
||||
//! # assert_eq!(verifying_key, decoded_verifying_key);
|
||||
//! # assert_eq!(signature, decoded_signature);
|
||||
//! #
|
||||
//! let verified: bool = decoded_verifying_key.verify(&message, &decoded_signature).is_ok();
|
||||
//!
|
||||
//! assert!(verified);
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![no_std]
|
||||
#![warn(future_incompatible, rust_2018_idioms)]
|
||||
#![deny(missing_docs)] // refuse to compile if documentation is missing
|
||||
#![deny(clippy::unwrap_used)] // don't allow unwrap
|
||||
#![cfg_attr(not(test), forbid(unsafe_code))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))]
|
||||
#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))]
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
pub use ed25519;
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
mod batch;
|
||||
mod constants;
|
||||
#[cfg(feature = "digest")]
|
||||
mod context;
|
||||
mod errors;
|
||||
mod signature;
|
||||
mod signing;
|
||||
mod verifying;
|
||||
|
||||
#[cfg(feature = "hazmat")]
|
||||
pub mod hazmat;
|
||||
#[cfg(not(feature = "hazmat"))]
|
||||
mod hazmat;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub use curve25519_dalek::digest::Digest;
|
||||
#[cfg(feature = "digest")]
|
||||
pub use sha2::Sha512;
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
pub use crate::batch::*;
|
||||
pub use crate::constants::*;
|
||||
#[cfg(feature = "digest")]
|
||||
pub use crate::context::Context;
|
||||
pub use crate::errors::*;
|
||||
pub use crate::signing::*;
|
||||
pub use crate::verifying::*;
|
||||
|
||||
// Re-export the `Signer` and `Verifier` traits from the `signature` crate
|
||||
#[cfg(feature = "digest")]
|
||||
pub use ed25519::signature::{DigestSigner, DigestVerifier};
|
||||
pub use ed25519::signature::{Signer, Verifier};
|
||||
pub use ed25519::Signature;
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
pub use ed25519::pkcs8;
|
178
ed25519-dalek/src/signature.rs
Normal file
178
ed25519-dalek/src/signature.rs
Normal file
@ -0,0 +1,178 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! An ed25519 signature.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::errors::*;
|
||||
|
||||
/// An ed25519 signature.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// These signatures, unlike the ed25519 signature reference implementation, are
|
||||
/// "detached"—that is, they do **not** include a copy of the message which has
|
||||
/// been signed.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Copy, Eq, PartialEq)]
|
||||
pub(crate) struct InternalSignature {
|
||||
/// `R` is an `EdwardsPoint`, formed by using an hash function with
|
||||
/// 512-bits output to produce the digest of:
|
||||
///
|
||||
/// - the nonce half of the `ExpandedSecretKey`, and
|
||||
/// - the message to be signed.
|
||||
///
|
||||
/// This digest is then interpreted as a `Scalar` and reduced into an
|
||||
/// element in ℤ/lℤ. The scalar is then multiplied by the distinguished
|
||||
/// basepoint to produce `R`, and `EdwardsPoint`.
|
||||
pub(crate) R: CompressedEdwardsY,
|
||||
|
||||
/// `s` is a `Scalar`, formed by using an hash function with 512-bits output
|
||||
/// to produce the digest of:
|
||||
///
|
||||
/// - the `r` portion of this `Signature`,
|
||||
/// - the `PublicKey` which should be used to verify this `Signature`, and
|
||||
/// - the message to be signed.
|
||||
///
|
||||
/// This digest is then interpreted as a `Scalar` and reduced into an
|
||||
/// element in ℤ/lℤ.
|
||||
pub(crate) s: Scalar,
|
||||
}
|
||||
|
||||
impl Clone for InternalSignature {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for InternalSignature {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
||||
write!(f, "Signature( R: {:?}, s: {:?} )", &self.R, &self.s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that the scalar `s` of a signature is within the bounds [0, 2^253).
|
||||
///
|
||||
/// **Unsafe**: This version of `check_scalar` permits signature malleability. See README.
|
||||
#[cfg(feature = "legacy_compatibility")]
|
||||
#[inline(always)]
|
||||
fn check_scalar(bytes: [u8; 32]) -> Result<Scalar, SignatureError> {
|
||||
// The highest 3 bits must not be set. No other checking for the
|
||||
// remaining 2^253 - 2^252 + 27742317777372353535851937790883648493
|
||||
// potential non-reduced scalars is performed.
|
||||
//
|
||||
// This is compatible with ed25519-donna and libsodium when
|
||||
// -DED25519_COMPAT is NOT specified.
|
||||
if bytes[31] & 224 != 0 {
|
||||
return Err(InternalError::ScalarFormat.into());
|
||||
}
|
||||
|
||||
// You cannot do arithmetic with scalars construct with Scalar::from_bits. We only use this
|
||||
// scalar for EdwardsPoint::vartime_double_scalar_mul_basepoint, which is an accepted usecase.
|
||||
// The `from_bits` method is deprecated because it's unsafe. We know this.
|
||||
#[allow(deprecated)]
|
||||
Ok(Scalar::from_bits(bytes))
|
||||
}
|
||||
|
||||
/// Ensures that the scalar `s` of a signature is within the bounds [0, ℓ)
|
||||
#[cfg(not(feature = "legacy_compatibility"))]
|
||||
#[inline(always)]
|
||||
fn check_scalar(bytes: [u8; 32]) -> Result<Scalar, SignatureError> {
|
||||
match Scalar::from_canonical_bytes(bytes).into() {
|
||||
None => Err(InternalError::ScalarFormat.into()),
|
||||
Some(x) => Ok(x),
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalSignature {
|
||||
/// Construct a `Signature` from a slice of bytes.
|
||||
///
|
||||
/// # Scalar Malleability Checking
|
||||
///
|
||||
/// As originally specified in the ed25519 paper (cf. the "Malleability"
|
||||
/// section of the README in this repo), no checks whatsoever were performed
|
||||
/// for signature malleability.
|
||||
///
|
||||
/// Later, a semi-functional, hacky check was added to most libraries to
|
||||
/// "ensure" that the scalar portion, `s`, of the signature was reduced `mod
|
||||
/// \ell`, the order of the basepoint:
|
||||
///
|
||||
/// ```ignore
|
||||
/// if signature.s[31] & 224 != 0 {
|
||||
/// return Err();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This bit-twiddling ensures that the most significant three bits of the
|
||||
/// scalar are not set:
|
||||
///
|
||||
/// ```python,ignore
|
||||
/// >>> 0b00010000 & 224
|
||||
/// 0
|
||||
/// >>> 0b00100000 & 224
|
||||
/// 32
|
||||
/// >>> 0b01000000 & 224
|
||||
/// 64
|
||||
/// >>> 0b10000000 & 224
|
||||
/// 128
|
||||
/// ```
|
||||
///
|
||||
/// However, this check is hacky and insufficient to check that the scalar is
|
||||
/// fully reduced `mod \ell = 2^252 + 27742317777372353535851937790883648493` as
|
||||
/// it leaves us with a guanteed bound of 253 bits. This means that there are
|
||||
/// `2^253 - 2^252 + 2774231777737235353585193779088364849311` remaining scalars
|
||||
/// which could cause malleabilllity.
|
||||
///
|
||||
/// RFC8032 [states](https://tools.ietf.org/html/rfc8032#section-5.1.7):
|
||||
///
|
||||
/// > To verify a signature on a message M using public key A, [...]
|
||||
/// > first split the signature into two 32-octet halves. Decode the first
|
||||
/// > half as a point R, and the second half as an integer S, in the range
|
||||
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||
/// > decodings fail (including S being out of range), the signature is
|
||||
/// > invalid.
|
||||
///
|
||||
/// However, by the time this was standardised, most libraries in use were
|
||||
/// only checking the most significant three bits. (See also the
|
||||
/// documentation for [`crate::VerifyingKey::verify_strict`].)
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn from_bytes(bytes: &[u8; SIGNATURE_LENGTH]) -> Result<InternalSignature, SignatureError> {
|
||||
// TODO: Use bytes.split_array_ref once it’s in MSRV.
|
||||
let mut R_bytes: [u8; 32] = [0u8; 32];
|
||||
let mut s_bytes: [u8; 32] = [0u8; 32];
|
||||
R_bytes.copy_from_slice(&bytes[00..32]);
|
||||
s_bytes.copy_from_slice(&bytes[32..64]);
|
||||
|
||||
Ok(InternalSignature {
|
||||
R: CompressedEdwardsY(R_bytes),
|
||||
s: check_scalar(s_bytes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ed25519::Signature> for InternalSignature {
|
||||
type Error = SignatureError;
|
||||
|
||||
fn try_from(sig: &ed25519::Signature) -> Result<InternalSignature, SignatureError> {
|
||||
InternalSignature::from_bytes(&sig.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InternalSignature> for ed25519::Signature {
|
||||
fn from(sig: InternalSignature) -> ed25519::Signature {
|
||||
ed25519::Signature::from_components(*sig.R.as_bytes(), *sig.s.as_bytes())
|
||||
}
|
||||
}
|
845
ed25519-dalek/src/signing.rs
Normal file
845
ed25519-dalek/src/signing.rs
Normal file
@ -0,0 +1,845 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! ed25519 signing keys.
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
use ed25519::pkcs8;
|
||||
|
||||
#[cfg(any(test, feature = "rand_core"))]
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use sha2::Sha512;
|
||||
|
||||
use curve25519_dalek::{
|
||||
digest::{generic_array::typenum::U64, Digest},
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
|
||||
use ed25519::signature::{KeypairRef, Signer, Verifier};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use crate::context::Context;
|
||||
#[cfg(feature = "digest")]
|
||||
use signature::DigestSigner;
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::{
|
||||
constants::{KEYPAIR_LENGTH, SECRET_KEY_LENGTH},
|
||||
errors::{InternalError, SignatureError},
|
||||
hazmat::ExpandedSecretKey,
|
||||
signature::InternalSignature,
|
||||
verifying::VerifyingKey,
|
||||
Signature,
|
||||
};
|
||||
|
||||
/// ed25519 secret key as defined in [RFC8032 § 5.1.5]:
|
||||
///
|
||||
/// > The private key is 32 octets (256 bits, corresponding to b) of
|
||||
/// > cryptographically secure random data.
|
||||
///
|
||||
/// [RFC8032 § 5.1.5]: https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5
|
||||
pub type SecretKey = [u8; SECRET_KEY_LENGTH];
|
||||
|
||||
/// ed25519 signing key which can be used to produce signatures.
|
||||
// Invariant: `public` is always the public key of `secret`. This prevents the signing function
|
||||
// oracle attack described in https://github.com/MystenLabs/ed25519-unsafe-libs
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SigningKey {
|
||||
/// The secret half of this signing key.
|
||||
pub(crate) secret_key: SecretKey,
|
||||
/// The public half of this signing key.
|
||||
pub(crate) verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate ed25519_dalek;
|
||||
/// #
|
||||
/// use ed25519_dalek::SigningKey;
|
||||
/// use ed25519_dalek::SECRET_KEY_LENGTH;
|
||||
/// use ed25519_dalek::SignatureError;
|
||||
///
|
||||
/// # fn doctest() -> Result<SigningKey, SignatureError> {
|
||||
/// let secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [
|
||||
/// 157, 097, 177, 157, 239, 253, 090, 096,
|
||||
/// 186, 132, 074, 244, 146, 236, 044, 196,
|
||||
/// 068, 073, 197, 105, 123, 050, 105, 025,
|
||||
/// 112, 059, 172, 003, 028, 174, 127, 096, ];
|
||||
///
|
||||
/// let signing_key: SigningKey = SigningKey::from_bytes(&secret_key_bytes);
|
||||
/// assert_eq!(signing_key.to_bytes(), secret_key_bytes);
|
||||
///
|
||||
/// # Ok(signing_key)
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// # let result = doctest();
|
||||
/// # assert!(result.is_ok());
|
||||
/// # }
|
||||
/// ```
|
||||
impl SigningKey {
|
||||
/// Construct a [`SigningKey`] from a [`SecretKey`]
|
||||
///
|
||||
#[inline]
|
||||
pub fn from_bytes(secret_key: &SecretKey) -> Self {
|
||||
let verifying_key = VerifyingKey::from(&ExpandedSecretKey::from(secret_key));
|
||||
Self {
|
||||
secret_key: *secret_key,
|
||||
verifying_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this [`SigningKey`] into a [`SecretKey`]
|
||||
#[inline]
|
||||
pub fn to_bytes(&self) -> SecretKey {
|
||||
self.secret_key
|
||||
}
|
||||
|
||||
/// Construct a [`SigningKey`] from the bytes of a `VerifyingKey` and `SecretKey`.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `bytes`: an `&[u8]` of length [`KEYPAIR_LENGTH`], representing the
|
||||
/// scalar for the secret key, and a compressed Edwards-Y coordinate of a
|
||||
/// point on curve25519, both as bytes. (As obtained from
|
||||
/// [`SigningKey::to_bytes`].)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose okay value is an EdDSA [`SigningKey`] or whose error value
|
||||
/// is an `SignatureError` describing the error that occurred.
|
||||
#[inline]
|
||||
pub fn from_keypair_bytes(bytes: &[u8; 64]) -> Result<SigningKey, SignatureError> {
|
||||
let (secret_key, verifying_key) = bytes.split_at(SECRET_KEY_LENGTH);
|
||||
let signing_key = SigningKey::try_from(secret_key)?;
|
||||
let verifying_key = VerifyingKey::try_from(verifying_key)?;
|
||||
|
||||
if signing_key.verifying_key() != verifying_key {
|
||||
return Err(InternalError::MismatchedKeypair.into());
|
||||
}
|
||||
|
||||
Ok(signing_key)
|
||||
}
|
||||
|
||||
/// Convert this signing key to a 64-byte keypair.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An array of bytes, `[u8; KEYPAIR_LENGTH]`. The first
|
||||
/// `SECRET_KEY_LENGTH` of bytes is the `SecretKey`, and the next
|
||||
/// `PUBLIC_KEY_LENGTH` bytes is the `VerifyingKey` (the same as other
|
||||
/// libraries, such as [Adam Langley's ed25519 Golang
|
||||
/// implementation](https://github.com/agl/ed25519/)). It is guaranteed that
|
||||
/// the encoded public key is the one derived from the encoded secret key.
|
||||
pub fn to_keypair_bytes(&self) -> [u8; KEYPAIR_LENGTH] {
|
||||
let mut bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH];
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH].copy_from_slice(&self.secret_key);
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(self.verifying_key.as_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Get the [`VerifyingKey`] for this [`SigningKey`].
|
||||
pub fn verifying_key(&self) -> VerifyingKey {
|
||||
self.verifying_key
|
||||
}
|
||||
|
||||
/// Create a signing context that can be used for Ed25519ph with
|
||||
/// [`DigestSigner`].
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn with_context<'k, 'v>(
|
||||
&'k self,
|
||||
context_value: &'v [u8],
|
||||
) -> Result<Context<'k, 'v, Self>, SignatureError> {
|
||||
Context::new(self, context_value)
|
||||
}
|
||||
|
||||
/// Generate an ed25519 signing key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
#[cfg_attr(feature = "rand_core", doc = "```")]
|
||||
#[cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
|
||||
/// # fn main() {
|
||||
/// use rand::rngs::OsRng;
|
||||
/// use ed25519_dalek::{Signature, SigningKey};
|
||||
///
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Input
|
||||
///
|
||||
/// A CSPRNG with a `fill_bytes()` method, e.g. `rand_os::OsRng`.
|
||||
///
|
||||
/// The caller must also supply a hash function which implements the
|
||||
/// `Digest` and `Default` traits, and which returns 512 bits of output.
|
||||
/// The standard hash function used for most ed25519 libraries is SHA-512,
|
||||
/// which is available with `use sha2::Sha512` as in the example above.
|
||||
/// Other suitable hash functions include Keccak-512 and Blake2b-512.
|
||||
#[cfg(any(test, feature = "rand_core"))]
|
||||
pub fn generate<R: CryptoRngCore + ?Sized>(csprng: &mut R) -> SigningKey {
|
||||
let mut secret = SecretKey::default();
|
||||
csprng.fill_bytes(&mut secret);
|
||||
Self::from_bytes(&secret)
|
||||
}
|
||||
|
||||
/// Sign a `prehashed_message` with this [`SigningKey`] using the
|
||||
/// Ed25519ph algorithm defined in [RFC8032 §5.1][rfc8032].
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An Ed25519ph [`Signature`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. This
|
||||
/// function technically works, and is probably safe to use, with any secure hash function with
|
||||
/// 512-bit digests, but anything outside of SHA-512 is NOT specification-compliant. We expose
|
||||
/// [`crate::Sha512`] for user convenience.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")]
|
||||
#[cfg_attr(
|
||||
any(not(feature = "rand_core"), not(feature = "digest")),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// use ed25519_dalek::Digest;
|
||||
/// use ed25519_dalek::SigningKey;
|
||||
/// use ed25519_dalek::Signature;
|
||||
/// use sha2::Sha512;
|
||||
/// use rand::rngs::OsRng;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
///
|
||||
/// // Create a hash digest object which we'll feed the message into:
|
||||
/// let mut prehashed: Sha512 = Sha512::new();
|
||||
///
|
||||
/// prehashed.update(message);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If you want, you can optionally pass a "context". It is generally a
|
||||
/// good idea to choose a context and try to make it unique to your project
|
||||
/// and this specific usage of signatures.
|
||||
///
|
||||
/// For example, without this, if you were to [convert your OpenPGP key
|
||||
/// to a Bitcoin key][terrible_idea] (just as an example, and also Don't
|
||||
/// Ever Do That) and someone tricked you into signing an "email" which was
|
||||
/// actually a Bitcoin transaction moving all your magic internet money to
|
||||
/// their address, it'd be a valid transaction.
|
||||
///
|
||||
/// By adding a context, this trick becomes impossible, because the context
|
||||
/// is concatenated into the hash, which is then signed. So, going with the
|
||||
/// previous example, if your bitcoin wallet used a context of
|
||||
/// "BitcoinWalletAppTxnSigning" and OpenPGP used a context (this is likely
|
||||
/// the least of their safety problems) of "GPGsCryptoIsntConstantTimeLol",
|
||||
/// then the signatures produced by both could never match the other, even
|
||||
/// if they signed the exact same message with the same key.
|
||||
///
|
||||
/// Let's add a context for good measure (remember, you'll want to choose
|
||||
/// your own!):
|
||||
///
|
||||
#[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")]
|
||||
#[cfg_attr(
|
||||
any(not(feature = "rand_core"), not(feature = "digest")),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// # use ed25519_dalek::Digest;
|
||||
/// # use ed25519_dalek::SigningKey;
|
||||
/// # use ed25519_dalek::Signature;
|
||||
/// # use ed25519_dalek::SignatureError;
|
||||
/// # use sha2::Sha512;
|
||||
/// # use rand::rngs::OsRng;
|
||||
/// #
|
||||
/// # fn do_test() -> Result<Signature, SignatureError> {
|
||||
/// # let mut csprng = OsRng;
|
||||
/// # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// # let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
/// # let mut prehashed: Sha512 = Sha512::new();
|
||||
/// # prehashed.update(message);
|
||||
/// #
|
||||
/// let context: &[u8] = b"Ed25519DalekSignPrehashedDoctest";
|
||||
///
|
||||
/// let sig: Signature = signing_key.sign_prehashed(prehashed, Some(context))?;
|
||||
/// #
|
||||
/// # Ok(sig)
|
||||
/// # }
|
||||
/// # fn main() {
|
||||
/// # do_test();
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
/// [terrible_idea]: https://github.com/isislovecruft/scripts/blob/master/gpgkey2bc.py
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn sign_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
ExpandedSecretKey::from(&self.secret_key).raw_sign_prehashed::<Sha512, MsgDigest>(
|
||||
prehashed_message,
|
||||
&self.verifying_key,
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// Verify a signature on a message with this signing key's public key.
|
||||
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
|
||||
self.verifying_key.verify(message, signature)
|
||||
}
|
||||
|
||||
/// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
/// * `signature` is a purported Ed25519ph [`Signature`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. This
|
||||
/// function technically works, and is probably safe to use, with any secure hash function with
|
||||
/// 512-bit digests, but anything outside of SHA-512 is NOT specification-compliant. We expose
|
||||
/// [`crate::Sha512`] for user convenience.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")]
|
||||
#[cfg_attr(
|
||||
any(not(feature = "rand_core"), not(feature = "digest")),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// use ed25519_dalek::Digest;
|
||||
/// use ed25519_dalek::SigningKey;
|
||||
/// use ed25519_dalek::Signature;
|
||||
/// use ed25519_dalek::SignatureError;
|
||||
/// use sha2::Sha512;
|
||||
/// use rand::rngs::OsRng;
|
||||
///
|
||||
/// # fn do_test() -> Result<(), SignatureError> {
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
///
|
||||
/// let mut prehashed: Sha512 = Sha512::new();
|
||||
/// prehashed.update(message);
|
||||
///
|
||||
/// let context: &[u8] = b"Ed25519DalekSignPrehashedDoctest";
|
||||
///
|
||||
/// let sig: Signature = signing_key.sign_prehashed(prehashed, Some(context))?;
|
||||
///
|
||||
/// // The sha2::Sha512 struct doesn't implement Copy, so we'll have to create a new one:
|
||||
/// let mut prehashed_again: Sha512 = Sha512::default();
|
||||
/// prehashed_again.update(message);
|
||||
///
|
||||
/// let verified = signing_key.verifying_key().verify_prehashed(prehashed_again, Some(context), &sig);
|
||||
///
|
||||
/// assert!(verified.is_ok());
|
||||
///
|
||||
/// # verified
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// # do_test();
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn verify_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
self.verifying_key
|
||||
.verify_prehashed(prehashed_message, context, signature)
|
||||
}
|
||||
|
||||
/// Strictly verify a signature on a message with this signing key's public key.
|
||||
///
|
||||
/// # On The (Multiple) Sources of Malleability in Ed25519 Signatures
|
||||
///
|
||||
/// This version of verification is technically non-RFC8032 compliant. The
|
||||
/// following explains why.
|
||||
///
|
||||
/// 1. Scalar Malleability
|
||||
///
|
||||
/// The authors of the RFC explicitly stated that verification of an ed25519
|
||||
/// signature must fail if the scalar `s` is not properly reduced mod \ell:
|
||||
///
|
||||
/// > To verify a signature on a message M using public key A, with F
|
||||
/// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or
|
||||
/// > Ed25519ph is being used, C being the context, first split the
|
||||
/// > signature into two 32-octet halves. Decode the first half as a
|
||||
/// > point R, and the second half as an integer S, in the range
|
||||
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||
/// > decodings fail (including S being out of range), the signature is
|
||||
/// > invalid.)
|
||||
///
|
||||
/// All `verify_*()` functions within ed25519-dalek perform this check.
|
||||
///
|
||||
/// 2. Point malleability
|
||||
///
|
||||
/// The authors of the RFC added in a malleability check to step #3 in
|
||||
/// §5.1.7, for small torsion components in the `R` value of the signature,
|
||||
/// *which is not strictly required*, as they state:
|
||||
///
|
||||
/// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's
|
||||
/// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'.
|
||||
///
|
||||
/// # History of Malleability Checks
|
||||
///
|
||||
/// As originally defined (cf. the "Malleability" section in the README of
|
||||
/// this repo), ed25519 signatures didn't consider *any* form of
|
||||
/// malleability to be an issue. Later the scalar malleability was
|
||||
/// considered important. Still later, particularly with interests in
|
||||
/// cryptocurrency design and in unique identities (e.g. for Signal users,
|
||||
/// Tor onion services, etc.), the group element malleability became a
|
||||
/// concern.
|
||||
///
|
||||
/// However, libraries had already been created to conform to the original
|
||||
/// definition. One well-used library in particular even implemented the
|
||||
/// group element malleability check, *but only for batch verification*!
|
||||
/// Which meant that even using the same library, a single signature could
|
||||
/// verify fine individually, but suddenly, when verifying it with a bunch
|
||||
/// of other signatures, the whole batch would fail!
|
||||
///
|
||||
/// # "Strict" Verification
|
||||
///
|
||||
/// This method performs *both* of the above signature malleability checks.
|
||||
///
|
||||
/// It must be done as a separate method because one doesn't simply get to
|
||||
/// change the definition of a cryptographic primitive ten years
|
||||
/// after-the-fact with zero consideration for backwards compatibility in
|
||||
/// hardware and protocols which have it already have the older definition
|
||||
/// baked in.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_strict(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.verifying_key.verify_strict(message, signature)
|
||||
}
|
||||
|
||||
/// Convert this signing key into a byte representation of a(n) (unreduced) Curve25519 scalar.
|
||||
///
|
||||
/// This can be used for performing X25519 Diffie-Hellman using Ed25519 keys. The bytes output
|
||||
/// by this function are a valid secret key for the X25519 public key given by
|
||||
/// `self.verifying_key().to_montgomery()`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We do NOT recommend this usage of a signing/verifying key. Signing keys are usually
|
||||
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
|
||||
/// help it, use a separate key for encryption.
|
||||
///
|
||||
/// For more information on the security of systems which use the same keys for both signing
|
||||
/// and Diffie-Hellman, see the paper
|
||||
/// [On using the same key pair for Ed25519 and an X25519 based KEM](https://eprint.iacr.org/2021/509).
|
||||
pub fn to_scalar_bytes(&self) -> [u8; 32] {
|
||||
ExpandedSecretKey::from(&self.secret_key).scalar_bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<VerifyingKey> for SigningKey {
|
||||
fn as_ref(&self) -> &VerifyingKey {
|
||||
&self.verifying_key
|
||||
}
|
||||
}
|
||||
|
||||
impl KeypairRef for SigningKey {
|
||||
type VerifyingKey = VerifyingKey;
|
||||
}
|
||||
|
||||
impl Signer<Signature> for SigningKey {
|
||||
/// Sign a message with this signing key's secret key.
|
||||
fn try_sign(&self, message: &[u8]) -> Result<Signature, SignatureError> {
|
||||
let expanded: ExpandedSecretKey = (&self.secret_key).into();
|
||||
Ok(expanded.raw_sign::<Sha512>(message, &self.verifying_key))
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`None`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is
|
||||
/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside
|
||||
/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestSigner<D, Signature> for SigningKey
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn try_sign_digest(&self, msg_digest: D) -> Result<Signature, SignatureError> {
|
||||
self.sign_prehashed(msg_digest, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`Some`]
|
||||
/// containing `self.value()`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is
|
||||
/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside
|
||||
/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestSigner<D, Signature> for Context<'_, '_, SigningKey>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn try_sign_digest(&self, msg_digest: D) -> Result<Signature, SignatureError> {
|
||||
self.key().sign_prehashed(msg_digest, Some(self.value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier<Signature> for SigningKey {
|
||||
/// Verify a signature on a message with this signing key's public key.
|
||||
fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
|
||||
self.verifying_key.verify(message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SecretKey> for SigningKey {
|
||||
#[inline]
|
||||
fn from(secret: SecretKey) -> Self {
|
||||
Self::from_bytes(&secret)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SecretKey> for SigningKey {
|
||||
#[inline]
|
||||
fn from(secret: &SecretKey) -> Self {
|
||||
Self::from_bytes(secret)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SigningKey {
|
||||
type Error = SignatureError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SigningKey, SignatureError> {
|
||||
SecretKey::try_from(bytes)
|
||||
.map(|bytes| Self::from_bytes(&bytes))
|
||||
.map_err(|_| {
|
||||
InternalError::BytesLength {
|
||||
name: "SecretKey",
|
||||
length: SECRET_KEY_LENGTH,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl Drop for SigningKey {
|
||||
fn drop(&mut self) {
|
||||
self.secret_key.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl ZeroizeOnDrop for SigningKey {}
|
||||
|
||||
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
|
||||
impl pkcs8::EncodePrivateKey for SigningKey {
|
||||
fn to_pkcs8_der(&self) -> pkcs8::Result<pkcs8::SecretDocument> {
|
||||
pkcs8::KeypairBytes::from(self).to_pkcs8_der()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<pkcs8::KeypairBytes> for SigningKey {
|
||||
type Error = pkcs8::Error;
|
||||
|
||||
fn try_from(pkcs8_key: pkcs8::KeypairBytes) -> pkcs8::Result<Self> {
|
||||
SigningKey::try_from(&pkcs8_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<&pkcs8::KeypairBytes> for SigningKey {
|
||||
type Error = pkcs8::Error;
|
||||
|
||||
fn try_from(pkcs8_key: &pkcs8::KeypairBytes) -> pkcs8::Result<Self> {
|
||||
let signing_key = SigningKey::from_bytes(&pkcs8_key.secret_key);
|
||||
|
||||
// Validate the public key in the PKCS#8 document if present
|
||||
if let Some(public_bytes) = &pkcs8_key.public_key {
|
||||
let expected_verifying_key = VerifyingKey::from_bytes(public_bytes.as_ref())
|
||||
.map_err(|_| pkcs8::Error::KeyMalformed)?;
|
||||
|
||||
if signing_key.verifying_key() != expected_verifying_key {
|
||||
return Err(pkcs8::Error::KeyMalformed);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(signing_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl From<SigningKey> for pkcs8::KeypairBytes {
|
||||
fn from(signing_key: SigningKey) -> pkcs8::KeypairBytes {
|
||||
pkcs8::KeypairBytes::from(&signing_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl From<&SigningKey> for pkcs8::KeypairBytes {
|
||||
fn from(signing_key: &SigningKey) -> pkcs8::KeypairBytes {
|
||||
pkcs8::KeypairBytes {
|
||||
secret_key: signing_key.to_bytes(),
|
||||
public_key: Some(pkcs8::PublicKeyBytes(signing_key.verifying_key.to_bytes())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for SigningKey {
|
||||
type Error = pkcs8::Error;
|
||||
|
||||
fn try_from(private_key: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result<Self> {
|
||||
pkcs8::KeypairBytes::try_from(private_key)?.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for SigningKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(&self.secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'d> Deserialize<'d> for SigningKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
{
|
||||
struct SigningKeyVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for SigningKeyVisitor {
|
||||
type Value = SigningKey;
|
||||
|
||||
fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
||||
write!(formatter, concat!("An ed25519 signing (private) key"))
|
||||
}
|
||||
|
||||
fn visit_borrowed_bytes<E: serde::de::Error>(
|
||||
self,
|
||||
bytes: &'de [u8],
|
||||
) -> Result<Self::Value, E> {
|
||||
SigningKey::try_from(bytes.as_ref()).map_err(E::custom)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut bytes = [0u8; 32];
|
||||
for i in 0..32 {
|
||||
bytes[i] = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
|
||||
}
|
||||
|
||||
let remaining = (0..)
|
||||
.map(|_| seq.next_element::<u8>())
|
||||
.take_while(|el| matches!(el, Ok(Some(_))))
|
||||
.count();
|
||||
|
||||
if remaining > 0 {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
32 + remaining,
|
||||
&"expected 32 bytes",
|
||||
));
|
||||
}
|
||||
|
||||
SigningKey::try_from(bytes).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(SigningKeyVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// The spec-compliant way to define an expanded secret key. This computes `SHA512(sk)`, clamps the
|
||||
/// first 32 bytes and uses it as a scalar, and uses the second 32 bytes as a domain separator for
|
||||
/// hashing.
|
||||
impl From<&SecretKey> for ExpandedSecretKey {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn from(secret_key: &SecretKey) -> ExpandedSecretKey {
|
||||
let hash = Sha512::default().chain_update(secret_key).finalize();
|
||||
ExpandedSecretKey::from_bytes(hash.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Signing functions. These are pub(crate) so that the `hazmat` module can use them
|
||||
//
|
||||
|
||||
impl ExpandedSecretKey {
|
||||
/// The plain, non-prehashed, signing function for Ed25519. `CtxDigest` is the digest used to
|
||||
/// calculate the pseudorandomness needed for signing. According to the spec, `CtxDigest =
|
||||
/// Sha512`, and `self` is derived via the method defined in `impl From<&SigningKey> for
|
||||
/// ExpandedSecretKey`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn raw_sign<CtxDigest>(
|
||||
&self,
|
||||
message: &[u8],
|
||||
verifying_key: &VerifyingKey,
|
||||
) -> Signature
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut h = CtxDigest::new();
|
||||
|
||||
h.update(self.hash_prefix);
|
||||
h.update(message);
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
let R: CompressedEdwardsY = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = CtxDigest::new();
|
||||
h.update(R.as_bytes());
|
||||
h.update(verifying_key.as_bytes());
|
||||
h.update(message);
|
||||
|
||||
let k = Scalar::from_hash(h);
|
||||
let s: Scalar = (k * self.scalar) + r;
|
||||
|
||||
InternalSignature { R, s }.into()
|
||||
}
|
||||
|
||||
/// The prehashed signing function for Ed25519 (i.e., Ed25519ph). `CtxDigest` is the digest
|
||||
/// function used to calculate the pseudorandomness needed for signing. `MsgDigest` is the
|
||||
/// digest function used to hash the signed message. According to the spec, `MsgDigest =
|
||||
/// CtxDigest = Sha512`, and `self` is derived via the method defined in `impl
|
||||
/// From<&SigningKey> for ExpandedSecretKey`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which `CtxDigest` function to use.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
verifying_key: &VerifyingKey,
|
||||
context: Option<&'a [u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut prehash: [u8; 64] = [0u8; 64];
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b""); // By default, the context is an empty string.
|
||||
|
||||
if ctx.len() > 255 {
|
||||
return Err(SignatureError::from(InternalError::PrehashedContextLength));
|
||||
}
|
||||
|
||||
let ctx_len: u8 = ctx.len() as u8;
|
||||
|
||||
// Get the result of the pre-hashed message.
|
||||
prehash.copy_from_slice(prehashed_message.finalize().as_slice());
|
||||
|
||||
// This is the dumbest, ten-years-late, non-admission of fucking up the
|
||||
// domain separation I have ever seen. Why am I still required to put
|
||||
// the upper half "prefix" of the hashed "secret key" in here? Why
|
||||
// can't the user just supply their own nonce and decide for themselves
|
||||
// whether or not they want a deterministic signature scheme? Why does
|
||||
// the message go into what's ostensibly the signature domain separation
|
||||
// hash? Why wasn't there always a way to provide a context string?
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// This is a really fucking stupid bandaid, and the damned scheme is
|
||||
// still bleeding from malleability, for fuck's sake.
|
||||
let mut h = CtxDigest::new()
|
||||
.chain_update(b"SigEd25519 no Ed25519 collisions")
|
||||
.chain_update([1]) // Ed25519ph
|
||||
.chain_update([ctx_len])
|
||||
.chain_update(ctx)
|
||||
.chain_update(self.hash_prefix)
|
||||
.chain_update(&prehash[..]);
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
let R: CompressedEdwardsY = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = CtxDigest::new()
|
||||
.chain_update(b"SigEd25519 no Ed25519 collisions")
|
||||
.chain_update([1]) // Ed25519ph
|
||||
.chain_update([ctx_len])
|
||||
.chain_update(ctx)
|
||||
.chain_update(R.as_bytes())
|
||||
.chain_update(verifying_key.as_bytes())
|
||||
.chain_update(&prehash[..]);
|
||||
|
||||
let k = Scalar::from_hash(h);
|
||||
let s: Scalar = (k * self.scalar) + r;
|
||||
|
||||
Ok(InternalSignature { R, s }.into())
|
||||
}
|
||||
}
|
686
ed25519-dalek/src/verifying.rs
Normal file
686
ed25519-dalek/src/verifying.rs
Normal file
@ -0,0 +1,686 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! ed25519 public keys.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt::Debug;
|
||||
use core::hash::{Hash, Hasher};
|
||||
|
||||
use curve25519_dalek::{
|
||||
digest::{generic_array::typenum::U64, Digest},
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
montgomery::MontgomeryPoint,
|
||||
scalar::Scalar,
|
||||
};
|
||||
|
||||
use ed25519::signature::Verifier;
|
||||
|
||||
use sha2::Sha512;
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
use ed25519::pkcs8;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use crate::context::Context;
|
||||
#[cfg(feature = "digest")]
|
||||
use signature::DigestVerifier;
|
||||
|
||||
use crate::{
|
||||
constants::PUBLIC_KEY_LENGTH,
|
||||
errors::{InternalError, SignatureError},
|
||||
hazmat::ExpandedSecretKey,
|
||||
signature::InternalSignature,
|
||||
signing::SigningKey,
|
||||
};
|
||||
|
||||
/// An ed25519 public key.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The `Eq` and `Hash` impls here use the compressed Edwards y encoding, _not_ the algebraic
|
||||
/// representation. This means if this `VerifyingKey` is non-canonically encoded, it will be
|
||||
/// considered unequal to the other equivalent encoding, despite the two representing the same
|
||||
/// point. More encoding details can be found
|
||||
/// [here](https://hdevalence.ca/blog/2020-10-04-its-25519am).
|
||||
/// If you want to make sure that signatures produced with respect to those sorts of public keys
|
||||
/// are rejected, use [`VerifyingKey::verify_strict`].
|
||||
// Invariant: VerifyingKey.1 is always the decompression of VerifyingKey.0
|
||||
#[derive(Copy, Clone, Default, Eq)]
|
||||
pub struct VerifyingKey {
|
||||
/// Serialized compressed Edwards-y point.
|
||||
pub(crate) compressed: CompressedEdwardsY,
|
||||
|
||||
/// Decompressed Edwards point used for curve arithmetic operations.
|
||||
pub(crate) point: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl Debug for VerifyingKey {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "VerifyingKey({:?}), {:?})", self.compressed, self.point)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for VerifyingKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for VerifyingKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_bytes().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<VerifyingKey> for VerifyingKey {
|
||||
fn eq(&self, other: &VerifyingKey) -> bool {
|
||||
self.as_bytes() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ExpandedSecretKey> for VerifyingKey {
|
||||
/// Derive this public key from its corresponding `ExpandedSecretKey`.
|
||||
fn from(expanded_secret_key: &ExpandedSecretKey) -> VerifyingKey {
|
||||
VerifyingKey::clamp_and_mul_base(expanded_secret_key.scalar_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SigningKey> for VerifyingKey {
|
||||
fn from(signing_key: &SigningKey) -> VerifyingKey {
|
||||
signing_key.verifying_key()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EdwardsPoint> for VerifyingKey {
|
||||
fn from(point: EdwardsPoint) -> VerifyingKey {
|
||||
VerifyingKey {
|
||||
point,
|
||||
compressed: point.compress(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifyingKey {
|
||||
/// Convert this public key to a byte array.
|
||||
#[inline]
|
||||
pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] {
|
||||
self.compressed.to_bytes()
|
||||
}
|
||||
|
||||
/// View this public key as a byte array.
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] {
|
||||
&(self.compressed).0
|
||||
}
|
||||
|
||||
/// Construct a `VerifyingKey` from a slice of bytes.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The caller is responsible for ensuring that the bytes passed into this
|
||||
/// method actually represent a `curve25519_dalek::curve::CompressedEdwardsY`
|
||||
/// and that said compressed point is actually a point on the curve.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ed25519_dalek::VerifyingKey;
|
||||
/// use ed25519_dalek::PUBLIC_KEY_LENGTH;
|
||||
/// use ed25519_dalek::SignatureError;
|
||||
///
|
||||
/// # fn doctest() -> Result<VerifyingKey, SignatureError> {
|
||||
/// let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [
|
||||
/// 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58,
|
||||
/// 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26];
|
||||
///
|
||||
/// let public_key = VerifyingKey::from_bytes(&public_key_bytes)?;
|
||||
/// #
|
||||
/// # Ok(public_key)
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// # doctest();
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose okay value is an EdDSA `VerifyingKey` or whose error value
|
||||
/// is a `SignatureError` describing the error that occurred.
|
||||
#[inline]
|
||||
pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_LENGTH]) -> Result<VerifyingKey, SignatureError> {
|
||||
let compressed = CompressedEdwardsY(*bytes);
|
||||
let point = compressed
|
||||
.decompress()
|
||||
.ok_or(InternalError::PointDecompression)?;
|
||||
|
||||
// Invariant: VerifyingKey.1 is always the decompression of VerifyingKey.0
|
||||
Ok(VerifyingKey { compressed, point })
|
||||
}
|
||||
|
||||
/// Create a verifying context that can be used for Ed25519ph with
|
||||
/// [`DigestVerifier`].
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn with_context<'k, 'v>(
|
||||
&'k self,
|
||||
context_value: &'v [u8],
|
||||
) -> Result<Context<'k, 'v, Self>, SignatureError> {
|
||||
Context::new(self, context_value)
|
||||
}
|
||||
|
||||
/// Returns whether this is a _weak_ public key, i.e., if this public key has low order.
|
||||
///
|
||||
/// A weak public key can be used to generate a signature that's valid for almost every
|
||||
/// message. [`Self::verify_strict`] denies weak keys, but if you want to check for this
|
||||
/// property before verification, then use this method.
|
||||
pub fn is_weak(&self) -> bool {
|
||||
self.point.is_small_order()
|
||||
}
|
||||
|
||||
/// Internal utility function for clamping a scalar representation and multiplying by the
|
||||
/// basepont to produce a public key.
|
||||
fn clamp_and_mul_base(bits: [u8; 32]) -> VerifyingKey {
|
||||
let point = EdwardsPoint::mul_base_clamped(bits);
|
||||
let compressed = point.compress();
|
||||
|
||||
// Invariant: VerifyingKey.1 is always the decompression of VerifyingKey.0
|
||||
VerifyingKey { compressed, point }
|
||||
}
|
||||
|
||||
// A helper function that computes `H(R || A || M)` where `H` is the 512-bit hash function
|
||||
// given by `CtxDigest` (this is SHA-512 in spec-compliant Ed25519). If `context.is_some()`,
|
||||
// this does the prehashed variant of the computation using its contents.
|
||||
#[allow(non_snake_case)]
|
||||
fn compute_challenge<CtxDigest>(
|
||||
context: Option<&[u8]>,
|
||||
R: &CompressedEdwardsY,
|
||||
A: &CompressedEdwardsY,
|
||||
M: &[u8],
|
||||
) -> Scalar
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut h = CtxDigest::new();
|
||||
if let Some(c) = context {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([c.len() as u8]);
|
||||
h.update(c);
|
||||
}
|
||||
h.update(R.as_bytes());
|
||||
h.update(A.as_bytes());
|
||||
h.update(M);
|
||||
|
||||
Scalar::from_hash(h)
|
||||
}
|
||||
|
||||
// Helper function for verification. Computes the _expected_ R component of the signature. The
|
||||
// caller compares this to the real R component. If `context.is_some()`, this does the
|
||||
// prehashed variant of the computation using its contents.
|
||||
// Note that this returns the compressed form of R and the caller does a byte comparison. This
|
||||
// means that all our verification functions do not accept non-canonically encoded R values.
|
||||
// See the validation criteria blog post for more details:
|
||||
// https://hdevalence.ca/blog/2020-10-04-its-25519am
|
||||
#[allow(non_snake_case)]
|
||||
fn recompute_R<CtxDigest>(
|
||||
&self,
|
||||
context: Option<&[u8]>,
|
||||
signature: &InternalSignature,
|
||||
M: &[u8],
|
||||
) -> CompressedEdwardsY
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let k = Self::compute_challenge::<CtxDigest>(context, &signature.R, &self.compressed, M);
|
||||
let minus_A: EdwardsPoint = -self.point;
|
||||
// Recall the (non-batched) verification equation: -[k]A + [s]B = R
|
||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress()
|
||||
}
|
||||
|
||||
/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R values. (see
|
||||
/// [`Self::recompute_R`]). `CtxDigest` is the digest used to calculate the pseudorandomness
|
||||
/// needed for signing. According to the spec, `CtxDigest = Sha512`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn raw_verify<CtxDigest>(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let expected_R = self.recompute_R::<CtxDigest>(None, &signature, message);
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// The prehashed non-batched Ed25519 verification check, rejecting non-canonical R values.
|
||||
/// (see [`Self::recompute_R`]). `CtxDigest` is the digest used to calculate the
|
||||
/// pseudorandomness needed for signing. `MsgDigest` is the digest used to hash the signed
|
||||
/// message. According to the spec, `MsgDigest = CtxDigest = Sha512`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn raw_verify_prehashed<CtxDigest, MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b"");
|
||||
debug_assert!(
|
||||
ctx.len() <= 255,
|
||||
"The context must not be longer than 255 octets."
|
||||
);
|
||||
|
||||
let message = prehashed_message.finalize();
|
||||
let expected_R = self.recompute_R::<CtxDigest>(Some(ctx), &signature, &message);
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
/// * `signature` is a purported Ed25519ph signature on the `prehashed_message`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. This
|
||||
/// function technically works, and is probably safe to use, with any secure hash function with
|
||||
/// 512-bit digests, but anything outside of SHA-512 is NOT specification-compliant. We expose
|
||||
/// [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
self.raw_verify_prehashed::<Sha512, MsgDigest>(prehashed_message, context, signature)
|
||||
}
|
||||
|
||||
/// Strictly verify a signature on a message with this keypair's public key.
|
||||
///
|
||||
/// # On The (Multiple) Sources of Malleability in Ed25519 Signatures
|
||||
///
|
||||
/// This version of verification is technically non-RFC8032 compliant. The
|
||||
/// following explains why.
|
||||
///
|
||||
/// 1. Scalar Malleability
|
||||
///
|
||||
/// The authors of the RFC explicitly stated that verification of an ed25519
|
||||
/// signature must fail if the scalar `s` is not properly reduced mod $\ell$:
|
||||
///
|
||||
/// > To verify a signature on a message M using public key A, with F
|
||||
/// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or
|
||||
/// > Ed25519ph is being used, C being the context, first split the
|
||||
/// > signature into two 32-octet halves. Decode the first half as a
|
||||
/// > point R, and the second half as an integer S, in the range
|
||||
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||
/// > decodings fail (including S being out of range), the signature is
|
||||
/// > invalid.)
|
||||
///
|
||||
/// All `verify_*()` functions within ed25519-dalek perform this check.
|
||||
///
|
||||
/// 2. Point malleability
|
||||
///
|
||||
/// The authors of the RFC added in a malleability check to step #3 in
|
||||
/// §5.1.7, for small torsion components in the `R` value of the signature,
|
||||
/// *which is not strictly required*, as they state:
|
||||
///
|
||||
/// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's
|
||||
/// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'.
|
||||
///
|
||||
/// # History of Malleability Checks
|
||||
///
|
||||
/// As originally defined (cf. the "Malleability" section in the README of
|
||||
/// this repo), ed25519 signatures didn't consider *any* form of
|
||||
/// malleability to be an issue. Later the scalar malleability was
|
||||
/// considered important. Still later, particularly with interests in
|
||||
/// cryptocurrency design and in unique identities (e.g. for Signal users,
|
||||
/// Tor onion services, etc.), the group element malleability became a
|
||||
/// concern.
|
||||
///
|
||||
/// However, libraries had already been created to conform to the original
|
||||
/// definition. One well-used library in particular even implemented the
|
||||
/// group element malleability check, *but only for batch verification*!
|
||||
/// Which meant that even using the same library, a single signature could
|
||||
/// verify fine individually, but suddenly, when verifying it with a bunch
|
||||
/// of other signatures, the whole batch would fail!
|
||||
///
|
||||
/// # "Strict" Verification
|
||||
///
|
||||
/// This method performs *both* of the above signature malleability checks.
|
||||
///
|
||||
/// It must be done as a separate method because one doesn't simply get to
|
||||
/// change the definition of a cryptographic primitive ten years
|
||||
/// after-the-fact with zero consideration for backwards compatibility in
|
||||
/// hardware and protocols which have it already have the older definition
|
||||
/// baked in.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_strict(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let signature_R = signature
|
||||
.R
|
||||
.decompress()
|
||||
.ok_or_else(|| SignatureError::from(InternalError::Verify))?;
|
||||
|
||||
// Logical OR is fine here as we're not trying to be constant time.
|
||||
if signature_R.is_small_order() || self.point.is_small_order() {
|
||||
return Err(InternalError::Verify.into());
|
||||
}
|
||||
|
||||
let expected_R = self.recompute_R::<Sha512>(None, &signature, message);
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm,
|
||||
/// using strict signture checking as defined by [`Self::verify_strict`].
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
/// * `signature` is a purported Ed25519ph signature on the `prehashed_message`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. This
|
||||
/// function technically works, and is probably safe to use, with any secure hash function with
|
||||
/// 512-bit digests, but anything outside of SHA-512 is NOT specification-compliant. We expose
|
||||
/// [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_prehashed_strict<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b"");
|
||||
debug_assert!(
|
||||
ctx.len() <= 255,
|
||||
"The context must not be longer than 255 octets."
|
||||
);
|
||||
|
||||
let signature_R = signature
|
||||
.R
|
||||
.decompress()
|
||||
.ok_or_else(|| SignatureError::from(InternalError::Verify))?;
|
||||
|
||||
// Logical OR is fine here as we're not trying to be constant time.
|
||||
if signature_R.is_small_order() || self.point.is_small_order() {
|
||||
return Err(InternalError::Verify.into());
|
||||
}
|
||||
|
||||
let message = prehashed_message.finalize();
|
||||
let expected_R = self.recompute_R::<Sha512>(Some(ctx), &signature, &message);
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this verifying key into Montgomery form.
|
||||
///
|
||||
/// This can be used for performing X25519 Diffie-Hellman using Ed25519 keys. The output of
|
||||
/// this function is a valid X25519 public key whose secret key is `sk.to_scalar_bytes()`,
|
||||
/// where `sk` is a valid signing key for this `VerifyingKey`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We do NOT recommend this usage of a signing/verifying key. Signing keys are usually
|
||||
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
|
||||
/// help it, use a separate key for encryption.
|
||||
///
|
||||
/// For more information on the security of systems which use the same keys for both signing
|
||||
/// and Diffie-Hellman, see the paper
|
||||
/// [On using the same key pair for Ed25519 and an X25519 based KEM](https://eprint.iacr.org/2021/509).
|
||||
pub fn to_montgomery(&self) -> MontgomeryPoint {
|
||||
self.point.to_montgomery()
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier<ed25519::Signature> for VerifyingKey {
|
||||
/// Verify a signature on a message with this keypair's public key.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||
fn verify(&self, message: &[u8], signature: &ed25519::Signature) -> Result<(), SignatureError> {
|
||||
self.raw_verify::<Sha512>(message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`VerifyingKey::verify_prehashed`] with `context` set to [`None`].
|
||||
#[cfg(feature = "digest")]
|
||||
impl<MsgDigest> DigestVerifier<MsgDigest, ed25519::Signature> for VerifyingKey
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn verify_digest(
|
||||
&self,
|
||||
msg_digest: MsgDigest,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.verify_prehashed(msg_digest, None, signature)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`VerifyingKey::verify_prehashed`] with `context` set to [`Some`]
|
||||
/// containing `self.value()`.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<MsgDigest> DigestVerifier<MsgDigest, ed25519::Signature> for Context<'_, '_, VerifyingKey>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn verify_digest(
|
||||
&self,
|
||||
msg_digest: MsgDigest,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.key()
|
||||
.verify_prehashed(msg_digest, Some(self.value()), signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerifyingKey {
|
||||
type Error = SignatureError;
|
||||
|
||||
#[inline]
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let bytes = bytes.try_into().map_err(|_| InternalError::BytesLength {
|
||||
name: "VerifyingKey",
|
||||
length: PUBLIC_KEY_LENGTH,
|
||||
})?;
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
|
||||
impl pkcs8::EncodePublicKey for VerifyingKey {
|
||||
fn to_public_key_der(&self) -> pkcs8::spki::Result<pkcs8::Document> {
|
||||
pkcs8::PublicKeyBytes::from(self).to_public_key_der()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<pkcs8::PublicKeyBytes> for VerifyingKey {
|
||||
type Error = pkcs8::spki::Error;
|
||||
|
||||
fn try_from(pkcs8_key: pkcs8::PublicKeyBytes) -> pkcs8::spki::Result<Self> {
|
||||
VerifyingKey::try_from(&pkcs8_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<&pkcs8::PublicKeyBytes> for VerifyingKey {
|
||||
type Error = pkcs8::spki::Error;
|
||||
|
||||
fn try_from(pkcs8_key: &pkcs8::PublicKeyBytes) -> pkcs8::spki::Result<Self> {
|
||||
VerifyingKey::from_bytes(pkcs8_key.as_ref()).map_err(|_| pkcs8::spki::Error::KeyMalformed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl From<VerifyingKey> for pkcs8::PublicKeyBytes {
|
||||
fn from(verifying_key: VerifyingKey) -> pkcs8::PublicKeyBytes {
|
||||
pkcs8::PublicKeyBytes::from(&verifying_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl From<&VerifyingKey> for pkcs8::PublicKeyBytes {
|
||||
fn from(verifying_key: &VerifyingKey) -> pkcs8::PublicKeyBytes {
|
||||
pkcs8::PublicKeyBytes(verifying_key.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pkcs8")]
|
||||
impl TryFrom<pkcs8::spki::SubjectPublicKeyInfoRef<'_>> for VerifyingKey {
|
||||
type Error = pkcs8::spki::Error;
|
||||
|
||||
fn try_from(public_key: pkcs8::spki::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
|
||||
pkcs8::PublicKeyBytes::try_from(public_key)?.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for VerifyingKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(&self.as_bytes()[..])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'d> Deserialize<'d> for VerifyingKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
{
|
||||
struct VerifyingKeyVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for VerifyingKeyVisitor {
|
||||
type Value = VerifyingKey;
|
||||
|
||||
fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
||||
write!(formatter, concat!("An ed25519 verifying (public) key"))
|
||||
}
|
||||
|
||||
fn visit_borrowed_bytes<E: serde::de::Error>(
|
||||
self,
|
||||
bytes: &'de [u8],
|
||||
) -> Result<Self::Value, E> {
|
||||
VerifyingKey::try_from(bytes.as_ref()).map_err(E::custom)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
for i in 0..32 {
|
||||
bytes[i] = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
|
||||
}
|
||||
|
||||
let remaining = (0..)
|
||||
.map(|_| seq.next_element::<u8>())
|
||||
.take_while(|el| matches!(el, Ok(Some(_))))
|
||||
.count();
|
||||
|
||||
if remaining > 0 {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
32 + remaining,
|
||||
&"expected 32 bytes",
|
||||
));
|
||||
}
|
||||
|
||||
VerifyingKey::try_from(&bytes[..]).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(VerifyingKeyVisitor)
|
||||
}
|
||||
}
|
660
ed25519-dalek/tests/ed25519.rs
Normal file
660
ed25519-dalek/tests/ed25519.rs
Normal file
@ -0,0 +1,660 @@
|
||||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of ed25519-dalek.
|
||||
// Copyright (c) 2017-2019 isis lovecruft
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||
|
||||
//! Integration tests for ed25519-dalek.
|
||||
|
||||
use curve25519_dalek;
|
||||
|
||||
use ed25519_dalek::*;
|
||||
|
||||
use hex::FromHex;
|
||||
#[cfg(feature = "digest")]
|
||||
use hex_literal::hex;
|
||||
|
||||
#[cfg(test)]
|
||||
mod vectors {
|
||||
use super::*;
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_POINT,
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
scalar::Scalar,
|
||||
traits::IsIdentity,
|
||||
};
|
||||
use sha2::{digest::Digest, Sha512};
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
ops::Neg,
|
||||
};
|
||||
|
||||
// TESTVECTORS is taken from sign.input.gz in agl's ed25519 Golang
|
||||
// package. It is a selection of test cases from
|
||||
// http://ed25519.cr.yp.to/python/sign.input
|
||||
#[test]
|
||||
fn against_reference_implementation() {
|
||||
// TestGolden
|
||||
let mut line: String;
|
||||
let mut lineno: usize = 0;
|
||||
|
||||
let f = File::open("TESTVECTORS");
|
||||
if f.is_err() {
|
||||
println!(
|
||||
"This test is only available when the code has been cloned \
|
||||
from the git repository, since the TESTVECTORS file is large \
|
||||
and is therefore not included within the distributed crate."
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
let file = BufReader::new(f.unwrap());
|
||||
|
||||
for l in file.lines() {
|
||||
lineno += 1;
|
||||
line = l.unwrap();
|
||||
|
||||
let parts: Vec<&str> = line.split(':').collect();
|
||||
assert_eq!(parts.len(), 5, "wrong number of fields in line {}", lineno);
|
||||
|
||||
let sec_bytes: Vec<u8> = FromHex::from_hex(&parts[0]).unwrap();
|
||||
let pub_bytes: Vec<u8> = FromHex::from_hex(&parts[1]).unwrap();
|
||||
let msg_bytes: Vec<u8> = FromHex::from_hex(&parts[2]).unwrap();
|
||||
let sig_bytes: Vec<u8> = FromHex::from_hex(&parts[3]).unwrap();
|
||||
|
||||
let sec_bytes = &sec_bytes[..SECRET_KEY_LENGTH].try_into().unwrap();
|
||||
let pub_bytes = &pub_bytes[..PUBLIC_KEY_LENGTH].try_into().unwrap();
|
||||
|
||||
let signing_key = SigningKey::from_bytes(sec_bytes);
|
||||
let expected_verifying_key = VerifyingKey::from_bytes(pub_bytes).unwrap();
|
||||
assert_eq!(expected_verifying_key, signing_key.verifying_key());
|
||||
|
||||
// The signatures in the test vectors also include the message
|
||||
// at the end, but we just want R and S.
|
||||
let sig1: Signature = Signature::try_from(&sig_bytes[..64]).unwrap();
|
||||
let sig2: Signature = signing_key.sign(&msg_bytes);
|
||||
|
||||
assert!(sig1 == sig2, "Signature bytes not equal on line {}", lineno);
|
||||
assert!(
|
||||
signing_key.verify(&msg_bytes, &sig2).is_ok(),
|
||||
"Signature verification failed on line {}",
|
||||
lineno
|
||||
);
|
||||
assert!(
|
||||
expected_verifying_key
|
||||
.verify_strict(&msg_bytes, &sig2)
|
||||
.is_ok(),
|
||||
"Signature strict verification failed on line {}",
|
||||
lineno
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// From https://tools.ietf.org/html/rfc8032#section-7.3
|
||||
#[cfg(feature = "digest")]
|
||||
#[test]
|
||||
fn ed25519ph_rf8032_test_vector_prehash() {
|
||||
let sec_bytes = hex!("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
|
||||
let pub_bytes = hex!("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf");
|
||||
let msg_bytes = hex!("616263");
|
||||
let sig_bytes = hex!("98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406");
|
||||
|
||||
let signing_key = SigningKey::from_bytes(&sec_bytes);
|
||||
let expected_verifying_key = VerifyingKey::from_bytes(&pub_bytes).unwrap();
|
||||
assert_eq!(expected_verifying_key, signing_key.verifying_key());
|
||||
let sig1 = Signature::try_from(&sig_bytes[..]).unwrap();
|
||||
|
||||
let mut prehash_for_signing = Sha512::default();
|
||||
let mut prehash_for_verifying = Sha512::default();
|
||||
|
||||
prehash_for_signing.update(&msg_bytes[..]);
|
||||
prehash_for_verifying.update(&msg_bytes[..]);
|
||||
|
||||
let sig2: Signature = signing_key
|
||||
.sign_prehashed(prehash_for_signing, None)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
sig1 == sig2,
|
||||
"Original signature from test vectors doesn't equal signature produced:\
|
||||
\noriginal:\n{:?}\nproduced:\n{:?}",
|
||||
sig1,
|
||||
sig2
|
||||
);
|
||||
assert!(
|
||||
signing_key
|
||||
.verify_prehashed(prehash_for_verifying.clone(), None, &sig2)
|
||||
.is_ok(),
|
||||
"Could not verify ed25519ph signature!"
|
||||
);
|
||||
assert!(
|
||||
expected_verifying_key
|
||||
.verify_prehashed_strict(prehash_for_verifying, None, &sig2)
|
||||
.is_ok(),
|
||||
"Could not strict-verify ed25519ph signature!"
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// The remaining items in this mod are for the repudiation tests
|
||||
//
|
||||
|
||||
// Taken from curve25519_dalek::constants::EIGHT_TORSION[4]
|
||||
const EIGHT_TORSION_4: [u8; 32] = [
|
||||
236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127,
|
||||
];
|
||||
|
||||
// Computes the prehashed or non-prehashed challenge, depending on whether context is given
|
||||
fn compute_challenge(
|
||||
message: &[u8],
|
||||
pub_key: &EdwardsPoint,
|
||||
signature_r: &EdwardsPoint,
|
||||
context: Option<&[u8]>,
|
||||
) -> Scalar {
|
||||
let mut h = Sha512::default();
|
||||
if let Some(c) = context {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update(&[1]);
|
||||
h.update(&[c.len() as u8]);
|
||||
h.update(c);
|
||||
}
|
||||
h.update(&signature_r.compress().as_bytes());
|
||||
h.update(&pub_key.compress().as_bytes()[..]);
|
||||
h.update(&message);
|
||||
Scalar::from_hash(h)
|
||||
}
|
||||
|
||||
fn serialize_signature(r: &EdwardsPoint, s: &Scalar) -> Vec<u8> {
|
||||
[&r.compress().as_bytes()[..], &s.as_bytes()[..]].concat()
|
||||
}
|
||||
|
||||
const WEAK_PUBKEY: CompressedEdwardsY = CompressedEdwardsY(EIGHT_TORSION_4);
|
||||
|
||||
// Pick a random Scalar
|
||||
fn non_null_scalar() -> Scalar {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let mut s_candidate = Scalar::random(&mut rng);
|
||||
while s_candidate == Scalar::ZERO {
|
||||
s_candidate = Scalar::random(&mut rng);
|
||||
}
|
||||
s_candidate
|
||||
}
|
||||
|
||||
fn pick_r(s: Scalar) -> EdwardsPoint {
|
||||
let r0 = s * ED25519_BASEPOINT_POINT;
|
||||
// Pick a torsion point of order 2
|
||||
r0 + WEAK_PUBKEY.decompress().unwrap().neg()
|
||||
}
|
||||
|
||||
// Tests that verify_strict() rejects small-order pubkeys. We test this by explicitly
|
||||
// constructing a pubkey-signature pair that verifies with respect to two distinct messages.
|
||||
// This should be accepted by verify(), but rejected by verify_strict().
|
||||
#[test]
|
||||
fn repudiation() {
|
||||
let message1 = b"Send 100 USD to Alice";
|
||||
let message2 = b"Send 100000 USD to Alice";
|
||||
|
||||
let mut s: Scalar = non_null_scalar();
|
||||
let pubkey = WEAK_PUBKEY.decompress().unwrap();
|
||||
let mut r = pick_r(s);
|
||||
|
||||
// Find an R such that
|
||||
// H(R || A || M₁) · A == A == H(R || A || M₂) · A
|
||||
// This happens with high probability when A is low order.
|
||||
while !(pubkey.neg() + compute_challenge(message1, &pubkey, &r, None) * pubkey)
|
||||
.is_identity()
|
||||
|| !(pubkey.neg() + compute_challenge(message2, &pubkey, &r, None) * pubkey)
|
||||
.is_identity()
|
||||
{
|
||||
// We pick an s and let R = sB - A where B is the basepoint
|
||||
s = non_null_scalar();
|
||||
r = pick_r(s);
|
||||
}
|
||||
|
||||
// At this point, both verification equations hold:
|
||||
// sB = R + H(R || A || M₁) · A
|
||||
// = R + H(R || A || M₂) · A
|
||||
// Check that this is true
|
||||
let signature = serialize_signature(&r, &s);
|
||||
let vk = VerifyingKey::from_bytes(&pubkey.compress().as_bytes()).unwrap();
|
||||
let sig = Signature::try_from(&signature[..]).unwrap();
|
||||
assert!(vk.verify(message1, &sig).is_ok());
|
||||
assert!(vk.verify(message2, &sig).is_ok());
|
||||
|
||||
// Check that this public key appears as weak
|
||||
assert!(vk.is_weak());
|
||||
|
||||
// Now check that the sigs fail under verify_strict. This is because verify_strict rejects
|
||||
// small order pubkeys.
|
||||
assert!(vk.verify_strict(message1, &sig).is_err());
|
||||
assert!(vk.verify_strict(message2, &sig).is_err());
|
||||
}
|
||||
|
||||
// Identical to repudiation() above, but testing verify_prehashed against
|
||||
// verify_prehashed_strict. See comments above for a description of what's happening.
|
||||
#[cfg(feature = "digest")]
|
||||
#[test]
|
||||
fn repudiation_prehash() {
|
||||
let message1 = Sha512::new().chain_update(b"Send 100 USD to Alice");
|
||||
let message2 = Sha512::new().chain_update(b"Send 100000 USD to Alice");
|
||||
let message1_bytes = message1.clone().finalize();
|
||||
let message2_bytes = message2.clone().finalize();
|
||||
|
||||
let mut s: Scalar = non_null_scalar();
|
||||
let pubkey = WEAK_PUBKEY.decompress().unwrap();
|
||||
let mut r = pick_r(s);
|
||||
let context_str = Some(&b"edtest"[..]);
|
||||
|
||||
while !(pubkey.neg()
|
||||
+ compute_challenge(&message1_bytes, &pubkey, &r, context_str) * pubkey)
|
||||
.is_identity()
|
||||
|| !(pubkey.neg()
|
||||
+ compute_challenge(&message2_bytes, &pubkey, &r, context_str) * pubkey)
|
||||
.is_identity()
|
||||
{
|
||||
s = non_null_scalar();
|
||||
r = pick_r(s);
|
||||
}
|
||||
|
||||
// Check that verify_prehashed succeeds on both sigs
|
||||
let signature = serialize_signature(&r, &s);
|
||||
let vk = VerifyingKey::from_bytes(&pubkey.compress().as_bytes()).unwrap();
|
||||
let sig = Signature::try_from(&signature[..]).unwrap();
|
||||
assert!(vk
|
||||
.verify_prehashed(message1.clone(), context_str, &sig)
|
||||
.is_ok());
|
||||
assert!(vk
|
||||
.verify_prehashed(message2.clone(), context_str, &sig)
|
||||
.is_ok());
|
||||
|
||||
// Check that verify_prehashed_strict fails on both sigs
|
||||
assert!(vk
|
||||
.verify_prehashed_strict(message1.clone(), context_str, &sig)
|
||||
.is_err());
|
||||
assert!(vk
|
||||
.verify_prehashed_strict(message2.clone(), context_str, &sig)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand_core")]
|
||||
mod integrations {
|
||||
use super::*;
|
||||
use rand::rngs::OsRng;
|
||||
#[cfg(feature = "digest")]
|
||||
use sha2::Sha512;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn sign_verify() {
|
||||
// TestSignVerify
|
||||
let signing_key: SigningKey;
|
||||
let good_sig: Signature;
|
||||
let bad_sig: Signature;
|
||||
|
||||
let good: &[u8] = "test message".as_bytes();
|
||||
let bad: &[u8] = "wrong message".as_bytes();
|
||||
|
||||
let mut csprng = OsRng;
|
||||
|
||||
signing_key = SigningKey::generate(&mut csprng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
good_sig = signing_key.sign(&good);
|
||||
bad_sig = signing_key.sign(&bad);
|
||||
|
||||
// Check that an honestly generated public key is not weak
|
||||
assert!(!verifying_key.is_weak());
|
||||
|
||||
assert!(
|
||||
signing_key.verify(&good, &good_sig).is_ok(),
|
||||
"Verification of a valid signature failed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key.verify_strict(&good, &good_sig).is_ok(),
|
||||
"Strict verification of a valid signature failed!"
|
||||
);
|
||||
assert!(
|
||||
signing_key.verify(&good, &bad_sig).is_err(),
|
||||
"Verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key.verify_strict(&good, &bad_sig).is_err(),
|
||||
"Strict verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
signing_key.verify(&bad, &good_sig).is_err(),
|
||||
"Verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key.verify_strict(&bad, &good_sig).is_err(),
|
||||
"Strict verification of a signature on a different message passed!"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
#[test]
|
||||
fn ed25519ph_sign_verify() {
|
||||
let signing_key: SigningKey;
|
||||
let good_sig: Signature;
|
||||
let bad_sig: Signature;
|
||||
|
||||
let good: &[u8] = b"test message";
|
||||
let bad: &[u8] = b"wrong message";
|
||||
|
||||
let mut csprng = OsRng;
|
||||
|
||||
// ugh… there's no `impl Copy for Sha512`… i hope we can all agree these are the same hashes
|
||||
let mut prehashed_good1: Sha512 = Sha512::default();
|
||||
prehashed_good1.update(good);
|
||||
let mut prehashed_good2: Sha512 = Sha512::default();
|
||||
prehashed_good2.update(good);
|
||||
let mut prehashed_good3: Sha512 = Sha512::default();
|
||||
prehashed_good3.update(good);
|
||||
|
||||
let mut prehashed_bad1: Sha512 = Sha512::default();
|
||||
prehashed_bad1.update(bad);
|
||||
let mut prehashed_bad2: Sha512 = Sha512::default();
|
||||
prehashed_bad2.update(bad);
|
||||
|
||||
let context: &[u8] = b"testing testing 1 2 3";
|
||||
|
||||
signing_key = SigningKey::generate(&mut csprng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
good_sig = signing_key
|
||||
.sign_prehashed(prehashed_good1, Some(context))
|
||||
.unwrap();
|
||||
bad_sig = signing_key
|
||||
.sign_prehashed(prehashed_bad1, Some(context))
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
signing_key
|
||||
.verify_prehashed(prehashed_good2.clone(), Some(context), &good_sig)
|
||||
.is_ok(),
|
||||
"Verification of a valid signature failed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key
|
||||
.verify_prehashed_strict(prehashed_good2, Some(context), &good_sig)
|
||||
.is_ok(),
|
||||
"Strict verification of a valid signature failed!"
|
||||
);
|
||||
assert!(
|
||||
signing_key
|
||||
.verify_prehashed(prehashed_good3.clone(), Some(context), &bad_sig)
|
||||
.is_err(),
|
||||
"Verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key
|
||||
.verify_prehashed_strict(prehashed_good3, Some(context), &bad_sig)
|
||||
.is_err(),
|
||||
"Strict verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
signing_key
|
||||
.verify_prehashed(prehashed_bad2.clone(), Some(context), &good_sig)
|
||||
.is_err(),
|
||||
"Verification of a signature on a different message passed!"
|
||||
);
|
||||
assert!(
|
||||
verifying_key
|
||||
.verify_prehashed_strict(prehashed_bad2, Some(context), &good_sig)
|
||||
.is_err(),
|
||||
"Strict verification of a signature on a different message passed!"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
#[test]
|
||||
fn verify_batch_seven_signatures() {
|
||||
let messages: [&[u8]; 7] = [
|
||||
b"Watch closely everyone, I'm going to show you how to kill a god.",
|
||||
b"I'm not a cryptographer I just encrypt a lot.",
|
||||
b"Still not a cryptographer.",
|
||||
b"This is a test of the tsunami alert system. This is only a test.",
|
||||
b"Fuck dumbin' it down, spit ice, skip jewellery: Molotov cocktails on me like accessories.",
|
||||
b"Hey, I never cared about your bucks, so if I run up with a mask on, probably got a gas can too.",
|
||||
b"And I'm not here to fill 'er up. Nope, we came to riot, here to incite, we don't want any of your stuff.", ];
|
||||
let mut csprng = OsRng;
|
||||
let mut signing_keys: Vec<SigningKey> = Vec::new();
|
||||
let mut signatures: Vec<Signature> = Vec::new();
|
||||
|
||||
for i in 0..messages.len() {
|
||||
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
signatures.push(signing_key.sign(&messages[i]));
|
||||
signing_keys.push(signing_key);
|
||||
}
|
||||
let verifying_keys: Vec<VerifyingKey> =
|
||||
signing_keys.iter().map(|key| key.verifying_key()).collect();
|
||||
|
||||
let result = verify_batch(&messages, &signatures, &verifying_keys);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_key_hash_trait_check() {
|
||||
let mut csprng = OsRng {};
|
||||
let secret: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let public_from_secret: VerifyingKey = (&secret).into();
|
||||
|
||||
let mut m = HashMap::new();
|
||||
m.insert(public_from_secret, "Example_Public_Key");
|
||||
|
||||
m.insert(public_from_secret, "Updated Value");
|
||||
|
||||
let (k, v) = m.get_key_value(&public_from_secret).unwrap();
|
||||
assert_eq!(k, &public_from_secret);
|
||||
assert_eq!(v.clone(), "Updated Value");
|
||||
assert_eq!(m.len(), 1usize);
|
||||
|
||||
let second_secret: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let public_from_second_secret: VerifyingKey = (&second_secret).into();
|
||||
assert_ne!(public_from_secret, public_from_second_secret);
|
||||
m.insert(public_from_second_secret, "Second public key");
|
||||
|
||||
let (k, v) = m.get_key_value(&public_from_second_secret).unwrap();
|
||||
assert_eq!(k, &public_from_second_secret);
|
||||
assert_eq!(v.clone(), "Second public key");
|
||||
assert_eq!(m.len(), 2usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "serde"))]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(crate = "serde")]
|
||||
struct Demo {
|
||||
signing_key: SigningKey,
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "serde"))]
|
||||
mod serialisation {
|
||||
use super::*;
|
||||
|
||||
// The size for bincode to serialize the length of a byte array.
|
||||
static BINCODE_INT_LENGTH: usize = 8;
|
||||
|
||||
static PUBLIC_KEY_BYTES: [u8; PUBLIC_KEY_LENGTH] = [
|
||||
130, 039, 155, 015, 062, 076, 188, 063, 124, 122, 026, 251, 233, 253, 225, 220, 014, 041,
|
||||
166, 120, 108, 035, 254, 077, 160, 083, 172, 058, 219, 042, 086, 120,
|
||||
];
|
||||
|
||||
static SECRET_KEY_BYTES: [u8; SECRET_KEY_LENGTH] = [
|
||||
062, 070, 027, 163, 092, 182, 011, 003, 077, 234, 098, 004, 011, 127, 079, 228, 243, 187,
|
||||
150, 073, 201, 137, 076, 022, 085, 251, 152, 002, 241, 042, 072, 054,
|
||||
];
|
||||
|
||||
/// Signature with the above signing_key of a blank message.
|
||||
static SIGNATURE_BYTES: [u8; SIGNATURE_LENGTH] = [
|
||||
010, 126, 151, 143, 157, 064, 047, 001, 196, 140, 179, 058, 226, 152, 018, 102, 160, 123,
|
||||
080, 016, 210, 086, 196, 028, 053, 231, 012, 157, 169, 019, 158, 063, 045, 154, 238, 007,
|
||||
053, 185, 227, 229, 079, 108, 213, 080, 124, 252, 084, 167, 216, 085, 134, 144, 129, 149,
|
||||
041, 081, 063, 120, 126, 100, 092, 059, 050, 011,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signature_bincode() {
|
||||
let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES);
|
||||
let encoded_signature: Vec<u8> = bincode::serialize(&signature).unwrap();
|
||||
let decoded_signature: Signature = bincode::deserialize(&encoded_signature).unwrap();
|
||||
|
||||
assert_eq!(signature, decoded_signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signature_json() {
|
||||
let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES);
|
||||
let encoded_signature = serde_json::to_string(&signature).unwrap();
|
||||
let decoded_signature: Signature = serde_json::from_str(&encoded_signature).unwrap();
|
||||
|
||||
assert_eq!(signature, decoded_signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_verifying_key_bincode() {
|
||||
let verifying_key: VerifyingKey = VerifyingKey::from_bytes(&PUBLIC_KEY_BYTES).unwrap();
|
||||
let encoded_verifying_key: Vec<u8> = bincode::serialize(&verifying_key).unwrap();
|
||||
let decoded_verifying_key: VerifyingKey =
|
||||
bincode::deserialize(&encoded_verifying_key).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&PUBLIC_KEY_BYTES[..],
|
||||
&encoded_verifying_key[encoded_verifying_key.len() - PUBLIC_KEY_LENGTH..]
|
||||
);
|
||||
assert_eq!(verifying_key, decoded_verifying_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_verifying_key_json() {
|
||||
let verifying_key: VerifyingKey = VerifyingKey::from_bytes(&PUBLIC_KEY_BYTES).unwrap();
|
||||
let encoded_verifying_key = serde_json::to_string(&verifying_key).unwrap();
|
||||
let decoded_verifying_key: VerifyingKey =
|
||||
serde_json::from_str(&encoded_verifying_key).unwrap();
|
||||
|
||||
assert_eq!(verifying_key, decoded_verifying_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_verifying_key_json_too_long() {
|
||||
// derived from `serialize_deserialize_verifying_key_json` test
|
||||
// trailing zero elements makes key too long (34 bytes)
|
||||
let encoded_verifying_key_too_long = "[130,39,155,15,62,76,188,63,124,122,26,251,233,253,225,220,14,41,166,120,108,35,254,77,160,83,172,58,219,42,86,120,0,0]";
|
||||
let de_err = serde_json::from_str::<VerifyingKey>(&encoded_verifying_key_too_long)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(
|
||||
de_err.contains("invalid length 34"),
|
||||
"expected invalid length error, got: {de_err}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_verifying_key_json_too_short() {
|
||||
// derived from `serialize_deserialize_verifying_key_json` test
|
||||
let encoded_verifying_key_too_long = "[130,39,155,15]";
|
||||
let de_err = serde_json::from_str::<VerifyingKey>(&encoded_verifying_key_too_long)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(
|
||||
de_err.contains("invalid length 4"),
|
||||
"expected invalid length error, got: {de_err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signing_key_bincode() {
|
||||
let signing_key = SigningKey::from_bytes(&SECRET_KEY_BYTES);
|
||||
let encoded_signing_key: Vec<u8> = bincode::serialize(&signing_key).unwrap();
|
||||
let decoded_signing_key: SigningKey = bincode::deserialize(&encoded_signing_key).unwrap();
|
||||
|
||||
for i in 0..SECRET_KEY_LENGTH {
|
||||
assert_eq!(SECRET_KEY_BYTES[i], decoded_signing_key.to_bytes()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signing_key_json() {
|
||||
let signing_key = SigningKey::from_bytes(&SECRET_KEY_BYTES);
|
||||
let encoded_signing_key = serde_json::to_string(&signing_key).unwrap();
|
||||
let decoded_signing_key: SigningKey = serde_json::from_str(&encoded_signing_key).unwrap();
|
||||
|
||||
for i in 0..SECRET_KEY_LENGTH {
|
||||
assert_eq!(SECRET_KEY_BYTES[i], decoded_signing_key.to_bytes()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signing_key_json_too_long() {
|
||||
// derived from `serialize_deserialize_signing_key_json` test
|
||||
// trailing zero elements makes key too long (34 bytes)
|
||||
let encoded_signing_key_too_long = "[62,70,27,163,92,182,11,3,77,234,98,4,11,127,79,228,243,187,150,73,201,137,76,22,85,251,152,2,241,42,72,54,0,0]";
|
||||
let de_err = serde_json::from_str::<SigningKey>(&encoded_signing_key_too_long)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(
|
||||
de_err.contains("invalid length 34"),
|
||||
"expected invalid length error, got: {de_err}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signing_key_json_too_short() {
|
||||
// derived from `serialize_deserialize_signing_key_json` test
|
||||
let encoded_signing_key_too_long = "[62,70,27,163]";
|
||||
let de_err = serde_json::from_str::<SigningKey>(&encoded_signing_key_too_long)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(
|
||||
de_err.contains("invalid length 4"),
|
||||
"expected invalid length error, got: {de_err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_signing_key_toml() {
|
||||
let demo = Demo {
|
||||
signing_key: SigningKey::from_bytes(&SECRET_KEY_BYTES),
|
||||
};
|
||||
|
||||
println!("\n\nWrite to toml");
|
||||
let demo_toml = toml::to_string(&demo).unwrap();
|
||||
println!("{}", demo_toml);
|
||||
let demo_toml_rebuild: Result<Demo, _> = toml::from_str(&demo_toml);
|
||||
println!("{:?}", demo_toml_rebuild);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_verifying_key_size() {
|
||||
let verifying_key: VerifyingKey = VerifyingKey::from_bytes(&PUBLIC_KEY_BYTES).unwrap();
|
||||
assert_eq!(
|
||||
bincode::serialized_size(&verifying_key).unwrap() as usize,
|
||||
BINCODE_INT_LENGTH + PUBLIC_KEY_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_signature_size() {
|
||||
let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES);
|
||||
assert_eq!(
|
||||
bincode::serialized_size(&signature).unwrap() as usize,
|
||||
SIGNATURE_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_signing_key_size() {
|
||||
let signing_key = SigningKey::from_bytes(&SECRET_KEY_BYTES);
|
||||
assert_eq!(
|
||||
bincode::serialized_size(&signing_key).unwrap() as usize,
|
||||
BINCODE_INT_LENGTH + SECRET_KEY_LENGTH
|
||||
);
|
||||
}
|
||||
}
|
BIN
ed25519-dalek/tests/examples/pkcs8-v1.der
Normal file
BIN
ed25519-dalek/tests/examples/pkcs8-v1.der
Normal file
Binary file not shown.
BIN
ed25519-dalek/tests/examples/pkcs8-v2.der
Normal file
BIN
ed25519-dalek/tests/examples/pkcs8-v2.der
Normal file
Binary file not shown.
BIN
ed25519-dalek/tests/examples/pubkey.der
Normal file
BIN
ed25519-dalek/tests/examples/pubkey.der
Normal file
Binary file not shown.
71
ed25519-dalek/tests/pkcs8.rs
Normal file
71
ed25519-dalek/tests/pkcs8.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! PKCS#8 private key and SPKI public key tests.
|
||||
//!
|
||||
//! These are standard formats for storing public and private keys, defined in
|
||||
//! RFC5958 (PKCS#8) and RFC5280 (SPKI).
|
||||
|
||||
#![cfg(feature = "pkcs8")]
|
||||
|
||||
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use hex_literal::hex;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use ed25519_dalek::pkcs8::{EncodePrivateKey, EncodePublicKey};
|
||||
|
||||
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
|
||||
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");
|
||||
|
||||
/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER.
|
||||
const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der");
|
||||
|
||||
/// Ed25519 SubjectVerifyingKeyInfo encoded as ASN.1 DER.
|
||||
const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der");
|
||||
|
||||
/// Secret key bytes.
|
||||
///
|
||||
/// Extracted with:
|
||||
/// $ openssl asn1parse -inform der -in tests/examples/pkcs8-v1.der
|
||||
const SK_BYTES: [u8; 32] = hex!("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842");
|
||||
|
||||
/// Public key bytes.
|
||||
const PK_BYTES: [u8; 32] = hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1");
|
||||
|
||||
#[test]
|
||||
fn decode_pkcs8_v1() {
|
||||
let keypair = SigningKey::from_pkcs8_der(PKCS8_V1_DER).unwrap();
|
||||
assert_eq!(SK_BYTES, keypair.to_bytes());
|
||||
assert_eq!(PK_BYTES, keypair.verifying_key().to_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pkcs8_v2() {
|
||||
let keypair = SigningKey::from_pkcs8_der(PKCS8_V2_DER).unwrap();
|
||||
assert_eq!(SK_BYTES, keypair.to_bytes());
|
||||
assert_eq!(PK_BYTES, keypair.verifying_key().to_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_verifying_key() {
|
||||
let verifying_key = VerifyingKey::from_public_key_der(PUBLIC_KEY_DER).unwrap();
|
||||
assert_eq!(PK_BYTES, verifying_key.to_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "alloc")]
|
||||
fn encode_pkcs8() {
|
||||
let keypair = SigningKey::from_bytes(&SK_BYTES);
|
||||
let pkcs8_key = keypair.to_pkcs8_der().unwrap();
|
||||
|
||||
let keypair2 = SigningKey::from_pkcs8_der(pkcs8_key.as_bytes()).unwrap();
|
||||
assert_eq!(keypair.to_bytes(), keypair2.to_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "alloc")]
|
||||
fn encode_verifying_key() {
|
||||
let verifying_key = VerifyingKey::from_bytes(&PK_BYTES).unwrap();
|
||||
let verifying_key_der = verifying_key.to_public_key_der().unwrap();
|
||||
|
||||
let verifying_key2 = VerifyingKey::from_public_key_der(verifying_key_der.as_bytes()).unwrap();
|
||||
assert_eq!(verifying_key, verifying_key2);
|
||||
}
|
231
ed25519-dalek/tests/validation_criteria.rs
Normal file
231
ed25519-dalek/tests/validation_criteria.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use ed25519::signature::Verifier;
|
||||
use ed25519_dalek::{Signature, VerifyingKey};
|
||||
|
||||
use serde::{de::Error as SError, Deserialize, Deserializer};
|
||||
use std::{collections::BTreeSet as Set, fs::File};
|
||||
|
||||
/// The set of edge cases that [`VerifyingKey::verify()`] permits.
|
||||
const VERIFY_ALLOWED_EDGECASES: &[Flag] = &[
|
||||
Flag::LowOrderA,
|
||||
Flag::LowOrderR,
|
||||
Flag::NonCanonicalA,
|
||||
Flag::LowOrderComponentA,
|
||||
Flag::LowOrderComponentR,
|
||||
// `ReencodedK` is not actually permitted by `verify()`, but it looks that way in the tests
|
||||
// because it sometimes occurs with a low-order A. 1/8 of the time, the resulting signature
|
||||
// will be identical the one made with a normal k. find_validation_criteria shows that indeed
|
||||
// this occurs 10/58 of the time
|
||||
Flag::ReencodedK,
|
||||
];
|
||||
|
||||
/// The set of edge cases that [`VerifyingKey::verify_strict()`] permits
|
||||
const VERIFY_STRICT_ALLOWED_EDGECASES: &[Flag] =
|
||||
&[Flag::LowOrderComponentA, Flag::LowOrderComponentR];
|
||||
|
||||
/// Each variant describes a specfiic edge case that can occur in an Ed25519 signature. Refer to
|
||||
/// the test vector [README][] for more info.
|
||||
///
|
||||
/// [README]: https://github.com/C2SP/CCTV/blob/5ea85644bd035c555900a2f707f7e4c31ea65ced/ed25519vectors/README.md
|
||||
#[derive(Deserialize, Debug, Copy, Clone, PartialOrd, Ord, Eq, PartialEq)]
|
||||
enum Flag {
|
||||
#[serde(rename = "low_order")]
|
||||
LowOrder,
|
||||
#[serde(rename = "low_order_A")]
|
||||
LowOrderA,
|
||||
#[serde(rename = "low_order_R")]
|
||||
LowOrderR,
|
||||
#[serde(rename = "non_canonical_A")]
|
||||
NonCanonicalA,
|
||||
#[serde(rename = "non_canonical_R")]
|
||||
NonCanonicalR,
|
||||
#[serde(rename = "low_order_component_A")]
|
||||
LowOrderComponentA,
|
||||
#[serde(rename = "low_order_component_R")]
|
||||
LowOrderComponentR,
|
||||
#[serde(rename = "low_order_residue")]
|
||||
LowOrderResidue,
|
||||
#[serde(rename = "reencoded_k")]
|
||||
ReencodedK,
|
||||
}
|
||||
|
||||
/// This is an intermediate representation between JSON and TestVector
|
||||
#[derive(Deserialize)]
|
||||
struct IntermediateTestVector {
|
||||
number: usize,
|
||||
#[serde(deserialize_with = "bytes_from_hex", rename = "key")]
|
||||
pubkey: Vec<u8>,
|
||||
#[serde(deserialize_with = "bytes_from_hex")]
|
||||
sig: Vec<u8>,
|
||||
msg: String,
|
||||
flags: Option<Set<Flag>>,
|
||||
}
|
||||
|
||||
/// The test vector struct from [CCTV][]. `sig` may or may not be a valid signature of `msg` with
|
||||
/// respect to `pubkey`, depending on the verification function's validation criteria. `flags`
|
||||
/// describes all the edge cases which this test vector falls into.
|
||||
///
|
||||
/// [CCTV]: https://github.com/C2SP/CCTV/tree/5ea85644bd035c555900a2f707f7e4c31ea65ced/ed25519vectors
|
||||
struct TestVector {
|
||||
number: usize,
|
||||
pubkey: VerifyingKey,
|
||||
sig: Signature,
|
||||
msg: Vec<u8>,
|
||||
flags: Set<Flag>,
|
||||
}
|
||||
|
||||
impl From<IntermediateTestVector> for TestVector {
|
||||
fn from(tv: IntermediateTestVector) -> Self {
|
||||
let number = tv.number;
|
||||
let pubkey = {
|
||||
let mut buf = [0u8; 32];
|
||||
buf.copy_from_slice(&tv.pubkey);
|
||||
VerifyingKey::from_bytes(&buf).unwrap()
|
||||
};
|
||||
let sig = {
|
||||
let mut buf = [0u8; 64];
|
||||
buf.copy_from_slice(&tv.sig);
|
||||
Signature::from_bytes(&buf)
|
||||
};
|
||||
let msg = tv.msg.as_bytes().to_vec();
|
||||
|
||||
// Unwrap the Option<Set<Flag>>
|
||||
let flags = tv.flags.unwrap_or_else(Default::default);
|
||||
|
||||
Self {
|
||||
number,
|
||||
pubkey,
|
||||
sig,
|
||||
msg,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tells serde how to deserialize bytes from hex
|
||||
fn bytes_from_hex<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut hex_str = String::deserialize(deserializer)?;
|
||||
// Prepend a 0 if it's not even length
|
||||
if hex_str.len() % 2 == 1 {
|
||||
hex_str.insert(0, '0');
|
||||
}
|
||||
hex::decode(hex_str).map_err(|e| SError::custom(format!("{:?}", e)))
|
||||
}
|
||||
|
||||
fn get_test_vectors() -> impl Iterator<Item = TestVector> {
|
||||
let f = File::open("VALIDATIONVECTORS").expect(
|
||||
"This test is only available when the code has been cloned from the git repository, since
|
||||
the VALIDATIONVECTORS file is large and is therefore not included within the distributed \
|
||||
crate.",
|
||||
);
|
||||
|
||||
serde_json::from_reader::<_, Vec<IntermediateTestVector>>(f)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(TestVector::from)
|
||||
}
|
||||
|
||||
/// Tests that the verify() and verify_strict() functions succeed only on test cases whose flags
|
||||
/// (i.e., edge cases it falls into) are a subset of VERIFY_ALLOWED_EDGECASES and
|
||||
/// VERIFY_STRICT_ALLOWED_EDGECASES, respectively
|
||||
#[test]
|
||||
fn check_validation_criteria() {
|
||||
let verify_allowed_edgecases = Set::from_iter(VERIFY_ALLOWED_EDGECASES.to_vec());
|
||||
let verify_strict_allowed_edgecases = Set::from_iter(VERIFY_STRICT_ALLOWED_EDGECASES.to_vec());
|
||||
|
||||
for TestVector {
|
||||
number,
|
||||
pubkey,
|
||||
msg,
|
||||
sig,
|
||||
flags,
|
||||
} in get_test_vectors()
|
||||
{
|
||||
// If all the verify-permitted flags here are ones we permit, then verify() should succeed.
|
||||
// Otherwise, it should not.
|
||||
let success = pubkey.verify(&msg, &sig).is_ok();
|
||||
if flags.is_subset(&verify_allowed_edgecases) {
|
||||
assert!(success, "verify() expected success in testcase #{number}",);
|
||||
} else {
|
||||
assert!(!success, "verify() expected failure in testcase #{number}",);
|
||||
}
|
||||
|
||||
// If all the verify_strict-permitted flags here are ones we permit, then verify_strict()
|
||||
// should succeed. Otherwise, it should not.
|
||||
let success = pubkey.verify_strict(&msg, &sig).is_ok();
|
||||
if flags.is_subset(&verify_strict_allowed_edgecases) {
|
||||
assert!(
|
||||
success,
|
||||
"verify_strict() expected success in testcase #{number}",
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!success,
|
||||
"verify_strict() expected failure in testcase #{number}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the flags that are consistently permitted by verify() and verify_strict()
|
||||
#[test]
|
||||
fn find_validation_criteria() {
|
||||
let mut verify_allowed_edgecases = Set::new();
|
||||
let mut verify_strict_allowed_edgecases = Set::new();
|
||||
|
||||
// Counts the number of times a signature with a re-encoded k and a low-order A verified. This
|
||||
// happens with 1/8 probability, assuming the usual verification equation(s).
|
||||
let mut num_lucky_reencoded_k = 0;
|
||||
let mut num_reencoded_k = 0;
|
||||
|
||||
for TestVector {
|
||||
number: _,
|
||||
pubkey,
|
||||
msg,
|
||||
sig,
|
||||
flags,
|
||||
} in get_test_vectors()
|
||||
{
|
||||
// If verify() was a success, add all the associated flags to verify-permitted set
|
||||
let success = pubkey.verify(&msg, &sig).is_ok();
|
||||
|
||||
// If this is ReencodedK && LowOrderA, log some statistics
|
||||
if flags.contains(&Flag::ReencodedK) && flags.contains(&Flag::LowOrderA) {
|
||||
num_reencoded_k += 1;
|
||||
num_lucky_reencoded_k += success as u8;
|
||||
}
|
||||
|
||||
if success {
|
||||
for flag in &flags {
|
||||
// Don't count re-encoded k when A is low-order. This is because the
|
||||
// re-encoded k might be a multiple of 8 by accident
|
||||
if *flag == Flag::ReencodedK && flags.contains(&Flag::LowOrderA) {
|
||||
continue;
|
||||
} else {
|
||||
verify_allowed_edgecases.insert(*flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If verify_strict() was a success, add all the associated flags to
|
||||
// verify_strict-permitted set
|
||||
let success = pubkey.verify_strict(&msg, &sig).is_ok();
|
||||
if success {
|
||||
for flag in &flags {
|
||||
verify_strict_allowed_edgecases.insert(*flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("VERIFY_ALLOWED_EDGECASES: {:?}", verify_allowed_edgecases);
|
||||
println!(
|
||||
"VERIFY_STRICT_ALLOWED_EDGECASES: {:?}",
|
||||
verify_strict_allowed_edgecases
|
||||
);
|
||||
println!(
|
||||
"re-encoded k && low-order A yielded a valid signature {}/{} of the time",
|
||||
num_lucky_reencoded_k, num_reencoded_k
|
||||
);
|
||||
}
|
54
ed25519-dalek/tests/x25519.rs
Normal file
54
ed25519-dalek/tests/x25519.rs
Normal file
@ -0,0 +1,54 @@
|
||||
//! Tests for converting Ed25519 keys into X25519 (Montgomery form) keys.
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
use hex_literal::hex;
|
||||
|
||||
/// Tests that X25519 Diffie-Hellman works when using keys converted from Ed25519.
|
||||
// TODO: generate test vectors using another implementation of Ed25519->X25519
|
||||
#[test]
|
||||
fn ed25519_to_x25519_dh() {
|
||||
// Keys from RFC8032 test vectors (from section 7.1)
|
||||
let ed25519_secret_key_a =
|
||||
hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
|
||||
let ed25519_secret_key_b =
|
||||
hex!("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb");
|
||||
|
||||
let ed25519_signing_key_a = SigningKey::from_bytes(&ed25519_secret_key_a);
|
||||
let ed25519_signing_key_b = SigningKey::from_bytes(&ed25519_secret_key_b);
|
||||
|
||||
let scalar_a_bytes = ed25519_signing_key_a.to_scalar_bytes();
|
||||
let scalar_b_bytes = ed25519_signing_key_b.to_scalar_bytes();
|
||||
|
||||
assert_eq!(
|
||||
scalar_a_bytes,
|
||||
hex!("357c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de90f")
|
||||
);
|
||||
assert_eq!(
|
||||
scalar_b_bytes,
|
||||
hex!("6ebd9ed75882d52815a97585caf4790a7f6c6b3b7f821c5e259a24b02e502e11")
|
||||
);
|
||||
|
||||
let x25519_public_key_a = ed25519_signing_key_a.verifying_key().to_montgomery();
|
||||
let x25519_public_key_b = ed25519_signing_key_b.verifying_key().to_montgomery();
|
||||
|
||||
assert_eq!(
|
||||
x25519_public_key_a.to_bytes(),
|
||||
hex!("d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e")
|
||||
);
|
||||
assert_eq!(
|
||||
x25519_public_key_b.to_bytes(),
|
||||
hex!("25c704c594b88afc00a76b69d1ed2b984d7e22550f3ed0802d04fbcd07d38d47")
|
||||
);
|
||||
|
||||
let expected_shared_secret =
|
||||
hex!("5166f24a6918368e2af831a4affadd97af0ac326bdf143596c045967cc00230e");
|
||||
|
||||
assert_eq!(
|
||||
x25519_public_key_a.mul_clamped(scalar_b_bytes).to_bytes(),
|
||||
expected_shared_secret
|
||||
);
|
||||
assert_eq!(
|
||||
x25519_public_key_b.mul_clamped(scalar_a_bytes).to_bytes(),
|
||||
expected_shared_secret
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user