├── .github └── workflows │ ├── rust.yml │ └── security_audit.yml ├── .gitignore ├── CHANGES.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── img ├── signatory-rustacean.png ├── signatory-rustacean.svg ├── signatory.png └── signatory.svg ├── signatory-dalek ├── Cargo.toml ├── README.md ├── benches │ └── ed25519.rs └── src │ └── lib.rs ├── signatory-ledger-tm ├── Cargo.toml ├── README.md ├── benches │ └── ed25519.rs └── src │ ├── ledgertm.rs │ ├── lib.rs │ └── signer.rs ├── signatory-ring ├── Cargo.toml ├── README.md ├── benches │ ├── ecdsa.rs │ └── ed25519.rs └── src │ ├── ecdsa.rs │ ├── ecdsa │ ├── p256.rs │ ├── p384.rs │ └── signer.rs │ ├── ed25519.rs │ └── lib.rs ├── signatory-secp256k1 ├── Cargo.toml ├── README.md ├── benches │ └── ecdsa.rs └── src │ └── lib.rs ├── signatory-sodiumoxide ├── Cargo.toml ├── README.md ├── benches │ └── ed25519.rs └── src │ └── lib.rs └── src ├── ecdsa.rs ├── ed25519.rs ├── ed25519 ├── public_key.rs ├── seed.rs ├── test_macros.rs └── test_vectors.rs ├── encoding.rs ├── encoding ├── decode.rs ├── encode.rs ├── error.rs ├── macros.rs └── pkcs8.rs ├── lib.rs ├── public_key.rs ├── test_vector.rs └── test_vector └── pkcs8.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: develop 7 | 8 | name: Rust 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v1 17 | 18 | - name: Cache cargo registry 19 | uses: actions/cache@v1 20 | with: 21 | path: ~/.cargo/registry 22 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 23 | 24 | - name: Cache cargo index 25 | uses: actions/cache@v1 26 | with: 27 | path: ~/.cargo/git 28 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 29 | 30 | - name: Install stable toolchain 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: stable 34 | override: true 35 | 36 | - name: Run cargo check 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: check 40 | 41 | build: 42 | name: Build 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | platform: 47 | - ubuntu-latest 48 | - macos-latest 49 | - windows-latest 50 | toolchain: 51 | - stable 52 | - 1.36.0 53 | steps: 54 | - name: Checkout sources 55 | uses: actions/checkout@v1 56 | 57 | - name: Cache cargo registry 58 | uses: actions/cache@v1 59 | with: 60 | path: ~/.cargo/registry 61 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 62 | 63 | - name: Cache cargo index 64 | uses: actions/cache@v1 65 | with: 66 | path: ~/.cargo/git 67 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 68 | 69 | - name: Cache cargo build 70 | uses: actions/cache@v1 71 | with: 72 | path: target 73 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-cargo-build-target-${{ hashFiles('Cargo.lock') }} 74 | 75 | - name: Install toolchain 76 | uses: actions-rs/toolchain@v1 77 | with: 78 | toolchain: ${{ matrix.toolchain }} 79 | override: true 80 | 81 | - name: Run cargo build --no-default-features 82 | uses: actions-rs/cargo@v1 83 | env: 84 | CARGO_INCREMENTAL: 0 85 | RUSTFLAGS: -D warnings 86 | with: 87 | command: build 88 | args: --release --no-default-features 89 | 90 | - name: Run cargo build 91 | uses: actions-rs/cargo@v1 92 | env: 93 | CARGO_INCREMENTAL: 0 94 | RUSTFLAGS: -D warnings 95 | with: 96 | command: build 97 | args: --release 98 | 99 | - name: Run cargo build --features=ecdsa 100 | uses: actions-rs/cargo@v1 101 | env: 102 | CARGO_INCREMENTAL: 0 103 | RUSTFLAGS: -D warnings 104 | with: 105 | command: build 106 | args: --release --features=ecdsa 107 | 108 | - name: Run cargo build --features=ed25519 109 | uses: actions-rs/cargo@v1 110 | env: 111 | CARGO_INCREMENTAL: 0 112 | RUSTFLAGS: -D warnings 113 | with: 114 | command: build 115 | args: --release --features=ed25519 116 | 117 | - name: Run cargo build --features=ed25519 118 | uses: actions-rs/cargo@v1 119 | env: 120 | CARGO_INCREMENTAL: 0 121 | RUSTFLAGS: -D warnings 122 | with: 123 | command: build 124 | args: --release --features=ecdsa,ed25519 125 | 126 | - name: Run cargo build --no-default-features --features=ecdsa,ed25519,encoding,pkcs8 127 | uses: actions-rs/cargo@v1 128 | env: 129 | CARGO_INCREMENTAL: 0 130 | RUSTFLAGS: -D warnings 131 | with: 132 | command: build 133 | args: --release --no-default-features --features=ecdsa,ed25519,encoding,pkcs8 134 | 135 | - name: Run cargo build --all-features 136 | uses: actions-rs/cargo@v1 137 | env: 138 | CARGO_INCREMENTAL: 0 139 | RUSTFLAGS: -D warnings 140 | with: 141 | command: build 142 | args: --release --all-features 143 | 144 | test: 145 | name: Test Suite 146 | strategy: 147 | matrix: 148 | platform: 149 | - ubuntu-latest 150 | - macos-latest 151 | - windows-latest 152 | toolchain: 153 | - stable 154 | - 1.36.0 155 | runs-on: ${{ matrix.platform }} 156 | steps: 157 | - name: Checkout sources 158 | uses: actions/checkout@v1 159 | 160 | - name: Cache cargo registry 161 | uses: actions/cache@v1 162 | with: 163 | path: ~/.cargo/registry 164 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 165 | 166 | - name: Cache cargo index 167 | uses: actions/cache@v1 168 | with: 169 | path: ~/.cargo/git 170 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 171 | 172 | - name: Cache cargo build 173 | uses: actions/cache@v1 174 | with: 175 | path: target 176 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-cargo-test-${{ hashFiles('Cargo.lock') }} 177 | 178 | - name: Install toolchain 179 | uses: actions-rs/toolchain@v1 180 | with: 181 | toolchain: ${{ matrix.toolchain }} 182 | override: true 183 | 184 | - name: Run cargo test 185 | uses: actions-rs/cargo@v1 186 | env: 187 | CARGO_INCREMENTAL: 0 188 | RUSTFLAGS: -D warnings 189 | with: 190 | command: test 191 | args: --release --lib --features=ecdsa,ed25519,test-vectors 192 | 193 | dalek: 194 | name: "Provider: ed25519-dalek" 195 | strategy: 196 | matrix: 197 | platform: 198 | - ubuntu-latest 199 | - macos-latest 200 | - windows-latest 201 | toolchain: 202 | - stable 203 | - 1.36.0 204 | runs-on: ${{ matrix.platform }} 205 | steps: 206 | - name: Checkout sources 207 | uses: actions/checkout@v1 208 | 209 | - name: Cache cargo registry 210 | uses: actions/cache@v1 211 | with: 212 | path: ~/.cargo/registry 213 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 214 | 215 | - name: Cache cargo index 216 | uses: actions/cache@v1 217 | with: 218 | path: ~/.cargo/git 219 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 220 | 221 | - name: Cache cargo build 222 | uses: actions/cache@v1 223 | with: 224 | path: target 225 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-dalek-${{ hashFiles('Cargo.lock') }} 226 | 227 | - name: Install toolchain 228 | uses: actions-rs/toolchain@v1 229 | with: 230 | toolchain: ${{ matrix.toolchain }} 231 | override: true 232 | 233 | - name: Run cargo build 234 | uses: actions-rs/cargo@v1 235 | env: 236 | CARGO_INCREMENTAL: 0 237 | RUSTFLAGS: -D warnings 238 | with: 239 | command: build 240 | args: --package=signatory-dalek --benches --release 241 | 242 | - name: Run cargo test 243 | uses: actions-rs/cargo@v1 244 | env: 245 | CARGO_INCREMENTAL: 0 246 | RUSTFLAGS: -D warnings 247 | with: 248 | command: test 249 | args: --package=signatory-dalek --release 250 | 251 | - name: Install clippy 252 | run: rustup component add clippy 253 | 254 | - name: Run cargo clippy --all 255 | uses: actions-rs/cargo@v1 256 | with: 257 | command: clippy 258 | args: --package signatory-dalek 259 | 260 | ring: 261 | name: "Provider: ring" 262 | strategy: 263 | matrix: 264 | platform: 265 | - ubuntu-latest 266 | - macos-latest 267 | - windows-latest 268 | toolchain: 269 | - stable 270 | - 1.36.0 271 | runs-on: ${{ matrix.platform }} 272 | steps: 273 | - name: Checkout sources 274 | uses: actions/checkout@v1 275 | 276 | - name: Cache cargo registry 277 | uses: actions/cache@v1 278 | with: 279 | path: ~/.cargo/registry 280 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 281 | 282 | - name: Cache cargo index 283 | uses: actions/cache@v1 284 | with: 285 | path: ~/.cargo/git 286 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 287 | 288 | - name: Cache cargo build 289 | uses: actions/cache@v1 290 | with: 291 | path: target 292 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-ring-${{ hashFiles('Cargo.lock') }} 293 | 294 | - name: Install toolchain 295 | uses: actions-rs/toolchain@v1 296 | with: 297 | toolchain: ${{ matrix.toolchain }} 298 | override: true 299 | 300 | - name: Run cargo build 301 | uses: actions-rs/cargo@v1 302 | env: 303 | CARGO_INCREMENTAL: 0 304 | RUSTFLAGS: -D warnings 305 | with: 306 | command: build 307 | args: --package=signatory-ring --benches --release 308 | 309 | - name: Run cargo test 310 | uses: actions-rs/cargo@v1 311 | env: 312 | CARGO_INCREMENTAL: 0 313 | RUSTFLAGS: -D warnings 314 | with: 315 | command: test 316 | args: --package=signatory-ring --release 317 | 318 | - name: Install clippy 319 | run: rustup component add clippy 320 | 321 | - name: Run cargo clippy --all 322 | uses: actions-rs/cargo@v1 323 | with: 324 | command: clippy 325 | args: --package signatory-ring 326 | 327 | secp256k1: 328 | name: "Provider: secp256k1" 329 | strategy: 330 | matrix: 331 | platform: 332 | - ubuntu-latest 333 | - macos-latest 334 | - windows-latest 335 | toolchain: 336 | - stable 337 | - 1.36.0 338 | runs-on: ${{ matrix.platform }} 339 | steps: 340 | - name: Checkout sources 341 | uses: actions/checkout@v1 342 | 343 | - name: Cache cargo registry 344 | uses: actions/cache@v1 345 | with: 346 | path: ~/.cargo/registry 347 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 348 | 349 | - name: Cache cargo index 350 | uses: actions/cache@v1 351 | with: 352 | path: ~/.cargo/git 353 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 354 | 355 | - name: Cache cargo build 356 | uses: actions/cache@v1 357 | with: 358 | path: target 359 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-secp256k1-${{ hashFiles('Cargo.lock') }} 360 | 361 | - name: Install toolchain 362 | uses: actions-rs/toolchain@v1 363 | with: 364 | toolchain: ${{ matrix.toolchain }} 365 | override: true 366 | 367 | - name: Run cargo build 368 | uses: actions-rs/cargo@v1 369 | env: 370 | CARGO_INCREMENTAL: 0 371 | RUSTFLAGS: -D warnings 372 | with: 373 | command: build 374 | args: --package=signatory-secp256k1 --benches --release 375 | 376 | - name: Run cargo test 377 | uses: actions-rs/cargo@v1 378 | env: 379 | CARGO_INCREMENTAL: 0 380 | RUSTFLAGS: -D warnings 381 | with: 382 | command: test 383 | args: --package=signatory-secp256k1 --release 384 | 385 | - name: Install clippy 386 | run: rustup component add clippy 387 | 388 | - name: Run cargo clippy --all 389 | uses: actions-rs/cargo@v1 390 | with: 391 | command: clippy 392 | args: --package signatory-secp256k1 393 | 394 | sodiumoxide: 395 | name: "Provider: sodiumoxide" 396 | strategy: 397 | matrix: 398 | platform: 399 | - ubuntu-latest 400 | - macos-latest 401 | - windows-latest 402 | toolchain: 403 | - stable 404 | - 1.36.0 405 | runs-on: ${{ matrix.platform }} 406 | steps: 407 | - name: Checkout sources 408 | uses: actions/checkout@v1 409 | 410 | - name: Cache cargo registry 411 | uses: actions/cache@v1 412 | with: 413 | path: ~/.cargo/registry 414 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 415 | 416 | - name: Cache cargo index 417 | uses: actions/cache@v1 418 | with: 419 | path: ~/.cargo/git 420 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 421 | 422 | - name: Cache cargo build 423 | uses: actions/cache@v1 424 | with: 425 | path: target 426 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-sodiumoxide-${{ hashFiles('Cargo.lock') }} 427 | 428 | - name: Install toolchain 429 | uses: actions-rs/toolchain@v1 430 | with: 431 | toolchain: ${{ matrix.toolchain }} 432 | override: true 433 | 434 | - name: Run cargo build 435 | uses: actions-rs/cargo@v1 436 | env: 437 | CARGO_INCREMENTAL: 0 438 | RUSTFLAGS: -D warnings 439 | with: 440 | command: build 441 | args: --package=signatory-sodiumoxide --benches --release 442 | 443 | - name: Run cargo test 444 | uses: actions-rs/cargo@v1 445 | env: 446 | CARGO_INCREMENTAL: 0 447 | RUSTFLAGS: -D warnings 448 | with: 449 | command: test 450 | args: --package=signatory-sodiumoxide --release 451 | 452 | - name: Install clippy 453 | run: rustup component add clippy 454 | 455 | - name: Run cargo clippy --all 456 | uses: actions-rs/cargo@v1 457 | with: 458 | command: clippy 459 | args: --package signatory-secp256k1 460 | 461 | ledger-tm: 462 | name: "Provider: ledger-tm" 463 | strategy: 464 | matrix: 465 | toolchain: 466 | - stable 467 | - 1.36.0 468 | runs-on: ubuntu-latest 469 | steps: 470 | - name: Checkout sources 471 | uses: actions/checkout@v1 472 | 473 | - name: Cache cargo registry 474 | uses: actions/cache@v1 475 | with: 476 | path: ~/.cargo/registry 477 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 478 | 479 | - name: Cache cargo index 480 | uses: actions/cache@v1 481 | with: 482 | path: ~/.cargo/git 483 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 484 | 485 | - name: Cache cargo build 486 | uses: actions/cache@v1 487 | with: 488 | path: target 489 | key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-ledger-tm-${{ hashFiles('Cargo.lock') }} 490 | 491 | - name: Install toolchain 492 | uses: actions-rs/toolchain@v1 493 | with: 494 | toolchain: ${{ matrix.toolchain }} 495 | override: true 496 | 497 | - name: Install libudev-dev 498 | run: sudo apt-get update && sudo apt-get install libudev-dev 499 | 500 | - name: Run cargo build 501 | uses: actions-rs/cargo@v1 502 | env: 503 | CARGO_INCREMENTAL: 0 504 | RUSTFLAGS: -D warnings 505 | with: 506 | command: build 507 | args: --package=signatory-ledger-tm --release 508 | 509 | - name: Install clippy 510 | run: rustup component add clippy 511 | 512 | - name: Run cargo clippy --all 513 | uses: actions-rs/cargo@v1 514 | with: 515 | command: clippy 516 | args: --package signatory-ledger-tm 517 | 518 | coverage: 519 | name: Code Coverage 520 | runs-on: ubuntu-latest 521 | steps: 522 | - name: Checkout repository 523 | uses: actions/checkout@v2 524 | 525 | - name: Cache cargo registry 526 | uses: actions/cache@v1 527 | with: 528 | path: ~/.cargo/registry 529 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 530 | 531 | - name: Cache cargo index 532 | uses: actions/cache@v1 533 | with: 534 | path: ~/.cargo/git 535 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 536 | 537 | - name: Cache cargo build 538 | uses: actions/cache@v1 539 | with: 540 | path: target 541 | key: ${{ runner.os }}-coverage-cargo-build-target-${{ hashFiles('Cargo.lock') }} 542 | 543 | - name: Install stable toolchain 544 | uses: actions-rs/toolchain@v1 545 | with: 546 | toolchain: stable 547 | override: true 548 | 549 | - name: Run cargo-tarpaulin 550 | uses: actions-rs/tarpaulin@v0.1 551 | env: 552 | CARGO_INCREMENTAL: 0 553 | with: 554 | version: 0.11.0 555 | args: --all --exclude signatory-ledger-tm -- --test-threads 1 556 | 557 | - name: Upload to codecov.io 558 | uses: codecov/codecov-action@v1.0.2 559 | with: 560 | token: ${{secrets.CODECOV_TOKEN}} 561 | 562 | - name: Archive code coverage results 563 | uses: actions/upload-artifact@v1 564 | with: 565 | name: code-coverage-report 566 | path: cobertura.xml 567 | 568 | fmt: 569 | name: Rustfmt 570 | runs-on: ubuntu-latest 571 | steps: 572 | - name: Checkout sources 573 | uses: actions/checkout@v1 574 | 575 | - name: Install stable toolchain 576 | uses: actions-rs/toolchain@v1 577 | with: 578 | toolchain: stable 579 | override: true 580 | 581 | - name: Install rustfmt 582 | run: rustup component add rustfmt 583 | 584 | - name: Run cargo fmt 585 | uses: actions-rs/cargo@v1 586 | with: 587 | command: fmt 588 | args: --all -- --check 589 | 590 | clippy: 591 | name: Clippy 592 | runs-on: ubuntu-latest 593 | steps: 594 | - name: Checkout sources 595 | uses: actions/checkout@v1 596 | 597 | - name: Cache cargo registry 598 | uses: actions/cache@v1 599 | with: 600 | path: ~/.cargo/registry 601 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} 602 | 603 | - name: Cache cargo index 604 | uses: actions/cache@v1 605 | with: 606 | path: ~/.cargo/git 607 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} 608 | 609 | - name: Install stable toolchain 610 | uses: actions-rs/toolchain@v1 611 | with: 612 | toolchain: stable 613 | override: true 614 | 615 | - name: Install clippy 616 | run: rustup component add clippy 617 | 618 | - name: Run cargo clippy --all 619 | uses: actions-rs/cargo@v1 620 | with: 621 | command: clippy 622 | args: --all-features 623 | -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | pull_request: 4 | paths: Cargo.lock 5 | push: 6 | branches: develop 7 | paths: Cargo.lock 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | jobs: 12 | security_audit: 13 | name: Security Audit 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Cache cargo bin 18 | uses: actions/cache@v1 19 | with: 20 | path: ~/.cargo/bin 21 | key: ${{ runner.os }}-cargo-audit-v0.11.2 22 | - uses: actions-rs/audit-check@v1 23 | with: 24 | args: --ignore RUSTSEC-2019-0031 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.19.0 (2020-04-19) 2 | 3 | - Update `signature` crate requirement to v1.0.1 ([#14]) 4 | - Update `subtle-encoding` requirement to 0.5 ([#2]) 5 | 6 | [#14]: https://github.com/iqlusioninc/signatory/pull/14 7 | [#2]: https://github.com/iqlusioninc/signatory/pull/2 8 | 9 | ## 0.18.1 (2020-03-02) 10 | 11 | - Update links to point to new repository location 12 | - signatory-secp256k1: update `secp256k1` requirement from 0.15 to 0.17 13 | - signatory-secp256k1: support Ethereum's `Keccak256` hash function for signing 14 | 15 | ## 0.18.0 (2020-01-19) 16 | 17 | - Upgrade `ecdsa` crate to v0.4 18 | 19 | ## 0.17.1 (2019-12-16) 20 | 21 | - Fix Windows builds 22 | 23 | ## 0.17.0 (2019-12-11) 24 | 25 | - Use `=` expression to lock all prerelease deps to specific versions 26 | - Upgrade to `secp256k1` crate v0.17 27 | - Upgrade to `ecdsa` crate v0.3 28 | 29 | ## 0.16.0 (2019-10-29) 30 | 31 | - Use the `ecdsa` crate 32 | - Upgrade to zeroize 1.0 33 | - Upgrade to `signature` and `ed25519` crates v1.0.0-pre.1 34 | 35 | ## 0.15.0 (2019-10-11) 36 | 37 | - signatory-dalek: Upgrade `ed25519-dalek` to 1.0.0-pre.2 38 | - Upgrade to `signature` and `ed25519` crates v1.0.0-pre.0 39 | 40 | ## 0.14.0 (2019-10-10) 41 | 42 | - Always use the `alloc` crate for `String`/`Vec` 43 | - Upgrade to `signature` crate v0.3, `ed25519` crate v0.2; MSRV 1.36+ 44 | 45 | ## 0.13.0 (2019-08-11) 46 | 47 | - Update ring to v0.16; secp256k1 to v0.15 48 | - Remove toplevel `signature` re-exports; add `encoding::Error` 49 | - Use `ed25519::Signature` from the `ed25519` crate 50 | 51 | ## 0.12.0 (2019-06-07) 52 | 53 | - Use the `signature` crate 54 | 55 | ## 0.11.5 (2019-06-04) 56 | 57 | - Upgrade to `zeroize` 0.9 58 | 59 | ## 0.11.4 (2019-05-20) 60 | 61 | - Support stable `alloc` API 62 | - Upgrade to `zeroize` 0.8 63 | 64 | ## 0.11.3 (2019-03-13) 65 | 66 | - Fix Missing TrailingWhitespace type-case in subtle-encoding error conversion 67 | 68 | ## 0.11.2 (2019-03-09) 69 | 70 | - ecdsa: impl `PartialOrd` + `Ord` for PublicKeys 71 | - ecdsa: Simplify trait bounds for Copy impl on curve point types 72 | 73 | ## 0.11.1 (2019-02-23) 74 | 75 | - ecdsa: impl `Copy` + `Hash` for ECDSA curve points and public keys 76 | 77 | ## 0.11.0 (2019-02-12) 78 | 79 | - signatory-yubihsm: Update `yubihsm` crate to v0.20 80 | - signatory-dalek: Update `ed25519-dalek` crate to 1.0.0-pre.1 81 | - signatory-ring: Update `ring` crate to 0.14 82 | - signatory-sodiumoxide: Update `sodiumoxide` crate to 0.2 83 | - signatory-secp256k1: Update `secp256k1` crate to 0.12 84 | - Upgrade to Rust 2018 edition 85 | - signatory-ledger-cosval: Upgrade ledger provider to validator app 0.2.1 86 | 87 | ## 0.10.1 (2018-11-27) 88 | 89 | - Upgrade to `subtle-encoding` v0.3.0 90 | 91 | ## 0.10.0 (2018-10-16) 92 | 93 | - Upgrade to `digest` 0.8, `generic-array` 0.12, and `yubihsm` 0.19 94 | - Upgrade to `zeroize` 0.4 95 | 96 | ## 0.9.4 (2018-10-10) 97 | 98 | - pkcs8: Properly gate `FILE_MODE` on Windows 99 | 100 | ## 0.9.3 (2018-10-09) 101 | 102 | - Upgrade to `subtle-encoding` v0.2 103 | - Fix unused import on Windows (closes #121) 104 | 105 | ## 0.9.2 (2018-10-08) 106 | 107 | - More documentation fixups 108 | 109 | ## 0.9.1 (2018-10-08) 110 | 111 | - Cargo.toml: Fix docs.rs build 112 | 113 | ## 0.9.0 (2018-10-08) 114 | 115 | - Remove redundant "namespacing" from type names 116 | - Move `curve` module (back) under `ecdsa` 117 | - signatory-yubihsm: Upgrade to yubihsm 0.18 118 | - Use `subtle-encoding` crate for constant-time encoding/decoding 119 | - ECDSA `SecretKey` type and related traits (e.g. `GeneratePkcs8`) 120 | - Properly handle leading zeroes in ASN.1 serialization/parsing 121 | - signatory-yubihsm: Expose the yubihsm crate as a pub extern 122 | - encoding: Use 0o600 file mode on Unix 123 | - Eliminate `ed25519::FromSeed` trait 124 | - yubihsm: NIST P-384 support 125 | - ring: NIST P-384 support 126 | - Add NIST P-384 elliptic curve type (closes #73) 127 | - signatory-yubihsm: Fix ECDSA over secp256k1 signing (closes #87) 128 | - `signatory-ledger-cosval` provider 129 | - signatory-yubihsm: Normalize secp256k1 signatures to "low S" form 130 | - signatory-secp256k1: Bump secp256k1 crate dependency to 0.11 131 | - Unify verification API under the `Verifier` trait 132 | - encoding: Add encoding module with hex and Base64 support 133 | - Unify signing API under the `Signer` trait 134 | 135 | ## 0.8.0 (2018-08-19) 136 | 137 | - Extract `from_pkcs8` into a trait 138 | - signatory-yubihsm: Make ecdsa and ed25519 modules public 139 | 140 | ## 0.7.0 (2018-08-19) 141 | 142 | - Factor providers into their own `signatory-*` crates 143 | - Unify ECDSA traits across DER and fixed-sized signatures 144 | - ECDSA DER signature parsing and serialization 145 | 146 | ## 0.6.1 (2018-07-31) 147 | 148 | - Upgrade to `secp256k1` crate v0.10 149 | 150 | ## 0.6.0 (2018-07-31) 151 | 152 | - Factor ECDSA PublicKey into compressed/uncompressed curve points 153 | - ECDSA support for `yubihsm-provider` 154 | - Upgrade to `yubihsm` crate 0.14 155 | - Add rustdoc logo 156 | - Audit project for security vulnerabilities with `cargo-audit` 157 | - Update to `ed25519-dalek` 0.8 158 | - Add ECDSA NIST P-256 support with *ring* provider 159 | - Factor ECDSA traits apart into separate traits per method 160 | - Upgrade to `sodiumoxide` 0.1 161 | - Add `ed25519::Seed::from_keypair` method 162 | - No default features 163 | - Add `ed25519::Seed` type 164 | 165 | ## 0.5.2 (2018-05-19) 166 | 167 | - Update to `yubihsm-rs` 0.9 168 | - Fix benchmarks 169 | 170 | ## 0.5.1 (2018-04-13) 171 | 172 | - Mark all Signers and Verifiers as Send safe 173 | 174 | ## 0.5.0 (2018-04-12) 175 | 176 | - Upgrade to `yubihsm-rs` 0.8 177 | - ECDSA verification support 178 | - ECDSA support with secp256k1 provider 179 | - Ed25519 FromSeed trait and miscellaneous cleanups 180 | - Remove unnecessary direct dependency on `curve25519-dalek` 181 | 182 | ## 0.4.1 (2018-04-05) 183 | 184 | - Add more bounds to the `Verifier` trait 185 | 186 | ## 0.4.0 (2018-04-05) 187 | 188 | - Add an `ed25519` module to all providers 189 | - `sodiumoxide` provider for Ed25519 190 | - *ring* Ed25519 provider 191 | - `ed25519::Verifier` trait 192 | 193 | ## 0.3.2 (2018-03-31) 194 | 195 | - Upgrade `ed25519-dalek` to 0.6.2 196 | 197 | ## 0.3.1 (2018-03-27) 198 | 199 | - Update to `yubihsm-rs` 0.7 200 | 201 | ## 0.3.0 (2018-03-20) 202 | 203 | - Refactor providers + `yubihsm-rs` update + `Sync`-safe signers 204 | 205 | ## 0.2.0 (2018-03-13) 206 | 207 | - Add `ed25519::Signer::public_key()` 208 | 209 | ## 0.1.0 (2018-03-12) 210 | 211 | - Initial release 212 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory" 3 | description = "Multi-provider elliptic curve digital signature library with ECDSA and Ed25519 support" 4 | version = "0.19.0" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/signatory" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cryptography", "ecdsa", "ed25519", "signing", "signatures"] 12 | edition = "2018" 13 | autobenches = false 14 | 15 | [badges] 16 | maintenance = { status = "passively-maintained" } 17 | 18 | [dependencies] 19 | ecdsa = { version = "0.5", optional = true } 20 | ed25519 = { version = "1", optional = true, default-features = false } 21 | getrandom = { version = "0.1", optional = true, default-features = false } 22 | sha2 = { version = "0.8", optional = true, default-features = false } 23 | signature = { version = "1.0.1", default-features = false } 24 | zeroize = { version = "1", default-features = false } 25 | 26 | [dependencies.subtle-encoding] 27 | version = "0.5" 28 | optional = true 29 | default-features = false 30 | features = ["base64", "hex"] 31 | 32 | [features] 33 | alloc = [] 34 | default = ["encoding", "getrandom", "std"] 35 | digest = ["signature/digest-preview"] 36 | encoding = ["subtle-encoding"] 37 | k256 = ["ecdsa/k256"] 38 | p256 = ["ecdsa/p256"] 39 | p384 = ["ecdsa/p384"] 40 | pkcs8 = ["encoding"] 41 | std = ["alloc", "signature/std", "subtle-encoding/std"] 42 | test-vectors = ["ecdsa/test-vectors"] 43 | 44 | [workspace] 45 | members = [ 46 | "signatory-dalek", 47 | "signatory-ledger-tm", 48 | "signatory-ring", 49 | "signatory-secp256k1", 50 | "signatory-sodiumoxide", 51 | ] 52 | 53 | [profile.release] 54 | opt-level = 3 55 | debug = false 56 | rpath = false 57 | lto = false 58 | debug-assertions = false 59 | codegen-units = 1 60 | panic = "abort" 61 | 62 | [profile.bench] 63 | opt-level = 3 64 | debug = false 65 | rpath = false 66 | lto = false 67 | debug-assertions = false 68 | codegen-units = 1 69 | 70 | [package.metadata.docs.rs] 71 | all-features = true 72 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Signatory][logo] 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | ![Apache2/MIT licensed][license-image] 6 | ![MSRV][rustc-image] 7 | [![Build Status][build-image]][build-link] 8 | 9 | Pure Rust multi-provider digital signature library with support for elliptic 10 | curve digital signature algorithms, namely ECDSA (described in [FIPS 186‑4]) 11 | and Ed25519 (described in [RFC 8032]). 12 | 13 | Signatory provides a thread-safe and object-safe API and implements providers 14 | for many popular Rust crates, including [ed25519‑dalek], [*ring*], [secp256k1], 15 | and [sodiumoxide]. 16 | 17 | [Documentation][docs-link] 18 | 19 | ## About 20 | 21 | Signatory exposes a thread-and-object-safe API for creating digital signatures 22 | which allows several signature providers to be compiled-in and available with 23 | specific providers selected at runtime. 24 | 25 | ## Requirements 26 | 27 | All Signatory providers require Rust **1.37+** 28 | 29 | ## Provider Support 30 | 31 | Signatory includes the following providers, which are each packaged into their 32 | own respective crates (except for the [yubihsm] provider, which is included 33 | [directly in the yubihsm crate]). 34 | 35 | ### ECDSA providers 36 | 37 | | Provider Crate | Backend Crate | Type | P‑256 | P‑384 | secp256k1 | 38 | | --------------------- | -------------- | ---- | ------ | ------ | ---------- | 39 | | [signatory‑ring] | [*ring*] | Soft | ✅ | ✅ | ⛔ | 40 | | [signatory‑secp256k1] | [secp256k1] | Soft | ⛔ | ⛔ | ✅ | 41 | | [yubihsm] | [yubihsm] | Hard | ✅ | ✅ | ✅ | 42 | 43 | ### Ed25519 providers 44 | 45 | | Provider Crate | Backend Crate | Type | Signing | Verification | 46 | | ----------------------- | --------------- | ---- | ------- | ------------ | 47 | | [signatory‑dalek] | [ed25519‑dalek] | Soft | 51 k/s | 18 k/s | 48 | | [signatory‑ring] | [*ring*] | Soft | 47 k/s | 16 k/s | 49 | | [signatory‑sodiumoxide] | [sodiumoxide] | Soft | 38 k/s | 15 k/s | 50 | | [yubihsm] | [yubihsm] | Hard | ~8/s | N/A | 51 | 52 | ### Tendermint only providers (amino encoded consensus votes) 53 | 54 | | Provider Crate | Backend Crate | Type | Signing | Verification | 55 | | --------------------- | --------------- | ---- | ------- | ------------ | 56 | | [signatory‑ledger-tm] | [ledger-tendermint] | Hard | N/A | N/A | 57 | 58 | Above benchmarks performed using `cargo bench` on an Intel Xeon E3-1225 v5 @ 3.30GHz. 59 | 60 | ## License 61 | 62 | **Signatory** is distributed under the terms of either the MIT license or the 63 | Apache License (Version 2.0), at your option. 64 | 65 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 66 | 67 | [//]: # (badges) 68 | 69 | [logo]: https://storage.googleapis.com/iqlusion-production-web/github/signatory/signatory.svg 70 | [crate-image]: https://img.shields.io/crates/v/signatory.svg 71 | [crate-link]: https://crates.io/crates/signatory 72 | [docs-image]: https://docs.rs/signatory/badge.svg 73 | [docs-link]: https://docs.rs/signatory/ 74 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 75 | [rustc-image]: https://img.shields.io/badge/rustc-1.37+-blue.svg 76 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 77 | [build-link]: https://github.com/iqlusioninc/signatory/actions 78 | 79 | [//]: # (general links) 80 | 81 | [FIPS 186‑4]: https://csrc.nist.gov/publications/detail/fips/186/4/final 82 | [RFC 8032]: https://tools.ietf.org/html/rfc8032 83 | [ed25519‑dalek]: https://github.com/dalek-cryptography/ed25519-dalek 84 | [*ring*]: https://github.com/briansmith/ring 85 | [secp256k1]: https://github.com/rust-bitcoin/rust-secp256k1/ 86 | [sodiumoxide]: https://github.com/dnaq/sodiumoxide 87 | [yubihsm]: https://github.com/tendermint/yubihsm-rs 88 | [ledger-tendermint]: https://crates.io/crates/ledger-tendermint 89 | [directly in the yubihsm crate]: https://docs.rs/yubihsm/latest/yubihsm/signatory/index.html 90 | [signatory‑dalek]: https://crates.io/crates/signatory-dalek 91 | [signatory‑ring]: https://crates.io/crates/signatory-ring 92 | [signatory‑secp256k1]: https://crates.io/crates/signatory-secp256k1 93 | [signatory‑sodiumoxide]: https://crates.io/crates/signatory-sodiumoxide 94 | [signatory‑ledger-tm]: https://crates.io/crates/signatory-ledger-tm 95 | -------------------------------------------------------------------------------- /img/signatory-rustacean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tendermint/signatory/08dbc95165889499036956f4d8537dc4540c51d3/img/signatory-rustacean.png -------------------------------------------------------------------------------- /img/signatory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tendermint/signatory/08dbc95165889499036956f4d8537dc4540c51d3/img/signatory.png -------------------------------------------------------------------------------- /signatory-dalek/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory-dalek" 3 | description = "Signatory Ed25519 provider for ed25519-dalek" 4 | version = "0.19.0" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/signatory" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop/providers/signatory-dalek/" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cryptography", "dalek", "ed25519", "signing", "signatures"] 12 | edition = "2018" 13 | 14 | [badges] 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | digest = { version = "0.8", default-features = false } 19 | ed25519-dalek = { version = "= 1.0.0-pre.2", default-features = false } 20 | sha2 = { version = "0.8", default-features = false } 21 | 22 | [dependencies.signatory] 23 | version = "0.19" 24 | default-features = false 25 | features = ["digest", "ed25519"] 26 | path = ".." 27 | 28 | [dev-dependencies] 29 | criterion = "0.3" 30 | 31 | [dev-dependencies.signatory] 32 | version = "0.19" 33 | default-features = false 34 | features = ["digest", "ed25519", "test-vectors"] 35 | path = ".." 36 | 37 | [features] 38 | default = ["u64_backend"] 39 | avx2_backend = ["ed25519-dalek/avx2_backend"] 40 | u32_backend = ["ed25519-dalek/u32_backend"] 41 | u64_backend = ["ed25519-dalek/u64_backend"] 42 | nightly = ["ed25519-dalek/nightly"] 43 | 44 | [[bench]] 45 | name = "ed25519" 46 | harness = false 47 | -------------------------------------------------------------------------------- /signatory-dalek/README.md: -------------------------------------------------------------------------------- 1 | # signatory-dalek [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![MIT/Apache2 licensed][license-image] 2 | 3 | [Signatory] Ed25519 ([RFC 8032]) provider for [ed25519-dalek]. 4 | 5 | [Documentation](https://docs.rs/signatory-dalek/) 6 | 7 | [Signatory]: https://github.com/iqlusioninc/signatory 8 | [RFC 8032]: https://tools.ietf.org/html/rfc8032 9 | [ed25519-dalek]: https://github.com/dalek-cryptography/ed25519-dalek 10 | 11 | ## License 12 | 13 | **Signatory** is distributed under the terms of either the MIT license or the 14 | Apache License (Version 2.0), at your option. 15 | 16 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 17 | 18 | [crate-image]: https://img.shields.io/crates/v/signatory-dalek.svg 19 | [crate-link]: https://crates.io/crates/signatory-dalek 20 | [docs-image]: https://docs.rs/signatory-dalek/badge.svg 21 | [docs-link]: https://docs.rs/signatory-dalek/ 22 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 23 | [build-link]: https://github.com/iqlusioninc/signatory/actions 24 | [license-image]: https://img.shields.io/badge/license-MIT/Apache2.0-blue.svg 25 | -------------------------------------------------------------------------------- /signatory-dalek/benches/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! ed25519-dalek provider benchmarks 2 | 3 | #![allow(unused_imports)] 4 | #![deny(warnings)] 5 | 6 | #[macro_use] 7 | extern crate criterion; 8 | 9 | use criterion::Criterion; 10 | use signatory::{ 11 | ed25519, 12 | signature::{Signature, Signer, Verifier}, 13 | test_vector::TestVector, 14 | }; 15 | use signatory_dalek::{Ed25519Signer, Ed25519Verifier}; 16 | 17 | /// Test vector to use for benchmarking 18 | const TEST_VECTOR: &TestVector = &ed25519::TEST_VECTORS[4]; 19 | 20 | fn sign_ed25519(c: &mut Criterion) { 21 | let signer = Ed25519Signer::from(&ed25519::Seed::from_bytes(TEST_VECTOR.sk).unwrap()); 22 | 23 | c.bench_function("dalek: Ed25519 signer", move |b| { 24 | b.iter(|| signer.sign(TEST_VECTOR.msg)) 25 | }); 26 | } 27 | 28 | fn verify_ed25519(c: &mut Criterion) { 29 | let verifier = Ed25519Verifier::from(&ed25519::PublicKey::from_bytes(TEST_VECTOR.pk).unwrap()); 30 | let signature = ed25519::Signature::from_bytes(TEST_VECTOR.sig).unwrap(); 31 | 32 | c.bench_function("dalek: Ed25519 verifier", move |b| { 33 | b.iter(|| verifier.verify(TEST_VECTOR.msg, &signature).unwrap()) 34 | }); 35 | } 36 | 37 | criterion_group! { 38 | name = ed25519; 39 | config = Criterion::default(); 40 | targets = sign_ed25519, verify_ed25519 41 | } 42 | 43 | criterion_main!(ed25519); 44 | -------------------------------------------------------------------------------- /signatory-dalek/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Signatory Ed25519 provider for the [ed25519-dalek] crate. 2 | //! 3 | //! For a usage example, see the toplevel Signatory docs: 4 | //! 5 | //! 6 | //! [ed25519-dalek]: https://github.com/dalek-cryptography/ed25519-dalek 7 | 8 | #![no_std] 9 | #![forbid(unsafe_code)] 10 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 11 | #![doc( 12 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 13 | html_root_url = "https://docs.rs/signatory-dalek/0.19.0" 14 | )] 15 | 16 | #[cfg(test)] 17 | #[macro_use] 18 | extern crate signatory; 19 | 20 | use sha2::Sha512; 21 | use signatory::{ 22 | ed25519, 23 | public_key::PublicKeyed, 24 | signature::{DigestSigner, DigestVerifier, Error, Signature, Signer, Verifier}, 25 | }; 26 | 27 | /// Ed25519 signature provider for ed25519-dalek 28 | pub struct Ed25519Signer(ed25519_dalek::Keypair); 29 | 30 | impl<'a> From<&'a ed25519::Seed> for Ed25519Signer { 31 | /// Create a new DalekSigner from an unexpanded seed value 32 | fn from(seed: &'a ed25519::Seed) -> Self { 33 | Ed25519Signer(keypair_from_seed(seed)) 34 | } 35 | } 36 | 37 | impl PublicKeyed for Ed25519Signer { 38 | fn public_key(&self) -> Result { 39 | Ok(ed25519::PublicKey::from_bytes(self.0.public.as_bytes()).unwrap()) 40 | } 41 | } 42 | 43 | impl Signer for Ed25519Signer { 44 | fn try_sign(&self, msg: &[u8]) -> Result { 45 | let signature = self.0.sign(msg).to_bytes(); 46 | Ok(ed25519::Signature::from_bytes(&signature[..]).unwrap()) 47 | } 48 | } 49 | 50 | // TODO: tests! 51 | impl DigestSigner for Ed25519Signer { 52 | fn try_sign_digest(&self, digest: Sha512) -> Result { 53 | // TODO: context support 54 | let context: Option<&'static [u8]> = None; 55 | 56 | let signature = 57 | ed25519::Signature::from_bytes(&self.0.sign_prehashed(digest, context).to_bytes()[..]) 58 | .unwrap(); 59 | 60 | Ok(signature) 61 | } 62 | } 63 | 64 | /// Ed25519 verifier provider for ed25519-dalek 65 | #[derive(Clone, Debug, Eq, PartialEq)] 66 | pub struct Ed25519Verifier(ed25519_dalek::PublicKey); 67 | 68 | impl<'a> From<&'a ed25519::PublicKey> for Ed25519Verifier { 69 | fn from(public_key: &'a ed25519::PublicKey) -> Self { 70 | Ed25519Verifier(ed25519_dalek::PublicKey::from_bytes(public_key.as_ref()).unwrap()) 71 | } 72 | } 73 | 74 | impl Verifier for Ed25519Verifier { 75 | fn verify(&self, msg: &[u8], sig: &ed25519::Signature) -> Result<(), Error> { 76 | let dalek_sig = ed25519_dalek::Signature::from_bytes(sig.as_ref()).unwrap(); 77 | self.0.verify(msg, &dalek_sig).map_err(|_| Error::new()) 78 | } 79 | } 80 | 81 | // TODO: tests! 82 | impl DigestVerifier for Ed25519Verifier { 83 | fn verify_digest(&self, digest: Sha512, sig: &ed25519::Signature) -> Result<(), Error> { 84 | // TODO: context support 85 | let context: Option<&'static [u8]> = None; 86 | let dalek_sig = ed25519_dalek::Signature::from_bytes(sig.as_ref()).unwrap(); 87 | self.0 88 | .verify_prehashed(digest, context, &dalek_sig) 89 | .map_err(|_| Error::new()) 90 | } 91 | } 92 | 93 | /// Convert a Signatory seed into a Dalek keypair 94 | fn keypair_from_seed(seed: &ed25519::Seed) -> ed25519_dalek::Keypair { 95 | let secret = ed25519_dalek::SecretKey::from_bytes(seed.as_secret_slice()).unwrap(); 96 | let public = ed25519_dalek::PublicKey::from(&secret); 97 | ed25519_dalek::Keypair { secret, public } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::{Ed25519Signer, Ed25519Verifier}; 103 | ed25519_tests!(Ed25519Signer, Ed25519Verifier); 104 | } 105 | -------------------------------------------------------------------------------- /signatory-ledger-tm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory-ledger-tm" 3 | description = "Signatory provider for Ledger Tendermint Validator app" 4 | version = "0.19.1" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["ZondaX GmbH "] 7 | homepage = "https://github.com/ZondaX/ledger-tendermint-rs" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop/providers/signatory-ledger-tm/" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cosmos", "ed25519", "signatures", "tendermint", "validator"] 12 | edition = "2018" 13 | 14 | [badges] 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | byteorder = "1.3.1" 19 | ledger = "0.2.5" 20 | matches = "0.1.8" 21 | thiserror = "1" 22 | 23 | [dependencies.signatory] 24 | version = "0.19" 25 | features = ["digest", "ed25519"] 26 | path = ".." 27 | 28 | [dev-dependencies] 29 | ed25519-dalek = "1.0.0-pre.2" 30 | lazy_static = "1.2.0" 31 | sha2 = "0.8.0" 32 | criterion = "0.3" 33 | 34 | [[bench]] 35 | name = "ed25519" 36 | harness = false 37 | -------------------------------------------------------------------------------- /signatory-ledger-tm/README.md: -------------------------------------------------------------------------------- 1 | # signatory-ledger-tm [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![MIT/Apache2 licensed][license-image] 2 | 3 | [Signatory] provider for Ledger Tendermint Validator app. 4 | 5 | [Documentation](https://docs.rs/signatory/) 6 | 7 | [Signatory]: https://github.com/iqlusioninc/signatory 8 | 9 | ## License 10 | 11 | **Signatory** is distributed under the terms of either the MIT license or the 12 | Apache License (Version 2.0), at your option. 13 | 14 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 15 | 16 | [crate-image]: https://img.shields.io/crates/v/signatory-ledger-tm.svg 17 | [crate-link]: https://crates.io/crates/signatory-ledger-tm 18 | [docs-image]: https://docs.rs/signatory-ledger-tm/badge.svg 19 | [docs-link]: https://docs.rs/signatory-ledger-tm/ 20 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 21 | [build-link]: https://github.com/iqlusioninc/signatory/actions 22 | [license-image]: https://img.shields.io/badge/license-MIT/Apache2.0-blue.svg 23 | -------------------------------------------------------------------------------- /signatory-ledger-tm/benches/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! ed25519-ledger-tm provider benchmarks 2 | 3 | #![allow(unused_imports)] 4 | #![deny(warnings)] 5 | 6 | #[macro_use] 7 | extern crate criterion; 8 | use signatory; 9 | 10 | use criterion::Criterion; 11 | use signatory::{ 12 | ed25519, 13 | public_key::PublicKeyed, 14 | signature::{Signature, Verifier}, 15 | }; 16 | use signatory_ledger_tm::Ed25519LedgerTmAppSigner; 17 | 18 | fn pubkey_ed25519(c: &mut Criterion) { 19 | let signer = Ed25519LedgerTmAppSigner::connect().unwrap(); 20 | 21 | c.bench_function("ledger-tm: Ed25519 get public key", move |b| { 22 | b.iter(|| signer.public_key().unwrap()) 23 | }); 24 | } 25 | 26 | criterion_group! { 27 | name = ed25519; 28 | config = Criterion::default(); 29 | targets = pubkey_ed25519 30 | } 31 | 32 | criterion_main!(ed25519); 33 | -------------------------------------------------------------------------------- /signatory-ledger-tm/src/ledgertm.rs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * (c) 2018, 2019 ZondaX GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 17 | use ledger::{ApduAnswer, ApduCommand}; 18 | use thiserror::Error; 19 | 20 | const CLA: u8 = 0x56; 21 | const INS_PUBLIC_KEY_ED25519: u8 = 0x01; 22 | const INS_SIGN_ED25519: u8 = 0x02; 23 | 24 | const USER_MESSAGE_CHUNK_SIZE: usize = 250; 25 | 26 | #[allow(dead_code)] 27 | const INS_GET_VERSION: u8 = 0x00; 28 | 29 | #[derive(Debug, Error)] 30 | pub enum Error { 31 | #[error("This version is not supported")] 32 | InvalidVersion, 33 | 34 | #[error("message cannot be empty")] 35 | InvalidEmptyMessage, 36 | 37 | #[error("message size is invalid (too big)")] 38 | InvalidMessageSize, 39 | 40 | #[error("received an invalid PK")] 41 | InvalidPK, 42 | 43 | #[error("received no signature back")] 44 | NoSignature, 45 | 46 | #[error("received an invalid signature")] 47 | InvalidSignature, 48 | 49 | #[error("ledger error")] 50 | Ledger(ledger::Error), 51 | } 52 | 53 | impl From for Error { 54 | fn from(err: ledger::Error) -> Error { 55 | Error::Ledger(err) 56 | } 57 | } 58 | 59 | pub struct TendermintValidatorApp { 60 | app: ledger::LedgerApp, 61 | } 62 | 63 | #[allow(unsafe_code)] 64 | unsafe impl Send for TendermintValidatorApp {} 65 | 66 | #[allow(dead_code)] 67 | pub struct Version { 68 | mode: u8, 69 | major: u8, 70 | minor: u8, 71 | patch: u8, 72 | } 73 | 74 | impl TendermintValidatorApp { 75 | pub fn connect() -> Result { 76 | let app = ledger::LedgerApp::new()?; 77 | Ok(TendermintValidatorApp { app }) 78 | } 79 | 80 | #[allow(dead_code)] 81 | pub fn version(&self) -> Result { 82 | let command = ApduCommand { 83 | cla: CLA, 84 | ins: INS_GET_VERSION, 85 | p1: 0x00, 86 | p2: 0x00, 87 | length: 0, 88 | data: Vec::new(), 89 | }; 90 | 91 | let response = self.app.exchange(command)?; 92 | 93 | // TODO: this is just temporary, ledger errors should check for 0x9000 94 | if response.retcode != 0x9000 { 95 | return Err(Error::InvalidVersion); 96 | } 97 | 98 | let version = Version { 99 | mode: response.data[0], 100 | major: response.data[1], 101 | minor: response.data[2], 102 | patch: response.data[3], 103 | }; 104 | 105 | Ok(version) 106 | } 107 | 108 | pub fn public_key(&self) -> Result<[u8; 32], Error> { 109 | let command = ApduCommand { 110 | cla: CLA, 111 | ins: INS_PUBLIC_KEY_ED25519, 112 | p1: 0x00, 113 | p2: 0x00, 114 | length: 0, 115 | data: Vec::new(), 116 | }; 117 | 118 | match self.app.exchange(command) { 119 | Ok(response) => { 120 | if response.retcode != 0x9000 { 121 | println!("WARNING: retcode={:X?}", response.retcode); 122 | } 123 | 124 | if response.data.len() != 32 { 125 | return Err(Error::InvalidPK); 126 | } 127 | 128 | let mut array = [0u8; 32]; 129 | array.copy_from_slice(&response.data[..32]); 130 | Ok(array) 131 | } 132 | Err(err) => { 133 | // TODO: Friendly error 134 | Err(Error::Ledger(err)) 135 | } 136 | } 137 | } 138 | 139 | // Sign message 140 | pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], Error> { 141 | let chunks = message.chunks(USER_MESSAGE_CHUNK_SIZE); 142 | 143 | if chunks.len() > 255 { 144 | return Err(Error::InvalidMessageSize); 145 | } 146 | 147 | if chunks.len() == 0 { 148 | return Err(Error::InvalidEmptyMessage); 149 | } 150 | 151 | let packet_count = chunks.len() as u8; 152 | let mut response: ApduAnswer = ApduAnswer { 153 | data: vec![], 154 | retcode: 0, 155 | }; 156 | 157 | // Send message chunks 158 | for (packet_idx, chunk) in chunks.enumerate() { 159 | let _command = ApduCommand { 160 | cla: CLA, 161 | ins: INS_SIGN_ED25519, 162 | p1: (packet_idx + 1) as u8, 163 | p2: packet_count, 164 | length: chunk.len() as u8, 165 | data: chunk.to_vec(), 166 | }; 167 | 168 | response = self.app.exchange(_command)?; 169 | } 170 | 171 | if response.data.is_empty() && response.retcode == 0x9000 { 172 | return Err(Error::NoSignature); 173 | } 174 | 175 | // Last response should contain the answer 176 | if response.data.len() != 64 { 177 | return Err(Error::InvalidSignature); 178 | } 179 | 180 | let mut array = [0u8; 64]; 181 | array.copy_from_slice(&response.data[..64]); 182 | Ok(array) 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use lazy_static::lazy_static; 189 | use std::sync::Mutex; 190 | use std::time::Instant; 191 | 192 | use crate::ledgertm::{Error, TendermintValidatorApp}; 193 | 194 | lazy_static! { 195 | static ref APP: Mutex = 196 | Mutex::new(TendermintValidatorApp::connect().unwrap()); 197 | } 198 | 199 | fn get_fake_proposal(index: u64, round: i64) -> Vec { 200 | use byteorder::{LittleEndian, WriteBytesExt}; 201 | let other: [u8; 12] = [ 202 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 203 | ]; 204 | 205 | let mut message = Vec::new(); 206 | message.write_u8(0).unwrap(); // (field_number << 3) | wire_type 207 | 208 | message.write_u8(0x08).unwrap(); // (field_number << 3) | wire_type 209 | message.write_u8(0x01).unwrap(); // PrevoteType 210 | 211 | message.write_u8(0x11).unwrap(); // (field_number << 3) | wire_type 212 | message.write_u64::(index).unwrap(); 213 | 214 | message.write_u8(0x19).unwrap(); // (field_number << 3) | wire_type 215 | message.write_i64::(round).unwrap(); 216 | 217 | // remaining fields (timestamp, not checked): 218 | message.write_u8(0x22).unwrap(); // (field_number << 3) | wire_type 219 | message.extend_from_slice(&other); 220 | 221 | // Increase index 222 | message[0] = message.len() as u8 - 1; 223 | message 224 | } 225 | 226 | #[test] 227 | fn version() { 228 | let app = APP.lock().unwrap(); 229 | 230 | let resp = app.version(); 231 | 232 | match resp { 233 | Ok(version) => { 234 | println!("mode {}", version.mode); 235 | println!("major {}", version.major); 236 | println!("minor {}", version.minor); 237 | println!("patch {}", version.patch); 238 | 239 | assert_eq!(version.mode, 0xFF); 240 | assert_eq!(version.major, 0x00); 241 | assert!(version.minor >= 0x04); 242 | } 243 | Err(err) => { 244 | eprintln!("Error: {:?}", err); 245 | } 246 | } 247 | } 248 | 249 | #[test] 250 | fn public_key() { 251 | let app = APP.lock().unwrap(); 252 | let resp = app.public_key(); 253 | 254 | match resp { 255 | Ok(pk) => { 256 | assert_eq!(pk.len(), 32); 257 | println!("PK {:0X?}", pk); 258 | } 259 | Err(err) => { 260 | eprintln!("Error: {:?}", err); 261 | panic!() 262 | } 263 | } 264 | } 265 | 266 | #[test] 267 | fn sign_empty() { 268 | let app = APP.lock().unwrap(); 269 | 270 | let some_message0 = b""; 271 | 272 | let signature = app.sign(some_message0); 273 | assert!(signature.is_err()); 274 | assert!(matches!( 275 | signature.err().unwrap(), 276 | Error::InvalidEmptyMessage 277 | )); 278 | } 279 | 280 | #[test] 281 | fn sign_verify() { 282 | let app = APP.lock().unwrap(); 283 | 284 | let some_message1 = get_fake_proposal(5, 0); 285 | app.sign(&some_message1).unwrap(); 286 | 287 | let some_message2 = get_fake_proposal(6, 0); 288 | match app.sign(&some_message2) { 289 | Ok(sig) => { 290 | use ed25519_dalek::PublicKey; 291 | use ed25519_dalek::Signature; 292 | 293 | println!("{:#?}", sig.to_vec()); 294 | 295 | // First, get public key 296 | let public_key_bytes = app.public_key().unwrap(); 297 | let public_key = PublicKey::from_bytes(&public_key_bytes).unwrap(); 298 | let signature = Signature::from_bytes(&sig).unwrap(); 299 | 300 | // Verify signature 301 | assert!(public_key.verify(&some_message2, &signature).is_ok()); 302 | } 303 | Err(e) => { 304 | println!("Err {:#?}", e); 305 | panic!(); 306 | } 307 | } 308 | } 309 | 310 | #[test] 311 | fn sign_many() { 312 | let app = APP.lock().unwrap(); 313 | 314 | // First, get public key 315 | let _resp = app.public_key().unwrap(); 316 | 317 | // Now send several votes 318 | for index in 50u8..254u8 { 319 | let some_message1 = [ 320 | 0x8, // (field_number << 3) | wire_type 321 | 0x1, // PrevoteType 322 | 0x11, // (field_number << 3) | wire_type 323 | index, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 324 | 0x19, // (field_number << 3) | wire_type 325 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 326 | 0x22, // (field_number << 3) | wire_type 327 | // remaining fields (timestamp): 328 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 329 | ]; 330 | 331 | let signature = app.sign(&some_message1); 332 | match signature { 333 | Ok(sig) => { 334 | println!("{:#?}", sig.to_vec()); 335 | } 336 | Err(e) => { 337 | println!("Err {:#?}", e); 338 | panic!(); 339 | } 340 | } 341 | } 342 | } 343 | 344 | #[test] 345 | fn quick_benchmark() { 346 | let app = APP.lock().unwrap(); 347 | 348 | // initialize app with a vote 349 | let msg = get_fake_proposal(0, 100); 350 | app.sign(&msg).unwrap(); 351 | 352 | let start = Instant::now(); 353 | // Now send several votes 354 | for i in 1u64..20u64 { 355 | app.sign(&get_fake_proposal(i, 100)).unwrap(); 356 | } 357 | let duration = start.elapsed(); 358 | println!("Elapsed {:?}", duration); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /signatory-ledger-tm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ledger-tm provider: Ledger Tendermint Validator app (Ed25519 signatures for Amino votes) 2 | 3 | #![doc( 4 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 5 | html_root_url = "https://docs.rs/signatory-ledger-tm/0.19.1" 6 | )] 7 | #![deny(unsafe_code)] 8 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 9 | 10 | mod ledgertm; 11 | mod signer; 12 | 13 | pub use crate::signer::Ed25519LedgerTmAppSigner; 14 | -------------------------------------------------------------------------------- /signatory-ledger-tm/src/signer.rs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * (c) 2018, 2019 ZondaX GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 17 | use crate::ledgertm::TendermintValidatorApp; 18 | use signatory::{ 19 | ed25519::{PublicKey, Signature}, 20 | public_key::PublicKeyed, 21 | signature::{Error, Signer}, 22 | }; 23 | use std::sync::{Arc, Mutex}; 24 | 25 | /// ed25519 signature provider for the Ledger Tendermint Validator app 26 | pub struct Ed25519LedgerTmAppSigner { 27 | app: Arc>, 28 | } 29 | 30 | impl Ed25519LedgerTmAppSigner { 31 | /// Create a new Ed25519 signer based on Ledger Nano S - Tendermint Validator app 32 | pub fn connect() -> Result { 33 | let validator_app = TendermintValidatorApp::connect().map_err(Error::from_source)?; 34 | let app = Arc::new(Mutex::new(validator_app)); 35 | let signer = Ed25519LedgerTmAppSigner { app }; 36 | let _pk = signer.public_key().unwrap(); 37 | Ok(signer) 38 | } 39 | } 40 | 41 | impl PublicKeyed for Ed25519LedgerTmAppSigner { 42 | /// Returns the public key that corresponds to the Tendermint Validator app connected to this signer 43 | fn public_key(&self) -> Result { 44 | let app = self.app.lock().unwrap(); 45 | let pk = app.public_key().map_err(Error::from_source)?; 46 | Ok(PublicKey(pk)) 47 | } 48 | } 49 | 50 | impl Signer for Ed25519LedgerTmAppSigner { 51 | /// c: Compute a compact, fixed-sized signature of the given amino/json vote 52 | fn try_sign(&self, msg: &[u8]) -> Result { 53 | let app = self.app.lock().unwrap(); 54 | let sig = app.sign(&msg).map_err(Error::from_source)?; 55 | Ok(Signature::from(sig)) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use crate::Ed25519LedgerTmAppSigner; 62 | 63 | #[test] 64 | fn public_key() { 65 | use signatory::public_key::PublicKeyed; 66 | let signer = Ed25519LedgerTmAppSigner::connect().unwrap(); 67 | 68 | let _pk = signer.public_key().unwrap(); 69 | println!("PK {:0X?}", _pk); 70 | } 71 | 72 | #[test] 73 | fn sign() { 74 | use crate::Ed25519LedgerTmAppSigner; 75 | use signatory::signature::Signer; 76 | 77 | let signer = Ed25519LedgerTmAppSigner::connect().unwrap(); 78 | 79 | // Sign message1 80 | let some_message1 = [ 81 | 33, 0x8, // (field_number << 3) | wire_type 82 | 0x1, // PrevoteType 83 | 0x11, // (field_number << 3) | wire_type 84 | 0x10, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 85 | 0x19, // (field_number << 3) | wire_type 86 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 87 | 0x22, // (field_number << 3) | wire_type 88 | // remaining fields (timestamp): 89 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 90 | ]; 91 | 92 | signer.sign(&some_message1); 93 | } 94 | 95 | #[test] 96 | fn sign2() { 97 | use signatory::signature::Signer; 98 | 99 | let signer = Ed25519LedgerTmAppSigner::connect().unwrap(); 100 | 101 | // Sign message1 102 | let some_message1 = [ 103 | 33, 0x8, // (field_number << 3) | wire_type 104 | 0x1, // PrevoteType 105 | 0x11, // (field_number << 3) | wire_type 106 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 107 | 0x19, // (field_number << 3) | wire_type 108 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 109 | 0x22, // (field_number << 3) | wire_type 110 | // remaining fields (timestamp): 111 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 112 | ]; 113 | 114 | signer.sign(&some_message1); 115 | 116 | // Sign message2 117 | let some_message2 = [ 118 | 33, 0x8, // (field_number << 3) | wire_type 119 | 0x1, // PrevoteType 120 | 0x11, // (field_number << 3) | wire_type 121 | 0x10, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 122 | 0x19, // (field_number << 3) | wire_type 123 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 124 | 0x22, // (field_number << 3) | wire_type 125 | // remaining fields (timestamp): 126 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 127 | ]; 128 | 129 | signer.sign(&some_message2); 130 | } 131 | 132 | #[test] 133 | fn sign_many() { 134 | use signatory::public_key::PublicKeyed; 135 | use signatory::signature::Signer; 136 | use Ed25519LedgerTmAppSigner; 137 | 138 | let signer = Ed25519LedgerTmAppSigner::connect().unwrap(); 139 | 140 | // Get public key to initialize 141 | let _pk = signer.public_key().unwrap(); 142 | println!("PK {:0X?}", _pk); 143 | 144 | for index in 50u8..254u8 { 145 | // Sign message1 146 | let some_message = [ 147 | 33, 0x8, // (field_number << 3) | wire_type 148 | 0x1, // PrevoteType 149 | 0x11, // (field_number << 3) | wire_type 150 | 0x40, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 151 | 0x19, // (field_number << 3) | wire_type 152 | index, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 153 | 0x22, // (field_number << 3) | wire_type 154 | // remaining fields (timestamp): 155 | 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, 156 | ]; 157 | 158 | signer.sign(&some_message); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /signatory-ring/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory-ring" 3 | description = "Signatory ECDSA (NIST P-256) and Ed25519 provider for *ring*" 4 | version = "0.19.0" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/signatory" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop/providers/signatory-ring/" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cryptography", "ecdsa", "ed25519", "ring", "signatures"] 12 | edition = "2018" 13 | 14 | [badges] 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | ring = { version = "0.16", default-features = false } 19 | 20 | [dependencies.signatory] 21 | version = "0.19" 22 | default-features = false 23 | features = ["pkcs8"] 24 | path = ".." 25 | 26 | [dev-dependencies] 27 | criterion = "0.3" 28 | 29 | [dev-dependencies.signatory] 30 | version = "0.19" 31 | default-features = false 32 | features = ["pkcs8", "test-vectors"] 33 | path = ".." 34 | 35 | [features] 36 | default = ["ecdsa", "ed25519", "std"] 37 | ecdsa = ["signatory/ecdsa", "signatory/p256", "signatory/p384"] 38 | ed25519 = ["signatory/ed25519"] 39 | std = ["signatory/std"] 40 | 41 | [[bench]] 42 | name = "ecdsa" 43 | harness = false 44 | 45 | [[bench]] 46 | name = "ed25519" 47 | harness = false 48 | -------------------------------------------------------------------------------- /signatory-ring/README.md: -------------------------------------------------------------------------------- 1 | # signatory-ring [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![MIT/Apache2 licensed][license-image] 2 | 3 | [Signatory] ECDSA ([FIPS 186-4]) and Ed25519 ([RFC 8032]) provider for [*ring*]. 4 | 5 | [Documentation](https://docs.rs/signatory-ring/) 6 | 7 | [Signatory]: https://github.com/iqlusioninc/signatory 8 | [FIPS 186-4]: https://csrc.nist.gov/publications/detail/fips/186/4/final 9 | [RFC 8032]: https://tools.ietf.org/html/rfc8032 10 | [*ring*]: https://github.com/briansmith/ring 11 | 12 | ## License 13 | 14 | **Signatory** is distributed under the terms of either the MIT license or the 15 | Apache License (Version 2.0), at your option. 16 | 17 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 18 | 19 | [crate-image]: https://img.shields.io/crates/v/signatory-ring.svg 20 | [crate-link]: https://crates.io/crates/signatory-ring 21 | [docs-image]: https://docs.rs/signatory-ring/badge.svg 22 | [docs-link]: https://docs.rs/signatory-ring/ 23 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 24 | [build-link]: https://github.com/iqlusioninc/signatory/actions 25 | [license-image]: https://img.shields.io/badge/license-MIT/Apache2.0-blue.svg 26 | -------------------------------------------------------------------------------- /signatory-ring/benches/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! *ring* ECDSA benchmarks 2 | 3 | #![deny(warnings)] 4 | 5 | #[macro_use] 6 | extern crate criterion; 7 | use signatory; 8 | 9 | use criterion::Criterion; 10 | use signatory::{ 11 | ecdsa::{ 12 | curve::nistp256::FixedSignature, 13 | generic_array::GenericArray, 14 | test_vectors::{nistp256::SHA256_FIXED_SIZE_TEST_VECTORS, TestVector}, 15 | PublicKey, 16 | }, 17 | encoding::FromPkcs8, 18 | signature::{Signature, Signer as _, Verifier as _}, 19 | test_vector::{TestVectorAlgorithm, ToPkcs8}, 20 | }; 21 | use signatory_ring::ecdsa::p256::{Signer, Verifier}; 22 | 23 | /// Test vector to use for benchmarking 24 | const TEST_VECTOR: &TestVector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 25 | 26 | fn sign_ecdsa_p256(c: &mut Criterion) { 27 | let signer = Signer::from_pkcs8(&TEST_VECTOR.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 28 | 29 | c.bench_function("ring: ECDSA (nistp256) signer", move |b| { 30 | b.iter(|| { 31 | let _: FixedSignature = signer.sign(TEST_VECTOR.msg); 32 | }) 33 | }); 34 | } 35 | 36 | fn verify_ecdsa_p256(c: &mut Criterion) { 37 | let signature = FixedSignature::from_bytes(TEST_VECTOR.sig).unwrap(); 38 | let verifier = Verifier::from(&PublicKey::from_untagged_point(GenericArray::from_slice( 39 | TEST_VECTOR.pk, 40 | ))); 41 | 42 | c.bench_function("ring: ECDSA (nistp256) verifier", move |b| { 43 | b.iter(|| { 44 | verifier.verify(TEST_VECTOR.msg, &signature).unwrap(); 45 | }) 46 | }); 47 | } 48 | 49 | criterion_group! { 50 | name = ecdsa_p256; 51 | config = Criterion::default(); 52 | targets = sign_ecdsa_p256, verify_ecdsa_p256 53 | } 54 | 55 | criterion_main!(ecdsa_p256); 56 | -------------------------------------------------------------------------------- /signatory-ring/benches/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! *ring* Ed25519 benchmarks 2 | 3 | #![deny(warnings)] 4 | 5 | #[macro_use] 6 | extern crate criterion; 7 | use signatory; 8 | 9 | use criterion::Criterion; 10 | use signatory::{ 11 | ed25519::TEST_VECTORS, 12 | signature::{Signature, Signer, Verifier}, 13 | test_vector::TestVector, 14 | }; 15 | use signatory_ring::ed25519; 16 | 17 | /// Test vector to use for benchmarking 18 | const TEST_VECTOR: &TestVector = &TEST_VECTORS[4]; 19 | 20 | fn sign_ed25519(c: &mut Criterion) { 21 | let signer = ed25519::Signer::from(&ed25519::Seed::from_bytes(TEST_VECTOR.sk).unwrap()); 22 | 23 | c.bench_function("ring: Ed25519 signer", move |b| { 24 | b.iter(|| signer.sign(TEST_VECTOR.msg)) 25 | }); 26 | } 27 | 28 | fn verify_ed25519(c: &mut Criterion) { 29 | let pk = ed25519::PublicKey::from_bytes(TEST_VECTOR.pk).unwrap(); 30 | let sig = ed25519::Signature::from_bytes(TEST_VECTOR.sig).unwrap(); 31 | let verifier = ed25519::Verifier::from(&pk); 32 | 33 | c.bench_function("ring: Ed25519 verifier", move |b| { 34 | b.iter(|| verifier.verify(TEST_VECTOR.msg, &sig).unwrap()) 35 | }); 36 | } 37 | 38 | criterion_group! { 39 | name = ed25519; 40 | config = Criterion::default(); 41 | targets = sign_ed25519, verify_ed25519 42 | } 43 | 44 | criterion_main!(ed25519); 45 | -------------------------------------------------------------------------------- /signatory-ring/src/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA provider for the *ring* crate (supporting NIST P-256/P-384) 2 | 3 | pub mod p256; 4 | pub mod p384; 5 | mod signer; 6 | -------------------------------------------------------------------------------- /signatory-ring/src/ecdsa/p256.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA P-256 provider for the *ring* crate 2 | 3 | pub use signatory::ecdsa::curve::nistp256::{Asn1Signature, FixedSignature, NistP256}; 4 | 5 | use super::signer::EcdsaSigner; 6 | use ring::signature::{ 7 | UnparsedPublicKey, ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_ASN1_SIGNING, 8 | ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, 9 | }; 10 | use signatory::{ 11 | public_key::PublicKeyed, 12 | signature::{self, Signature}, 13 | }; 14 | 15 | #[cfg(feature = "std")] 16 | use ring::rand::SystemRandom; 17 | #[cfg(feature = "std")] 18 | use signatory::encoding::{ 19 | self, 20 | pkcs8::{self, FromPkcs8, GeneratePkcs8}, 21 | }; 22 | 23 | /// NIST P-256 public key 24 | pub type PublicKey = signatory::ecdsa::PublicKey; 25 | 26 | /// NIST P-256 ECDSA signer 27 | pub struct Signer(EcdsaSigner); 28 | 29 | #[cfg(feature = "std")] 30 | impl FromPkcs8 for Signer { 31 | /// Create a new ECDSA signer which produces fixed-width signatures from a PKCS#8 keypair 32 | fn from_pkcs8>(secret_key: K) -> Result { 33 | Ok(Signer(EcdsaSigner::from_pkcs8( 34 | &ECDSA_P256_SHA256_ASN1_SIGNING, 35 | secret_key.as_ref(), 36 | )?)) 37 | } 38 | } 39 | 40 | #[cfg(feature = "std")] 41 | impl FromPkcs8 for Signer { 42 | /// Create a new ECDSA signer which produces fixed-width signatures from a PKCS#8 keypair 43 | fn from_pkcs8>(secret_key: K) -> Result { 44 | Ok(Signer(EcdsaSigner::from_pkcs8( 45 | &ECDSA_P256_SHA256_FIXED_SIGNING, 46 | secret_key.as_ref(), 47 | )?)) 48 | } 49 | } 50 | 51 | #[cfg(feature = "std")] 52 | impl GeneratePkcs8 for Signer { 53 | /// Randomly generate a P-256 **PKCS#8** keypair 54 | fn generate_pkcs8() -> Result { 55 | let keypair = ring::signature::EcdsaKeyPair::generate_pkcs8( 56 | &ECDSA_P256_SHA256_ASN1_SIGNING, 57 | &SystemRandom::new(), 58 | ) 59 | .unwrap(); 60 | 61 | pkcs8::SecretKey::from_bytes(keypair.as_ref()) 62 | } 63 | } 64 | 65 | #[cfg(feature = "std")] 66 | impl GeneratePkcs8 for Signer { 67 | /// Randomly generate a P-256 **PKCS#8** keypair 68 | fn generate_pkcs8() -> Result { 69 | let keypair = ring::signature::EcdsaKeyPair::generate_pkcs8( 70 | &ECDSA_P256_SHA256_FIXED_SIGNING, 71 | &SystemRandom::new(), 72 | ) 73 | .unwrap(); 74 | 75 | pkcs8::SecretKey::from_bytes(keypair.as_ref()) 76 | } 77 | } 78 | 79 | impl PublicKeyed for Signer 80 | where 81 | S: Signature + Send + Sync, 82 | { 83 | fn public_key(&self) -> Result { 84 | PublicKey::from_bytes(self.0.public_key()).ok_or_else(signature::Error::new) 85 | } 86 | } 87 | 88 | impl signature::Signer for Signer { 89 | fn try_sign(&self, msg: &[u8]) -> Result { 90 | self.0.sign(msg) 91 | } 92 | } 93 | 94 | impl signature::Signer for Signer { 95 | fn try_sign(&self, msg: &[u8]) -> Result { 96 | self.0.sign(msg) 97 | } 98 | } 99 | 100 | /// NIST P-256 ECDSA verifier 101 | #[derive(Clone, Debug, Eq, PartialEq)] 102 | pub struct Verifier(PublicKey); 103 | 104 | impl<'a> From<&'a PublicKey> for Verifier { 105 | fn from(public_key: &'a PublicKey) -> Self { 106 | Verifier(*public_key) 107 | } 108 | } 109 | 110 | impl signature::Verifier for Verifier { 111 | fn verify(&self, msg: &[u8], signature: &Asn1Signature) -> Result<(), signature::Error> { 112 | UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, self.0.as_ref()) 113 | .verify(msg, signature.as_ref()) 114 | .map_err(|_| signature::Error::new()) 115 | } 116 | } 117 | 118 | impl signature::Verifier for Verifier { 119 | fn verify(&self, msg: &[u8], signature: &FixedSignature) -> Result<(), signature::Error> { 120 | UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, self.0.as_ref()) 121 | .verify(msg, signature.as_ref()) 122 | .map_err(|_| signature::Error::new()) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::{PublicKey, Signer, Verifier}; 129 | use signatory::{ 130 | ecdsa::{ 131 | curve::nistp256::{Asn1Signature, FixedSignature}, 132 | generic_array::GenericArray, 133 | test_vectors::nistp256::SHA256_FIXED_SIZE_TEST_VECTORS, 134 | }, 135 | encoding::FromPkcs8, 136 | public_key::PublicKeyed, 137 | signature::{Signature as _, Signer as _, Verifier as _}, 138 | test_vector::{TestVectorAlgorithm, ToPkcs8}, 139 | }; 140 | 141 | #[test] 142 | pub fn asn1_signature_roundtrip() { 143 | // TODO: DER test vectors 144 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 145 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 146 | let signature: Asn1Signature = signer.sign(vector.msg); 147 | 148 | let verifier = Verifier::from(&signer.public_key().unwrap()); 149 | assert!(verifier.verify(vector.msg, &signature).is_ok()); 150 | } 151 | 152 | #[test] 153 | pub fn rejects_tweaked_asn1_signature() { 154 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 155 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 156 | let signature: Asn1Signature = signer.sign(vector.msg); 157 | 158 | let mut tweaked_signature = signature.as_ref().to_vec(); 159 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 160 | 161 | let verifier = Verifier::from(&signer.public_key().unwrap()); 162 | let result = verifier.verify( 163 | vector.msg, 164 | &Asn1Signature::from_bytes(tweaked_signature.as_ref()).unwrap(), 165 | ); 166 | 167 | assert!( 168 | result.is_err(), 169 | "expected bad signature to cause validation error!" 170 | ); 171 | } 172 | 173 | #[test] 174 | pub fn fixed_signature_vectors() { 175 | for vector in SHA256_FIXED_SIZE_TEST_VECTORS { 176 | let signer = 177 | Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 178 | let public_key = PublicKey::from_untagged_point(&GenericArray::from_slice(vector.pk)); 179 | assert_eq!(signer.public_key().unwrap(), public_key); 180 | 181 | // Compute a signature with a random `k` 182 | // TODO: test deterministic `k` 183 | let signature: FixedSignature = signer.sign(vector.msg); 184 | 185 | let verifier = Verifier::from(&signer.public_key().unwrap()); 186 | assert!(verifier.verify(vector.msg, &signature).is_ok()); 187 | 188 | // Make sure the vector signature verifies 189 | assert!(verifier 190 | .verify( 191 | vector.msg, 192 | &FixedSignature::from_bytes(&vector.sig).unwrap() 193 | ) 194 | .is_ok()); 195 | } 196 | } 197 | 198 | #[test] 199 | pub fn rejects_tweaked_fixed_signature() { 200 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 201 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 202 | let signature: FixedSignature = signer.sign(vector.msg); 203 | 204 | let mut tweaked_signature = signature.as_ref().to_vec(); 205 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 206 | 207 | let verifier = Verifier::from(&signer.public_key().unwrap()); 208 | let result = verifier.verify( 209 | vector.msg, 210 | &FixedSignature::from_bytes(tweaked_signature.as_ref()).unwrap(), 211 | ); 212 | 213 | assert!( 214 | result.is_err(), 215 | "expected bad signature to cause validation error!" 216 | ); 217 | } 218 | 219 | #[test] 220 | fn test_fixed_to_asn1_transformed_signature_verifies() { 221 | for vector in SHA256_FIXED_SIZE_TEST_VECTORS { 222 | let signer = 223 | Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP256)).unwrap(); 224 | 225 | let fixed_signature: FixedSignature = signer.sign(vector.msg); 226 | 227 | let asn1_signature = Asn1Signature::from(&fixed_signature); 228 | let verifier = Verifier::from(&signer.public_key().unwrap()); 229 | assert!(verifier.verify(vector.msg, &asn1_signature).is_ok()); 230 | } 231 | } 232 | 233 | /// Ensure leading zeros are handled properly when serializing ASN.1 signatures 234 | #[test] 235 | fn test_fixed_to_asn1_leading_zero_handling() { 236 | // Failing case is a signature using a key/msg from test vector 237 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[1]; 238 | 239 | let fixed_signature = FixedSignature::from_bytes( 240 | b"\xd1\x64\xfd\xe7\x8d\xd5\x3d\xb8\xb3\xc7\x88\x3d\x40\x8a\x79\x28\ 241 | \x17\x70\x5b\x73\x6b\xc9\x97\x47\xba\x7c\x50\x48\x0b\x6f\x84\x54\ 242 | \x00\x06\x9d\x3a\x33\x6b\x40\xc0\x83\x83\x36\x2e\xe5\x8c\x46\x71\ 243 | \x7e\x22\x30\x1e\xd9\x98\xb6\xcc\xaa\x43\x35\x7f\x97\x56\xe2\x5c" 244 | .as_ref(), 245 | ) 246 | .unwrap(); 247 | 248 | let public_key = PublicKey::from_untagged_point(&GenericArray::from_slice(vector.pk)); 249 | let verifier = Verifier::from(&public_key); 250 | assert!(verifier.verify(vector.msg, &fixed_signature).is_ok()); 251 | 252 | let asn1_signature = Asn1Signature::from(&fixed_signature); 253 | assert!(verifier.verify(vector.msg, &asn1_signature).is_ok()); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /signatory-ring/src/ecdsa/p384.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA P-384 provider for the *ring* crate 2 | 3 | pub use signatory::ecdsa::curve::nistp384::{Asn1Signature, FixedSignature, NistP384}; 4 | 5 | use super::signer::EcdsaSigner; 6 | use ring::signature::{ 7 | UnparsedPublicKey, ECDSA_P384_SHA384_ASN1, ECDSA_P384_SHA384_ASN1_SIGNING, 8 | ECDSA_P384_SHA384_FIXED, ECDSA_P384_SHA384_FIXED_SIGNING, 9 | }; 10 | use signatory::{ 11 | public_key::PublicKeyed, 12 | signature::{self, Signature}, 13 | }; 14 | 15 | #[cfg(feature = "std")] 16 | use ring::rand::SystemRandom; 17 | #[cfg(feature = "std")] 18 | use signatory::encoding::{ 19 | self, 20 | pkcs8::{self, FromPkcs8, GeneratePkcs8}, 21 | }; 22 | 23 | /// NIST P-384 public key 24 | pub type PublicKey = signatory::ecdsa::PublicKey; 25 | 26 | /// NIST P-384 ECDSA signer 27 | pub struct Signer(EcdsaSigner); 28 | 29 | #[cfg(feature = "std")] 30 | impl FromPkcs8 for Signer { 31 | /// Create a new ECDSA signer which produces fixed-width signatures from a PKCS#8 keypair 32 | fn from_pkcs8>(secret_key: K) -> Result { 33 | Ok(Signer(EcdsaSigner::from_pkcs8( 34 | &ECDSA_P384_SHA384_ASN1_SIGNING, 35 | secret_key.as_ref(), 36 | )?)) 37 | } 38 | } 39 | 40 | #[cfg(feature = "std")] 41 | impl FromPkcs8 for Signer { 42 | /// Create a new ECDSA signer which produces fixed-width signatures from a PKCS#8 keypair 43 | fn from_pkcs8>(secret_key: K) -> Result { 44 | Ok(Signer(EcdsaSigner::from_pkcs8( 45 | &ECDSA_P384_SHA384_FIXED_SIGNING, 46 | secret_key.as_ref(), 47 | )?)) 48 | } 49 | } 50 | 51 | #[cfg(feature = "std")] 52 | impl GeneratePkcs8 for Signer { 53 | /// Randomly generate a P-384 **PKCS#8** keypair 54 | fn generate_pkcs8() -> Result { 55 | let keypair = ring::signature::EcdsaKeyPair::generate_pkcs8( 56 | &ECDSA_P384_SHA384_ASN1_SIGNING, 57 | &SystemRandom::new(), 58 | ) 59 | .unwrap(); 60 | pkcs8::SecretKey::from_bytes(keypair.as_ref()) 61 | } 62 | } 63 | 64 | #[cfg(feature = "std")] 65 | impl GeneratePkcs8 for Signer { 66 | /// Randomly generate a P-384 **PKCS#8** keypair 67 | fn generate_pkcs8() -> Result { 68 | let keypair = ring::signature::EcdsaKeyPair::generate_pkcs8( 69 | &ECDSA_P384_SHA384_FIXED_SIGNING, 70 | &SystemRandom::new(), 71 | ) 72 | .unwrap(); 73 | pkcs8::SecretKey::from_bytes(keypair.as_ref()) 74 | } 75 | } 76 | 77 | impl PublicKeyed for Signer 78 | where 79 | S: Signature + Send + Sync, 80 | { 81 | /// Obtain the public key which identifies this signer 82 | fn public_key(&self) -> Result { 83 | PublicKey::from_bytes(self.0.public_key()).ok_or_else(signature::Error::new) 84 | } 85 | } 86 | 87 | impl signature::Signer for Signer { 88 | fn try_sign(&self, msg: &[u8]) -> Result { 89 | self.0.sign(msg) 90 | } 91 | } 92 | 93 | impl signature::Signer for Signer { 94 | fn try_sign(&self, msg: &[u8]) -> Result { 95 | self.0.sign(msg) 96 | } 97 | } 98 | 99 | /// NIST P-384 ECDSA verifier 100 | #[derive(Clone, Debug, Eq, PartialEq)] 101 | pub struct Verifier(PublicKey); 102 | 103 | impl<'a> From<&'a PublicKey> for Verifier { 104 | fn from(public_key: &'a PublicKey) -> Self { 105 | Verifier(*public_key) 106 | } 107 | } 108 | 109 | impl signature::Verifier for Verifier { 110 | fn verify(&self, msg: &[u8], signature: &Asn1Signature) -> Result<(), signature::Error> { 111 | UnparsedPublicKey::new(&ECDSA_P384_SHA384_ASN1, self.0.as_ref()) 112 | .verify(msg, signature.as_ref()) 113 | .map_err(|_| signature::Error::new()) 114 | } 115 | } 116 | 117 | impl signature::Verifier for Verifier { 118 | fn verify(&self, msg: &[u8], signature: &FixedSignature) -> Result<(), signature::Error> { 119 | UnparsedPublicKey::new(&ECDSA_P384_SHA384_FIXED, self.0.as_ref()) 120 | .verify(msg, signature.as_ref()) 121 | .map_err(|_| signature::Error::new()) 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::{PublicKey, Signer, Verifier}; 128 | use signatory::{ 129 | ecdsa::{ 130 | curve::nistp384::{Asn1Signature, FixedSignature}, 131 | generic_array::GenericArray, 132 | test_vectors::nistp384::SHA384_FIXED_SIZE_TEST_VECTORS, 133 | }, 134 | encoding::FromPkcs8, 135 | public_key::PublicKeyed, 136 | signature::{Signature as _, Signer as _, Verifier as _}, 137 | test_vector::{TestVectorAlgorithm, ToPkcs8}, 138 | }; 139 | 140 | #[test] 141 | pub fn asn1_signature_roundtrip() { 142 | // TODO: DER test vectors 143 | let vector = &SHA384_FIXED_SIZE_TEST_VECTORS[0]; 144 | 145 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP384)).unwrap(); 146 | let signature: Asn1Signature = signer.sign(vector.msg); 147 | 148 | let verifier = Verifier::from(&signer.public_key().unwrap()); 149 | assert!(verifier.verify(vector.msg, &signature).is_ok()); 150 | } 151 | 152 | #[test] 153 | pub fn rejects_tweaked_asn1_signature() { 154 | let vector = &SHA384_FIXED_SIZE_TEST_VECTORS[0]; 155 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP384)).unwrap(); 156 | let signature: Asn1Signature = signer.sign(vector.msg); 157 | 158 | let mut tweaked_signature = signature.as_ref().to_vec(); 159 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 160 | 161 | let verifier = Verifier::from(&signer.public_key().unwrap()); 162 | let result = verifier.verify( 163 | vector.msg, 164 | &Asn1Signature::from_bytes(tweaked_signature.as_ref()).unwrap(), 165 | ); 166 | 167 | assert!( 168 | result.is_err(), 169 | "expected bad signature to cause validation error!" 170 | ); 171 | } 172 | 173 | #[test] 174 | pub fn fixed_signature_vectors() { 175 | for vector in SHA384_FIXED_SIZE_TEST_VECTORS { 176 | let signer = 177 | Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP384)).unwrap(); 178 | let public_key = PublicKey::from_untagged_point(&GenericArray::from_slice(vector.pk)); 179 | assert_eq!(signer.public_key().unwrap(), public_key); 180 | 181 | // Compute a signature with a random `k` 182 | // TODO: test deterministic `k` 183 | let signature: FixedSignature = signer.sign(vector.msg); 184 | 185 | let verifier = Verifier::from(&signer.public_key().unwrap()); 186 | assert!(verifier.verify(vector.msg, &signature).is_ok()); 187 | 188 | // Make sure the vector signature verifies 189 | assert!(verifier 190 | .verify( 191 | vector.msg, 192 | &FixedSignature::from_bytes(&vector.sig).unwrap() 193 | ) 194 | .is_ok()); 195 | } 196 | } 197 | 198 | #[test] 199 | pub fn rejects_tweaked_fixed_signature() { 200 | let vector = &SHA384_FIXED_SIZE_TEST_VECTORS[0]; 201 | let signer = Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP384)).unwrap(); 202 | let signature: FixedSignature = signer.sign(vector.msg); 203 | 204 | let mut tweaked_signature = signature.as_ref().to_vec(); 205 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 206 | 207 | let verifier = Verifier::from(&signer.public_key().unwrap()); 208 | let result = verifier.verify( 209 | vector.msg, 210 | &FixedSignature::from_bytes(tweaked_signature.as_ref()).unwrap(), 211 | ); 212 | 213 | assert!( 214 | result.is_err(), 215 | "expected bad signature to cause validation error!" 216 | ); 217 | } 218 | 219 | #[test] 220 | fn test_fixed_to_asn1_transformed_signature_verifies() { 221 | for vector in SHA384_FIXED_SIZE_TEST_VECTORS { 222 | let signer = 223 | Signer::from_pkcs8(&vector.to_pkcs8(TestVectorAlgorithm::NistP384)).unwrap(); 224 | let fixed_signature: FixedSignature = signer.sign(vector.msg); 225 | 226 | let asn1_signature = Asn1Signature::from(&fixed_signature); 227 | let verifier = Verifier::from(&signer.public_key().unwrap()); 228 | assert!(verifier.verify(vector.msg, &asn1_signature).is_ok()); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /signatory-ring/src/ecdsa/signer.rs: -------------------------------------------------------------------------------- 1 | //! Generic *ring* ECDSA signer 2 | 3 | use core::marker::PhantomData; 4 | use ring::{ 5 | self, 6 | rand::SystemRandom, 7 | signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, KeyPair}, 8 | }; 9 | use signatory::{ 10 | encoding, 11 | signature::{self, Signature}, 12 | }; 13 | 14 | /// Generic ECDSA signer which is wrapped with curve and signature-specific types 15 | pub(super) struct EcdsaSigner { 16 | /// *ring* ECDSA keypair 17 | keypair: EcdsaKeyPair, 18 | 19 | /// Cryptographically secure random number generator 20 | csrng: SystemRandom, 21 | 22 | /// Signature type produced by this signer 23 | signature: PhantomData, 24 | } 25 | 26 | impl EcdsaSigner 27 | where 28 | S: Signature, 29 | { 30 | /// Create an ECDSA signer 31 | #[cfg(feature = "std")] 32 | pub fn from_pkcs8( 33 | alg: &'static EcdsaSigningAlgorithm, 34 | pkcs8_bytes: &[u8], 35 | ) -> Result { 36 | let keypair = EcdsaKeyPair::from_pkcs8(alg, pkcs8_bytes) 37 | .map_err(|_| encoding::error::ErrorKind::Decode)?; 38 | 39 | let csrng = SystemRandom::new(); 40 | 41 | Ok(Self { 42 | keypair, 43 | csrng, 44 | signature: PhantomData, 45 | }) 46 | } 47 | 48 | /// Get the public key for this ECDSA signer 49 | pub fn public_key(&self) -> &[u8] { 50 | self.keypair.public_key().as_ref() 51 | } 52 | 53 | /// Sign a message, returning the signature 54 | pub fn sign(&self, msg: &[u8]) -> Result { 55 | let sig = self 56 | .keypair 57 | .sign(&self.csrng, msg) 58 | .map_err(|_| signature::Error::new())?; 59 | 60 | S::from_bytes(sig.as_ref()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /signatory-ring/src/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 signer and verifier implementation for *ring* 2 | 3 | pub use signatory::ed25519::{PublicKey, Seed, Signature}; 4 | 5 | use ring::{ 6 | self, 7 | signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey}, 8 | }; 9 | use signatory::{ 10 | public_key::PublicKeyed, 11 | signature::{self, Signature as _}, 12 | }; 13 | 14 | #[cfg(feature = "std")] 15 | use ring::rand::SystemRandom; 16 | #[cfg(feature = "std")] 17 | use signatory::encoding::{ 18 | self, 19 | pkcs8::{self, FromPkcs8, GeneratePkcs8}, 20 | }; 21 | 22 | /// Ed25519 signature provider for *ring* 23 | pub struct Signer(Ed25519KeyPair); 24 | 25 | impl<'a> From<&'a Seed> for Signer { 26 | /// Create a new Ed25519Signer from an unexpanded seed value 27 | fn from(seed: &'a Seed) -> Self { 28 | let keypair = Ed25519KeyPair::from_seed_unchecked(seed.as_secret_slice()).unwrap(); 29 | 30 | Signer(keypair) 31 | } 32 | } 33 | 34 | #[cfg(feature = "std")] 35 | impl FromPkcs8 for Signer { 36 | /// Create a new Ed25519Signer from a PKCS#8 encoded private key 37 | fn from_pkcs8>(secret_key: K) -> Result { 38 | let keypair = Ed25519KeyPair::from_pkcs8(secret_key.as_ref()) 39 | .map_err(|_| encoding::error::ErrorKind::Decode)?; 40 | 41 | Ok(Signer(keypair)) 42 | } 43 | } 44 | 45 | #[cfg(feature = "std")] 46 | impl GeneratePkcs8 for Signer { 47 | /// Randomly generate an Ed25519 **PKCS#8** keypair 48 | fn generate_pkcs8() -> Result { 49 | let keypair = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap(); 50 | pkcs8::SecretKey::from_bytes(keypair.as_ref()) 51 | } 52 | } 53 | 54 | impl PublicKeyed for Signer { 55 | fn public_key(&self) -> Result { 56 | Ok(PublicKey::from_bytes(self.0.public_key()).unwrap()) 57 | } 58 | } 59 | 60 | impl signature::Signer for Signer { 61 | fn try_sign(&self, msg: &[u8]) -> Result { 62 | Ok(Signature::from_bytes(self.0.sign(msg).as_ref()).unwrap()) 63 | } 64 | } 65 | 66 | /// Ed25519 verifier for *ring* 67 | #[derive(Clone, Debug, Eq, PartialEq)] 68 | pub struct Verifier(PublicKey); 69 | 70 | impl<'a> From<&'a PublicKey> for Verifier { 71 | fn from(public_key: &'a PublicKey) -> Self { 72 | Verifier(*public_key) 73 | } 74 | } 75 | 76 | impl signature::Verifier for Verifier { 77 | fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { 78 | UnparsedPublicKey::new(&ring::signature::ED25519, self.0.as_bytes()) 79 | .verify(msg, signature.as_ref()) 80 | .map_err(|_| signature::Error::new()) 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::{Signer, Verifier}; 87 | ed25519_tests!(Signer, Verifier); 88 | } 89 | -------------------------------------------------------------------------------- /signatory-ring/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Signatory ECDSA and Ed25519 provider for *ring* 2 | 3 | #![no_std] 4 | #![forbid(unsafe_code)] 5 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 6 | #![doc( 7 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 8 | html_root_url = "https://docs.rs/signatory-ring/0.19.0" 9 | )] 10 | 11 | #[cfg(test)] 12 | #[macro_use] 13 | extern crate std; 14 | 15 | #[cfg(all(test, feature = "ed25519"))] 16 | #[macro_use] 17 | extern crate signatory; 18 | 19 | /// ECDSA signing and verification support 20 | #[cfg(feature = "ecdsa")] 21 | pub mod ecdsa; 22 | 23 | /// Ed25519 signing and verification support 24 | #[cfg(feature = "ed25519")] 25 | pub mod ed25519; 26 | -------------------------------------------------------------------------------- /signatory-secp256k1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory-secp256k1" 3 | description = "Signatory ECDSA provider for secp256k1-rs" 4 | version = "0.19.0" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/signatory" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop/providers/signatory-secp256k1/" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cryptography", "bitcoin", "ecdsa", "secp256k1", "signatures"] 12 | edition = "2018" 13 | 14 | [badges] 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | secp256k1 = "0.17" 19 | sha3 = { version = "0.8", optional = true } 20 | signature = { version = "1", features = ["derive-preview"] } 21 | 22 | [dependencies.signatory] 23 | version = "0.19" 24 | features = ["digest", "ecdsa", "k256", "sha2"] 25 | path = ".." 26 | 27 | [dev-dependencies] 28 | criterion = "0.3" 29 | 30 | [dev-dependencies.signatory] 31 | version = "0.19" 32 | default-features = false 33 | features = ["digest", "ecdsa", "k256", "sha2", "test-vectors"] 34 | path = ".." 35 | 36 | [[bench]] 37 | name = "ecdsa" 38 | harness = false 39 | -------------------------------------------------------------------------------- /signatory-secp256k1/README.md: -------------------------------------------------------------------------------- 1 | # signatory-secp256k1 [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![MIT/Apache2 licensed][license-image] 2 | 3 | [Signatory] ECDSA ([FIPS 186-4]) provider for [secp256k1-rs]. 4 | 5 | [Documentation](https://docs.rs/signatory/) 6 | 7 | [Signatory]: https://github.com/iqlusioninc/signatory 8 | [FIPS 186-4]: https://csrc.nist.gov/publications/detail/fips/186/4/final 9 | [secp256k1-rs]: https://github.com/rust-bitcoin/rust-secp256k1/ 10 | 11 | ## License 12 | 13 | **Signatory** is distributed under the terms of either the MIT license or the 14 | Apache License (Version 2.0), at your option. 15 | 16 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 17 | 18 | [crate-image]: https://img.shields.io/crates/v/signatory-secp256k1.svg 19 | [crate-link]: https://crates.io/crates/signatory-secp256k1 20 | [docs-image]: https://docs.rs/signatory-secp256k1/badge.svg 21 | [docs-link]: https://docs.rs/signatory-secp256k1/ 22 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 23 | [build-link]: https://github.com/iqlusioninc/signatory/actions 24 | [license-image]: https://img.shields.io/badge/license-MIT/Apache2.0-blue.svg 25 | -------------------------------------------------------------------------------- /signatory-secp256k1/benches/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! secp256k1 provider benchmarks 2 | 3 | #![allow(unused_imports)] 4 | #![deny(warnings)] 5 | 6 | #[macro_use] 7 | extern crate criterion; 8 | use signatory; 9 | 10 | use criterion::Criterion; 11 | use signatory::{ 12 | ecdsa::{ 13 | self, 14 | curve::secp256k1::FixedSignature, 15 | generic_array::GenericArray, 16 | test_vectors::{secp256k1::SHA256_FIXED_SIZE_TEST_VECTORS, TestVector}, 17 | PublicKey, 18 | }, 19 | signature::{Signature, Signer, Verifier}, 20 | }; 21 | use signatory_secp256k1::{EcdsaSigner, EcdsaVerifier, SecretKey}; 22 | 23 | /// Test vector to use for benchmarking 24 | const TEST_VECTOR: &TestVector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 25 | 26 | fn sign_ecdsa(c: &mut Criterion) { 27 | let signer = EcdsaSigner::from(&SecretKey::from_bytes(TEST_VECTOR.sk).unwrap()); 28 | 29 | c.bench_function("secp256k1: ECDSA signer", move |b| { 30 | b.iter(|| { 31 | let _: FixedSignature = signer.sign(TEST_VECTOR.msg); 32 | }) 33 | }); 34 | } 35 | 36 | fn verify_ecdsa(c: &mut Criterion) { 37 | let signature = FixedSignature::from_bytes(TEST_VECTOR.sig).unwrap(); 38 | let public_key = 39 | PublicKey::from_compressed_point(GenericArray::clone_from_slice(TEST_VECTOR.pk)).unwrap(); 40 | let verifier = EcdsaVerifier::from(&public_key); 41 | 42 | c.bench_function("secp256k1: ECDSA verifier", move |b| { 43 | b.iter(|| { 44 | verifier.verify(TEST_VECTOR.msg, &signature).unwrap(); 45 | }) 46 | }); 47 | } 48 | 49 | criterion_group! { 50 | name = ecdsa; 51 | config = Criterion::default(); 52 | targets = sign_ecdsa, verify_ecdsa 53 | } 54 | 55 | criterion_main!(ecdsa); 56 | -------------------------------------------------------------------------------- /signatory-secp256k1/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA provider for the `secp256k1` crate (a.k.a. secp256k1-rs) 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 5 | #![doc( 6 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 7 | html_root_url = "https://docs.rs/signatory-secp256k1/0.19.0" 8 | )] 9 | 10 | pub use signatory; 11 | pub use signatory::ecdsa::curve::secp256k1::{Asn1Signature, FixedSignature, PublicKey, SecretKey}; 12 | 13 | use secp256k1::{self, Secp256k1, SignOnly, VerifyOnly}; 14 | use signatory::{ 15 | public_key::PublicKeyed, 16 | sha2::Sha256, 17 | signature::{digest::Digest, DigestSigner, DigestVerifier, Error, Signature, Signer, Verifier}, 18 | }; 19 | 20 | #[cfg(feature = "sha3")] 21 | use sha3::Keccak256; 22 | 23 | /// ECDSA signature provider for the secp256k1 crate 24 | #[derive(Signer)] 25 | pub struct EcdsaSigner { 26 | /// ECDSA secret key 27 | secret_key: secp256k1::SecretKey, 28 | 29 | /// secp256k1 engine 30 | engine: Secp256k1, 31 | } 32 | 33 | impl From<&SecretKey> for EcdsaSigner { 34 | /// Create a new secp256k1 signer from the given `SecretKey` 35 | fn from(secret_key: &SecretKey) -> EcdsaSigner { 36 | let secret_key = 37 | secp256k1::SecretKey::from_slice(secret_key.secret_scalar().as_ref()).unwrap(); 38 | let engine = Secp256k1::signing_only(); 39 | EcdsaSigner { secret_key, engine } 40 | } 41 | } 42 | 43 | impl PublicKeyed for EcdsaSigner { 44 | /// Return the public key that corresponds to the private key for this signer 45 | fn public_key(&self) -> Result { 46 | let public_key = secp256k1::PublicKey::from_secret_key(&self.engine, &self.secret_key); 47 | PublicKey::from_bytes(&public_key.serialize()[..]).ok_or_else(Error::new) 48 | } 49 | } 50 | 51 | impl DigestSigner for EcdsaSigner { 52 | /// Compute an ASN.1 DER-encoded signature of the given 32-byte SHA-256 digest 53 | fn try_sign_digest(&self, digest: Sha256) -> Result { 54 | let signature = self.raw_sign_digest(digest)?.serialize_der(); 55 | Ok(Asn1Signature::from_bytes(signature.as_ref()).unwrap()) 56 | } 57 | } 58 | 59 | impl DigestSigner for EcdsaSigner { 60 | /// Compute a compact, fixed-sized signature of the given 32-byte SHA-256 digest 61 | fn try_sign_digest(&self, digest: Sha256) -> Result { 62 | let signature = self.raw_sign_digest(digest)?.serialize_compact(); 63 | Ok(FixedSignature::from_bytes(signature.as_ref()).unwrap()) 64 | } 65 | } 66 | 67 | #[cfg(feature = "sha3")] 68 | impl DigestSigner for EcdsaSigner { 69 | fn try_sign_digest(&self, digest: Keccak256) -> Result { 70 | Ok( 71 | FixedSignature::from_bytes(&self.raw_sign_digest(digest)?.serialize_compact()[..]) 72 | .unwrap(), 73 | ) 74 | } 75 | } 76 | 77 | impl EcdsaSigner { 78 | /// Sign a digest and produce a `secp256k1::Signature` 79 | fn raw_sign_digest(&self, digest: D) -> Result { 80 | let msg = secp256k1::Message::from_slice(digest.result().as_slice()) 81 | .map_err(Error::from_source)?; 82 | 83 | Ok(self.engine.sign(&msg, &self.secret_key)) 84 | } 85 | } 86 | 87 | /// ECDSA verifier provider for the secp256k1 crate 88 | #[derive(Clone, Debug, Eq, PartialEq, Verifier)] 89 | pub struct EcdsaVerifier { 90 | /// ECDSA public key 91 | public_key: secp256k1::PublicKey, 92 | 93 | /// ECDSA engine 94 | engine: Secp256k1, 95 | } 96 | 97 | impl<'a> From<&'a PublicKey> for EcdsaVerifier { 98 | fn from(public_key: &'a PublicKey) -> Self { 99 | let public_key = secp256k1::PublicKey::from_slice(public_key.as_bytes()).unwrap(); 100 | let engine = Secp256k1::verification_only(); 101 | EcdsaVerifier { public_key, engine } 102 | } 103 | } 104 | 105 | impl DigestVerifier for EcdsaVerifier { 106 | fn verify_digest(&self, digest: Sha256, signature: &Asn1Signature) -> Result<(), Error> { 107 | self.raw_verify_digest( 108 | digest, 109 | secp256k1::Signature::from_der(signature.as_bytes()).map_err(Error::from_source)?, 110 | ) 111 | } 112 | } 113 | 114 | impl DigestVerifier for EcdsaVerifier { 115 | fn verify_digest(&self, digest: Sha256, signature: &FixedSignature) -> Result<(), Error> { 116 | self.raw_verify_digest( 117 | digest, 118 | secp256k1::Signature::from_compact(signature.as_bytes()).map_err(Error::from_source)?, 119 | ) 120 | } 121 | } 122 | 123 | impl EcdsaVerifier { 124 | /// Verify a digest against a `secp256k1::Signature` 125 | fn raw_verify_digest(&self, digest: Sha256, sig: secp256k1::Signature) -> Result<(), Error> { 126 | let msg = secp256k1::Message::from_slice(digest.result().as_slice()) 127 | .map_err(Error::from_source)?; 128 | 129 | self.engine 130 | .verify(&msg, &sig, &self.public_key) 131 | .map_err(Error::from_source) 132 | } 133 | } 134 | 135 | // TODO: test against actual test vectors, rather than just checking if signatures roundtrip 136 | #[cfg(test)] 137 | mod tests { 138 | use super::{EcdsaSigner, EcdsaVerifier, PublicKey, SecretKey}; 139 | use signatory::{ 140 | self, 141 | ecdsa::{ 142 | curve::secp256k1::{Asn1Signature, FixedSignature}, 143 | test_vectors::secp256k1::SHA256_FIXED_SIZE_TEST_VECTORS, 144 | }, 145 | public_key::PublicKeyed, 146 | signature::{Signature, Signer, Verifier}, 147 | }; 148 | 149 | #[test] 150 | pub fn asn1_signature_roundtrip() { 151 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 152 | let signer = EcdsaSigner::from(&SecretKey::from_bytes(vector.sk).unwrap()); 153 | 154 | let signature: Asn1Signature = signer.sign(vector.msg); 155 | 156 | let verifier = EcdsaVerifier::from(&signer.public_key().unwrap()); 157 | assert!(verifier.verify(vector.msg, &signature).is_ok()); 158 | } 159 | 160 | #[test] 161 | pub fn rejects_tweaked_asn1_signature() { 162 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 163 | let signer = EcdsaSigner::from(&SecretKey::from_bytes(vector.sk).unwrap()); 164 | 165 | let signature: Asn1Signature = signer.sign(vector.msg); 166 | let mut tweaked_signature = signature.as_ref().to_vec(); 167 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 168 | 169 | let verifier = EcdsaVerifier::from(&signer.public_key().unwrap()); 170 | let result = verifier.verify( 171 | vector.msg, 172 | &Asn1Signature::from_bytes(tweaked_signature.as_ref()).unwrap(), 173 | ); 174 | 175 | assert!( 176 | result.is_err(), 177 | "expected bad signature to cause validation error!" 178 | ); 179 | } 180 | 181 | #[test] 182 | pub fn fixed_signature_vectors() { 183 | for vector in SHA256_FIXED_SIZE_TEST_VECTORS { 184 | let signer = EcdsaSigner::from(&SecretKey::from_bytes(vector.sk).unwrap()); 185 | let public_key = PublicKey::from_bytes(vector.pk).unwrap(); 186 | assert_eq!(signer.public_key().unwrap(), public_key); 187 | 188 | let signature: FixedSignature = signer.sign(vector.msg); 189 | assert_eq!(signature.as_ref(), vector.sig); 190 | 191 | EcdsaVerifier::from(&public_key) 192 | .verify(vector.msg, &signature) 193 | .unwrap(); 194 | } 195 | } 196 | 197 | #[test] 198 | pub fn rejects_tweaked_fixed_signature() { 199 | let vector = &SHA256_FIXED_SIZE_TEST_VECTORS[0]; 200 | let signer = EcdsaSigner::from(&SecretKey::from_bytes(vector.sk).unwrap()); 201 | 202 | let signature: FixedSignature = signer.sign(vector.msg); 203 | let mut tweaked_signature = signature.as_ref().to_vec(); 204 | *tweaked_signature.iter_mut().last().unwrap() ^= 42; 205 | 206 | let verifier = EcdsaVerifier::from(&signer.public_key().unwrap()); 207 | let result = verifier.verify( 208 | vector.msg, 209 | &FixedSignature::from_bytes(tweaked_signature.as_ref()).unwrap(), 210 | ); 211 | 212 | assert!( 213 | result.is_err(), 214 | "expected bad signature to cause validation error!" 215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /signatory-sodiumoxide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory-sodiumoxide" 3 | description = "Signatory Ed25519 provider for sodiumoxide" 4 | version = "0.19.0" # Also update html_root_url in lib.rs when bumping this 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/signatory" 8 | repository = "https://github.com/iqlusioninc/signatory/tree/develop/providers/signatory-sodiumoxide/" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography", "no-std"] 11 | keywords = ["cryptography", "ed25519", "no_std", "sodiumoxide", "signatures"] 12 | edition = "2018" 13 | 14 | [badges] 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | sodiumoxide = "0.2" 19 | 20 | [dependencies.signatory] 21 | version = "0.19" 22 | features = ["ed25519", "test-vectors"] 23 | path = ".." 24 | 25 | [dev-dependencies] 26 | criterion = "0.3" 27 | 28 | [[bench]] 29 | name = "ed25519" 30 | harness = false 31 | -------------------------------------------------------------------------------- /signatory-sodiumoxide/README.md: -------------------------------------------------------------------------------- 1 | # signatory-sodiumoxide [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![MIT/Apache2 licensed][license-image] 2 | 3 | [Signatory] Ed25519 ([RFC 8032]) provider for [sodiumoxide]. 4 | 5 | [Documentation](https://docs.rs/signatory-sodiumoxide/) 6 | 7 | [Signatory]: https://github.com/iqlusioninc/signatory 8 | [RFC 8032]: https://tools.ietf.org/html/rfc8032 9 | [sodiumoxide]: https://github.com/dnaq/sodiumoxide 10 | 11 | ## License 12 | 13 | **Signatory** is distributed under the terms of either the MIT license or the 14 | Apache License (Version 2.0), at your option. 15 | 16 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 17 | 18 | [crate-image]: https://img.shields.io/crates/v/signatory-sodiumoxide.svg 19 | [crate-link]: https://crates.io/crates/signatory-sodiumoxide 20 | [docs-image]: https://docs.rs/signatory-sodiumoxide/badge.svg 21 | [docs-link]: https://docs.rs/signatory-sodiumoxide/ 22 | [build-image]: https://github.com/iqlusioninc/signatory/workflows/Rust/badge.svg?branch=develop&event=push 23 | [build-link]: https://github.com/iqlusioninc/signatory/actions 24 | [license-image]: https://img.shields.io/badge/license-MIT/Apache2.0-blue.svg 25 | -------------------------------------------------------------------------------- /signatory-sodiumoxide/benches/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! sodiumoxide provider benchmarks 2 | 3 | #![allow(unused_imports)] 4 | #![deny(warnings)] 5 | 6 | #[macro_use] 7 | extern crate criterion; 8 | use signatory; 9 | 10 | use criterion::Criterion; 11 | use signatory::{ 12 | ed25519, 13 | signature::{Signature, Signer, Verifier}, 14 | test_vector::TestVector, 15 | }; 16 | use signatory_sodiumoxide::{Ed25519Signer, Ed25519Verifier}; 17 | 18 | /// Test vector to use for benchmarking 19 | const TEST_VECTOR: &TestVector = &ed25519::TEST_VECTORS[4]; 20 | 21 | fn sign_ed25519(c: &mut Criterion) { 22 | let signer = Ed25519Signer::from(&ed25519::Seed::from_bytes(TEST_VECTOR.sk).unwrap()); 23 | 24 | c.bench_function("sodiumoxide: Ed25519 signer", move |b| { 25 | b.iter(|| signer.sign(TEST_VECTOR.msg)) 26 | }); 27 | } 28 | 29 | fn verify_ed25519(c: &mut Criterion) { 30 | let signature = ed25519::Signature::from_bytes(TEST_VECTOR.sig).unwrap(); 31 | let verifier = Ed25519Verifier::from(&ed25519::PublicKey::from_bytes(TEST_VECTOR.pk).unwrap()); 32 | 33 | c.bench_function("sodiumoxide: Ed25519 verifier", move |b| { 34 | b.iter(|| verifier.verify(TEST_VECTOR.msg, &signature).unwrap()) 35 | }); 36 | } 37 | 38 | criterion_group! { 39 | name = ed25519; 40 | config = Criterion::default(); 41 | targets = sign_ed25519, verify_ed25519 42 | } 43 | 44 | criterion_main!(ed25519); 45 | -------------------------------------------------------------------------------- /signatory-sodiumoxide/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Signatory Ed25519 provider for *sodiumoxide* 2 | 3 | #![no_std] 4 | #![forbid(unsafe_code)] 5 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 6 | #![doc( 7 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 8 | html_root_url = "https://docs.rs/signatory-sodiumoxide/0.19.0" 9 | )] 10 | 11 | #[cfg(test)] 12 | #[macro_use] 13 | extern crate signatory; 14 | 15 | use signatory::{ 16 | ed25519, 17 | public_key::PublicKeyed, 18 | signature::{Error, Signature, Signer, Verifier}, 19 | }; 20 | use sodiumoxide::crypto::sign::ed25519::{self as sodiumoxide_ed25519, SecretKey}; 21 | 22 | /// Ed25519 signature provider for *sodiumoxide* 23 | pub struct Ed25519Signer { 24 | secret_key: SecretKey, 25 | public_key: ed25519::PublicKey, 26 | } 27 | 28 | impl<'a> From<&'a ed25519::Seed> for Ed25519Signer { 29 | /// Create a new SodiumOxideSigner from an unexpanded seed value 30 | fn from(seed: &'a ed25519::Seed) -> Self { 31 | let sodiumoxide_seed = 32 | sodiumoxide_ed25519::Seed::from_slice(seed.as_secret_slice()).unwrap(); 33 | let (public_key, secret_key) = sodiumoxide_ed25519::keypair_from_seed(&sodiumoxide_seed); 34 | 35 | Self { 36 | secret_key, 37 | public_key: ed25519::PublicKey::from_bytes(&public_key.0).unwrap(), 38 | } 39 | } 40 | } 41 | 42 | impl PublicKeyed for Ed25519Signer { 43 | fn public_key(&self) -> Result { 44 | Ok(self.public_key) 45 | } 46 | } 47 | 48 | impl Signer for Ed25519Signer { 49 | fn try_sign(&self, msg: &[u8]) -> Result { 50 | let signature = sodiumoxide_ed25519::sign_detached(msg, &self.secret_key); 51 | Ok(Signature::from_bytes(&signature.0[..]).unwrap()) 52 | } 53 | } 54 | 55 | /// Ed25519 verifier for sodiumoxide 56 | #[derive(Clone, Debug, Eq, PartialEq)] 57 | pub struct Ed25519Verifier(sodiumoxide_ed25519::PublicKey); 58 | 59 | impl<'a> From<&'a ed25519::PublicKey> for Ed25519Verifier { 60 | fn from(public_key: &'a ed25519::PublicKey) -> Self { 61 | Ed25519Verifier(sodiumoxide_ed25519::PublicKey::from_slice(public_key.as_bytes()).unwrap()) 62 | } 63 | } 64 | 65 | impl Verifier for Ed25519Verifier { 66 | fn verify(&self, msg: &[u8], signature: &ed25519::Signature) -> Result<(), Error> { 67 | let sig = sodiumoxide_ed25519::Signature::from_slice(signature.as_ref()).unwrap(); 68 | if sodiumoxide_ed25519::verify_detached(&sig, msg, &self.0) { 69 | Ok(()) 70 | } else { 71 | Err(Error::new()) 72 | } 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::{Ed25519Signer, Ed25519Verifier}; 79 | ed25519_tests!(Ed25519Signer, Ed25519Verifier); 80 | } 81 | -------------------------------------------------------------------------------- /src/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! The Elliptic Curve Digital Signature Algorithm (ECDSA) as specified in 2 | //! FIPS 186-4 (Digital Signature Standard) 3 | 4 | use core::ops::Add; 5 | use ecdsa::curve::{CompressedPointSize, UncompressedPointSize}; 6 | use generic_array::{typenum::U1, ArrayLength}; 7 | 8 | // Use public and secret key types from the ecdsa crate 9 | // TODO(tarcieri): re-export these in a more usable manner 10 | pub use ::ecdsa::{PublicKey, SecretKey}; 11 | 12 | // Use signature and curve types from the `ecdsa` crate 13 | pub use ::ecdsa::{curve, generic_array, Asn1Signature, Curve, FixedSignature}; 14 | 15 | #[cfg(feature = "test-vectors")] 16 | pub use ::ecdsa::test_vectors; 17 | 18 | impl crate::public_key::PublicKey for PublicKey 19 | where 20 | C: Curve, 21 | ::Output: Add, 22 | CompressedPointSize: ArrayLength, 23 | UncompressedPointSize: ArrayLength, 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519: Schnorr signatures using the twisted Edwards form of Curve25519 2 | //! 3 | //! Described in RFC 8032: 4 | //! 5 | //! This module contains two convenience methods for signing and verifying 6 | //! Ed25519 signatures which work with any signer or verifier. 7 | //! 8 | //! # Example (with ed25519-dalek) 9 | //! 10 | //! ```nobuild 11 | //! extern crate signatory; 12 | //! extern crate signatory_dalek; // or another Ed25519 provider 13 | //! 14 | //! use signatory::ed25519; 15 | //! use signatory_dalek::{Ed25519Signer, Ed25519Verifier}; 16 | //! 17 | //! // Create a private key (a.k.a. a "seed") and use it to generate a signature 18 | //! let seed = ed25519::Seed::generate(); 19 | //! let signer = Ed25519Signer::from(&seed); 20 | //! let msg = "How are you? Fine, thank you."; 21 | //! 22 | //! // Sign a message 23 | //! let sig = ed25519::sign(&signer, msg.as_bytes()).unwrap(); 24 | //! 25 | //! // Get the public key for the given signer and make a verifier 26 | //! let pk = ed25519::public_key(&signer).unwrap(); 27 | //! let verifier = Ed25519Verifier::from(&pk); 28 | //! assert!(ed25519::verify(&verifier, msg.as_bytes(), &sig).is_ok()); 29 | //! ``` 30 | 31 | mod public_key; 32 | mod seed; 33 | 34 | #[cfg(feature = "test-vectors")] 35 | #[macro_use] 36 | mod test_macros; 37 | 38 | /// RFC 8032 Ed25519 test vectors 39 | #[cfg(feature = "test-vectors")] 40 | mod test_vectors; 41 | 42 | #[cfg(feature = "test-vectors")] 43 | pub use self::test_vectors::TEST_VECTORS; 44 | pub use self::{ 45 | public_key::{PublicKey, PUBLIC_KEY_SIZE}, 46 | seed::{Seed, SEED_SIZE}, 47 | }; 48 | 49 | // Import `Signature` type from the `ed25519` crate 50 | pub use ::ed25519::{Signature, SIGNATURE_LENGTH as SIGNATURE_SIZE}; 51 | -------------------------------------------------------------------------------- /src/ed25519/public_key.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 public keys 2 | 3 | use core::fmt::{self, Debug}; 4 | 5 | #[cfg(feature = "encoding")] 6 | use crate::encoding::Decode; 7 | #[cfg(all(feature = "alloc", feature = "encoding"))] 8 | use crate::encoding::Encode; 9 | #[cfg(all(feature = "alloc", feature = "encoding"))] 10 | use alloc::vec::Vec; 11 | #[cfg(feature = "encoding")] 12 | use subtle_encoding::Encoding; 13 | 14 | /// Size of an Ed25519 public key in bytes (256-bits) 15 | pub const PUBLIC_KEY_SIZE: usize = 32; 16 | 17 | /// Ed25519 public keys 18 | #[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 19 | pub struct PublicKey(pub [u8; PUBLIC_KEY_SIZE]); 20 | 21 | impl PublicKey { 22 | /// Create an Ed25519 public key from a 32-byte array 23 | pub fn new(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { 24 | PublicKey(bytes) 25 | } 26 | 27 | /// Create an Ed25519 public key from its serialized (compressed Edwards-y) form 28 | pub fn from_bytes(bytes: B) -> Option 29 | where 30 | B: AsRef<[u8]>, 31 | { 32 | if bytes.as_ref().len() == PUBLIC_KEY_SIZE { 33 | let mut public_key = [0u8; PUBLIC_KEY_SIZE]; 34 | public_key.copy_from_slice(bytes.as_ref()); 35 | Some(PublicKey(public_key)) 36 | } else { 37 | None 38 | } 39 | } 40 | 41 | /// Obtain public key as a byte array reference 42 | #[inline] 43 | pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] { 44 | &self.0 45 | } 46 | 47 | /// Convert public key into owned byte array 48 | #[inline] 49 | pub fn into_bytes(self) -> [u8; PUBLIC_KEY_SIZE] { 50 | self.0 51 | } 52 | } 53 | 54 | impl AsRef<[u8]> for PublicKey { 55 | #[inline] 56 | fn as_ref(&self) -> &[u8] { 57 | self.0.as_ref() 58 | } 59 | } 60 | 61 | impl Debug for PublicKey { 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 63 | write!(f, "ed25519::PublicKey({:?})", self.as_ref()) 64 | } 65 | } 66 | 67 | #[cfg(feature = "encoding")] 68 | impl Decode for PublicKey { 69 | /// Decode an Ed25519 public key from a byte slice with the given encoding 70 | /// (e.g. hex, Base64) 71 | fn decode( 72 | encoded_key: &[u8], 73 | encoding: &E, 74 | ) -> Result { 75 | let mut decoded_key = [0u8; PUBLIC_KEY_SIZE]; 76 | let decoded_len = encoding.decode_to_slice(encoded_key, &mut decoded_key)?; 77 | 78 | if decoded_len == PUBLIC_KEY_SIZE { 79 | Ok(Self::new(decoded_key)) 80 | } else { 81 | Err(crate::encoding::error::ErrorKind::Decode)? 82 | } 83 | } 84 | } 85 | 86 | #[cfg(all(feature = "alloc", feature = "encoding"))] 87 | impl Encode for PublicKey { 88 | /// Encode an Ed25519 seed with the given encoding (e.g. hex, Base64) 89 | fn encode(&self, encoding: &E) -> Vec { 90 | encoding.encode(self.as_bytes()) 91 | } 92 | } 93 | 94 | impl crate::public_key::PublicKey for PublicKey {} 95 | -------------------------------------------------------------------------------- /src/ed25519/seed.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 seeds: 32-bit uniformly random secret value used to derive scalars 2 | //! and nonce prefixes 3 | 4 | #[cfg(feature = "encoding")] 5 | use crate::encoding::Decode; 6 | #[cfg(all(feature = "alloc", feature = "encoding"))] 7 | use crate::encoding::Encode; 8 | #[cfg(all(feature = "encoding", feature = "alloc"))] 9 | use alloc::vec::Vec; 10 | #[cfg(feature = "getrandom")] 11 | use getrandom::getrandom; 12 | #[cfg(feature = "encoding")] 13 | use subtle_encoding::Encoding; 14 | use zeroize::Zeroize; 15 | 16 | /// Size of the "seed" value for an Ed25519 private key 17 | pub const SEED_SIZE: usize = 32; 18 | 19 | /// Size of an Ed25519 keypair (private scalar + compressed Edwards-y public key) 20 | pub const KEYPAIR_SIZE: usize = 64; 21 | 22 | /// Ed25519 seeds: derivation secrets for Ed25519 private scalars/nonce prefixes 23 | #[derive(Clone)] 24 | pub struct Seed(pub [u8; SEED_SIZE]); 25 | 26 | impl Seed { 27 | /// Create an Ed25519 seed from a 32-byte array 28 | pub fn new(bytes: [u8; SEED_SIZE]) -> Self { 29 | Seed(bytes) 30 | } 31 | 32 | /// Generate a new Ed25519 seed using the operating system's 33 | /// cryptographically secure random number generator 34 | #[cfg(feature = "getrandom")] 35 | pub fn generate() -> Self { 36 | let mut bytes = [0u8; SEED_SIZE]; 37 | getrandom(&mut bytes[..]).expect("RNG failure!"); 38 | Self::new(bytes) 39 | } 40 | 41 | /// Create an Ed25519 seed from a byte slice, returning `KeyInvalid` if the 42 | /// slice is not the correct size (32-bytes) 43 | pub fn from_bytes(bytes: B) -> Option 44 | where 45 | B: AsRef<[u8]>, 46 | { 47 | if bytes.as_ref().len() == SEED_SIZE { 48 | let mut seed = [0u8; SEED_SIZE]; 49 | seed.copy_from_slice(bytes.as_ref()); 50 | Some(Seed::new(seed)) 51 | } else { 52 | None 53 | } 54 | } 55 | 56 | /// Create an Ed25519 seed from a keypair: i.e. a seed and its assocaited 57 | /// public key (i.e. compressed Edwards-y coordinate) 58 | pub fn from_keypair(keypair: &[u8]) -> Option { 59 | if keypair.len() == KEYPAIR_SIZE { 60 | // TODO: ensure public key part of keypair is correct 61 | Self::from_bytes(&keypair[..SEED_SIZE]) 62 | } else { 63 | None 64 | } 65 | } 66 | 67 | /// Decode a `Seed` from an encoded (hex or Base64) Ed25519 keypair 68 | #[cfg(feature = "encoding")] 69 | pub fn decode_keypair( 70 | encoded_keypair: &[u8], 71 | encoding: &E, 72 | ) -> Result { 73 | let mut decoded_keypair = [0u8; SEED_SIZE * 2]; 74 | let decoded_len = encoding 75 | .decode_to_slice(encoded_keypair, &mut decoded_keypair) 76 | .map_err(|_| signature::Error::new())?; 77 | 78 | if decoded_len == SEED_SIZE * 2 { 79 | Self::from_keypair(&decoded_keypair).ok_or_else(signature::Error::new) 80 | } else { 81 | Err(signature::Error::new()) 82 | } 83 | } 84 | 85 | /// Expose the secret values of the `Seed` as a byte slice 86 | pub fn as_secret_slice(&self) -> &[u8] { 87 | self.0.as_ref() 88 | } 89 | } 90 | 91 | #[cfg(feature = "encoding")] 92 | impl Decode for Seed { 93 | /// Decode an Ed25519 seed from a byte slice with the given encoding 94 | /// (e.g. hex, Base64) 95 | fn decode( 96 | encoded_seed: &[u8], 97 | encoding: &E, 98 | ) -> Result { 99 | let mut decoded_seed = [0u8; SEED_SIZE]; 100 | let decoded_len = encoding.decode_to_slice(encoded_seed, &mut decoded_seed)?; 101 | 102 | if decoded_len == SEED_SIZE { 103 | Ok(Self::new(decoded_seed)) 104 | } else { 105 | Err(crate::encoding::error::ErrorKind::Decode)? 106 | } 107 | } 108 | } 109 | 110 | #[cfg(all(feature = "encoding", feature = "alloc"))] 111 | impl Encode for Seed { 112 | /// Encode an Ed25519 seed with the given encoding (e.g. hex, Base64) 113 | fn encode(&self, encoding: &E) -> Vec { 114 | encoding.encode(self.as_secret_slice()) 115 | } 116 | } 117 | 118 | impl Drop for Seed { 119 | fn drop(&mut self) { 120 | self.0.zeroize(); 121 | } 122 | } 123 | 124 | impl From<[u8; 32]> for Seed { 125 | fn from(bytes: [u8; SEED_SIZE]) -> Self { 126 | Seed::new(bytes) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/ed25519/test_macros.rs: -------------------------------------------------------------------------------- 1 | //! Macro for generating shared tests for all software Ed25519 implementations 2 | 3 | /// Generate tests for Ed25519 4 | #[macro_export] 5 | macro_rules! ed25519_tests { 6 | ($signer:ident, $verifier:ident) => { 7 | use $crate::ed25519::{self, SIGNATURE_SIZE, TEST_VECTORS}; 8 | use $crate::signature::{Signer as _, Verifier as _}; 9 | 10 | #[test] 11 | fn sign_rfc8032_test_vectors() { 12 | for vector in TEST_VECTORS { 13 | let seed = ed25519::Seed::from_bytes(vector.sk).unwrap(); 14 | let signer = $signer::from(&seed); 15 | assert_eq!(signer.sign(vector.msg).as_ref(), vector.sig); 16 | } 17 | } 18 | 19 | #[test] 20 | fn verify_rfc8032_test_vectors() { 21 | use $crate::signature::Signature; 22 | for vector in TEST_VECTORS { 23 | let pk = ed25519::PublicKey::from_bytes(vector.pk).unwrap(); 24 | let verifier = $verifier::from(&pk); 25 | let sig = ed25519::Signature::from_bytes(vector.sig).unwrap(); 26 | assert!( 27 | verifier.verify(vector.msg, &sig).is_ok(), 28 | "expected signature to verify" 29 | ); 30 | } 31 | } 32 | 33 | #[test] 34 | fn rejects_tweaked_rfc8032_test_vectors() { 35 | use $crate::signature::Signature; 36 | for vector in TEST_VECTORS { 37 | let pk = ed25519::PublicKey::from_bytes(vector.pk).unwrap(); 38 | let verifier = $verifier::from(&pk); 39 | 40 | let mut tweaked_sig = [0u8; SIGNATURE_SIZE]; 41 | tweaked_sig.copy_from_slice(vector.sig); 42 | tweaked_sig[0] ^= 0x42; 43 | 44 | let result = verifier.verify( 45 | vector.msg, 46 | &ed25519::Signature::from_bytes(&tweaked_sig[..]).unwrap(), 47 | ); 48 | 49 | assert!( 50 | result.is_err(), 51 | "expected signature verification failure but it succeeded" 52 | ); 53 | } 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/ed25519/test_vectors.rs: -------------------------------------------------------------------------------- 1 | use crate::test_vector::{TestVector, TestVectorAlgorithm}; 2 | 3 | /// Ed25519 test vectors (from RFC 8032, converted to Rust bytestring literals) 4 | #[rustfmt::skip] 5 | pub const TEST_VECTORS: &[TestVector] = &[ 6 | TestVector { 7 | alg: TestVectorAlgorithm::Ed25519, 8 | sk: b"\x9D\x61\xB1\x9D\xEF\xFD\x5A\x60\xBA\x84\x4A\xF4\x92\xEC\x2C\xC4\x44\x49\xC5\x69\x7B\x32\x69\x19\x70\x3B\xAC\x03\x1C\xAE\x7F\x60", 9 | pk: b"\xD7\x5A\x98\x01\x82\xB1\x0A\xB7\xD5\x4B\xFE\xD3\xC9\x64\x07\x3A\x0E\xE1\x72\xF3\xDA\xA6\x23\x25\xAF\x02\x1A\x68\xF7\x07\x51\x1A", 10 | nonce: None, // Ed25519 uses deterministic nonces 11 | msg: b"", 12 | sig: b"\xE5\x56\x43\x00\xC3\x60\xAC\x72\x90\x86\xE2\xCC\x80\x6E\x82\x8A\x84\x87\x7F\x1E\xB8\xE5\xD9\x74\xD8\x73\xE0\x65\x22\x49\x01\x55\x5F\xB8\x82\x15\x90\xA3\x3B\xAC\xC6\x1E\x39\x70\x1C\xF9\xB4\x6B\xD2\x5B\xF5\xF0\x59\x5B\xBE\x24\x65\x51\x41\x43\x8E\x7A\x10\x0B", 13 | pass: true 14 | }, 15 | TestVector { 16 | alg: TestVectorAlgorithm::Ed25519, 17 | sk: b"\x4C\xCD\x08\x9B\x28\xFF\x96\xDA\x9D\xB6\xC3\x46\xEC\x11\x4E\x0F\x5B\x8A\x31\x9F\x35\xAB\xA6\x24\xDA\x8C\xF6\xED\x4F\xB8\xA6\xFB", 18 | pk: b"\x3D\x40\x17\xC3\xE8\x43\x89\x5A\x92\xB7\x0A\xA7\x4D\x1B\x7E\xBC\x9C\x98\x2C\xCF\x2E\xC4\x96\x8C\xC0\xCD\x55\xF1\x2A\xF4\x66\x0C", 19 | nonce: None, 20 | msg: b"\x72", 21 | sig: b"\x92\xA0\x09\xA9\xF0\xD4\xCA\xB8\x72\x0E\x82\x0B\x5F\x64\x25\x40\xA2\xB2\x7B\x54\x16\x50\x3F\x8F\xB3\x76\x22\x23\xEB\xDB\x69\xDA\x08\x5A\xC1\xE4\x3E\x15\x99\x6E\x45\x8F\x36\x13\xD0\xF1\x1D\x8C\x38\x7B\x2E\xAE\xB4\x30\x2A\xEE\xB0\x0D\x29\x16\x12\xBB\x0C\x00", 22 | pass: true 23 | }, 24 | TestVector { 25 | alg: TestVectorAlgorithm::Ed25519, 26 | sk: b"\xC5\xAA\x8D\xF4\x3F\x9F\x83\x7B\xED\xB7\x44\x2F\x31\xDC\xB7\xB1\x66\xD3\x85\x35\x07\x6F\x09\x4B\x85\xCE\x3A\x2E\x0B\x44\x58\xF7", 27 | pk: b"\xFC\x51\xCD\x8E\x62\x18\xA1\xA3\x8D\xA4\x7E\xD0\x02\x30\xF0\x58\x08\x16\xED\x13\xBA\x33\x03\xAC\x5D\xEB\x91\x15\x48\x90\x80\x25", 28 | nonce: None, 29 | msg: b"\xAF\x82", 30 | sig: b"\x62\x91\xD6\x57\xDE\xEC\x24\x02\x48\x27\xE6\x9C\x3A\xBE\x01\xA3\x0C\xE5\x48\xA2\x84\x74\x3A\x44\x5E\x36\x80\xD7\xDB\x5A\xC3\xAC\x18\xFF\x9B\x53\x8D\x16\xF2\x90\xAE\x67\xF7\x60\x98\x4D\xC6\x59\x4A\x7C\x15\xE9\x71\x6E\xD2\x8D\xC0\x27\xBE\xCE\xEA\x1E\xC4\x0A", 31 | pass: true 32 | }, 33 | TestVector { 34 | alg: TestVectorAlgorithm::Ed25519, 35 | sk: b"\xF5\xE5\x76\x7C\xF1\x53\x31\x95\x17\x63\x0F\x22\x68\x76\xB8\x6C\x81\x60\xCC\x58\x3B\xC0\x13\x74\x4C\x6B\xF2\x55\xF5\xCC\x0E\xE5", 36 | pk: b"\x27\x81\x17\xFC\x14\x4C\x72\x34\x0F\x67\xD0\xF2\x31\x6E\x83\x86\xCE\xFF\xBF\x2B\x24\x28\xC9\xC5\x1F\xEF\x7C\x59\x7F\x1D\x42\x6E", 37 | nonce: None, 38 | msg: b"\x08\xB8\xB2\xB7\x33\x42\x42\x43\x76\x0F\xE4\x26\xA4\xB5\x49\x08\x63\x21\x10\xA6\x6C\x2F\x65\x91\xEA\xBD\x33\x45\xE3\xE4\xEB\x98\xFA\x6E\x26\x4B\xF0\x9E\xFE\x12\xEE\x50\xF8\xF5\x4E\x9F\x77\xB1\xE3\x55\xF6\xC5\x05\x44\xE2\x3F\xB1\x43\x3D\xDF\x73\xBE\x84\xD8\x79\xDE\x7C\x00\x46\xDC\x49\x96\xD9\xE7\x73\xF4\xBC\x9E\xFE\x57\x38\x82\x9A\xDB\x26\xC8\x1B\x37\xC9\x3A\x1B\x27\x0B\x20\x32\x9D\x65\x86\x75\xFC\x6E\xA5\x34\xE0\x81\x0A\x44\x32\x82\x6B\xF5\x8C\x94\x1E\xFB\x65\xD5\x7A\x33\x8B\xBD\x2E\x26\x64\x0F\x89\xFF\xBC\x1A\x85\x8E\xFC\xB8\x55\x0E\xE3\xA5\xE1\x99\x8B\xD1\x77\xE9\x3A\x73\x63\xC3\x44\xFE\x6B\x19\x9E\xE5\xD0\x2E\x82\xD5\x22\xC4\xFE\xBA\x15\x45\x2F\x80\x28\x8A\x82\x1A\x57\x91\x16\xEC\x6D\xAD\x2B\x3B\x31\x0D\xA9\x03\x40\x1A\xA6\x21\x00\xAB\x5D\x1A\x36\x55\x3E\x06\x20\x3B\x33\x89\x0C\xC9\xB8\x32\xF7\x9E\xF8\x05\x60\xCC\xB9\xA3\x9C\xE7\x67\x96\x7E\xD6\x28\xC6\xAD\x57\x3C\xB1\x16\xDB\xEF\xEF\xD7\x54\x99\xDA\x96\xBD\x68\xA8\xA9\x7B\x92\x8A\x8B\xBC\x10\x3B\x66\x21\xFC\xDE\x2B\xEC\xA1\x23\x1D\x20\x6B\xE6\xCD\x9E\xC7\xAF\xF6\xF6\xC9\x4F\xCD\x72\x04\xED\x34\x55\xC6\x8C\x83\xF4\xA4\x1D\xA4\xAF\x2B\x74\xEF\x5C\x53\xF1\xD8\xAC\x70\xBD\xCB\x7E\xD1\x85\xCE\x81\xBD\x84\x35\x9D\x44\x25\x4D\x95\x62\x9E\x98\x55\xA9\x4A\x7C\x19\x58\xD1\xF8\xAD\xA5\xD0\x53\x2E\xD8\xA5\xAA\x3F\xB2\xD1\x7B\xA7\x0E\xB6\x24\x8E\x59\x4E\x1A\x22\x97\xAC\xBB\xB3\x9D\x50\x2F\x1A\x8C\x6E\xB6\xF1\xCE\x22\xB3\xDE\x1A\x1F\x40\xCC\x24\x55\x41\x19\xA8\x31\xA9\xAA\xD6\x07\x9C\xAD\x88\x42\x5D\xE6\xBD\xE1\xA9\x18\x7E\xBB\x60\x92\xCF\x67\xBF\x2B\x13\xFD\x65\xF2\x70\x88\xD7\x8B\x7E\x88\x3C\x87\x59\xD2\xC4\xF5\xC6\x5A\xDB\x75\x53\x87\x8A\xD5\x75\xF9\xFA\xD8\x78\xE8\x0A\x0C\x9B\xA6\x3B\xCB\xCC\x27\x32\xE6\x94\x85\xBB\xC9\xC9\x0B\xFB\xD6\x24\x81\xD9\x08\x9B\xEC\xCF\x80\xCF\xE2\xDF\x16\xA2\xCF\x65\xBD\x92\xDD\x59\x7B\x07\x07\xE0\x91\x7A\xF4\x8B\xBB\x75\xFE\xD4\x13\xD2\x38\xF5\x55\x5A\x7A\x56\x9D\x80\xC3\x41\x4A\x8D\x08\x59\xDC\x65\xA4\x61\x28\xBA\xB2\x7A\xF8\x7A\x71\x31\x4F\x31\x8C\x78\x2B\x23\xEB\xFE\x80\x8B\x82\xB0\xCE\x26\x40\x1D\x2E\x22\xF0\x4D\x83\xD1\x25\x5D\xC5\x1A\xDD\xD3\xB7\x5A\x2B\x1A\xE0\x78\x45\x04\xDF\x54\x3A\xF8\x96\x9B\xE3\xEA\x70\x82\xFF\x7F\xC9\x88\x8C\x14\x4D\xA2\xAF\x58\x42\x9E\xC9\x60\x31\xDB\xCA\xD3\xDA\xD9\xAF\x0D\xCB\xAA\xAF\x26\x8C\xB8\xFC\xFF\xEA\xD9\x4F\x3C\x7C\xA4\x95\xE0\x56\xA9\xB4\x7A\xCD\xB7\x51\xFB\x73\xE6\x66\xC6\xC6\x55\xAD\xE8\x29\x72\x97\xD0\x7A\xD1\xBA\x5E\x43\xF1\xBC\xA3\x23\x01\x65\x13\x39\xE2\x29\x04\xCC\x8C\x42\xF5\x8C\x30\xC0\x4A\xAF\xDB\x03\x8D\xDA\x08\x47\xDD\x98\x8D\xCD\xA6\xF3\xBF\xD1\x5C\x4B\x4C\x45\x25\x00\x4A\xA0\x6E\xEF\xF8\xCA\x61\x78\x3A\xAC\xEC\x57\xFB\x3D\x1F\x92\xB0\xFE\x2F\xD1\xA8\x5F\x67\x24\x51\x7B\x65\xE6\x14\xAD\x68\x08\xD6\xF6\xEE\x34\xDF\xF7\x31\x0F\xDC\x82\xAE\xBF\xD9\x04\xB0\x1E\x1D\xC5\x4B\x29\x27\x09\x4B\x2D\xB6\x8D\x6F\x90\x3B\x68\x40\x1A\xDE\xBF\x5A\x7E\x08\xD7\x8F\xF4\xEF\x5D\x63\x65\x3A\x65\x04\x0C\xF9\xBF\xD4\xAC\xA7\x98\x4A\x74\xD3\x71\x45\x98\x67\x80\xFC\x0B\x16\xAC\x45\x16\x49\xDE\x61\x88\xA7\xDB\xDF\x19\x1F\x64\xB5\xFC\x5E\x2A\xB4\x7B\x57\xF7\xF7\x27\x6C\xD4\x19\xC1\x7A\x3C\xA8\xE1\xB9\x39\xAE\x49\xE4\x88\xAC\xBA\x6B\x96\x56\x10\xB5\x48\x01\x09\xC8\xB1\x7B\x80\xE1\xB7\xB7\x50\xDF\xC7\x59\x8D\x5D\x50\x11\xFD\x2D\xCC\x56\x00\xA3\x2E\xF5\xB5\x2A\x1E\xCC\x82\x0E\x30\x8A\xA3\x42\x72\x1A\xAC\x09\x43\xBF\x66\x86\xB6\x4B\x25\x79\x37\x65\x04\xCC\xC4\x93\xD9\x7E\x6A\xED\x3F\xB0\xF9\xCD\x71\xA4\x3D\xD4\x97\xF0\x1F\x17\xC0\xE2\xCB\x37\x97\xAA\x2A\x2F\x25\x66\x56\x16\x8E\x6C\x49\x6A\xFC\x5F\xB9\x32\x46\xF6\xB1\x11\x63\x98\xA3\x46\xF1\xA6\x41\xF3\xB0\x41\xE9\x89\xF7\x91\x4F\x90\xCC\x2C\x7F\xFF\x35\x78\x76\xE5\x06\xB5\x0D\x33\x4B\xA7\x7C\x22\x5B\xC3\x07\xBA\x53\x71\x52\xF3\xF1\x61\x0E\x4E\xAF\xE5\x95\xF6\xD9\xD9\x0D\x11\xFA\xA9\x33\xA1\x5E\xF1\x36\x95\x46\x86\x8A\x7F\x3A\x45\xA9\x67\x68\xD4\x0F\xD9\xD0\x34\x12\xC0\x91\xC6\x31\x5C\xF4\xFD\xE7\xCB\x68\x60\x69\x37\x38\x0D\xB2\xEA\xAA\x70\x7B\x4C\x41\x85\xC3\x2E\xDD\xCD\xD3\x06\x70\x5E\x4D\xC1\xFF\xC8\x72\xEE\xEE\x47\x5A\x64\xDF\xAC\x86\xAB\xA4\x1C\x06\x18\x98\x3F\x87\x41\xC5\xEF\x68\xD3\xA1\x01\xE8\xA3\xB8\xCA\xC6\x0C\x90\x5C\x15\xFC\x91\x08\x40\xB9\x4C\x00\xA0\xB9\xD0", 39 | sig: b"\x0A\xAB\x4C\x90\x05\x01\xB3\xE2\x4D\x7C\xDF\x46\x63\x32\x6A\x3A\x87\xDF\x5E\x48\x43\xB2\xCB\xDB\x67\xCB\xF6\xE4\x60\xFE\xC3\x50\xAA\x53\x71\xB1\x50\x8F\x9F\x45\x28\xEC\xEA\x23\xC4\x36\xD9\x4B\x5E\x8F\xCD\x4F\x68\x1E\x30\xA6\xAC\x00\xA9\x70\x4A\x18\x8A\x03", 40 | pass: true 41 | }, 42 | TestVector { 43 | alg: TestVectorAlgorithm::Ed25519, 44 | sk: b"\x83\x3F\xE6\x24\x09\x23\x7B\x9D\x62\xEC\x77\x58\x75\x20\x91\x1E\x9A\x75\x9C\xEC\x1D\x19\x75\x5B\x7D\xA9\x01\xB9\x6D\xCA\x3D\x42", 45 | pk: b"\xEC\x17\x2B\x93\xAD\x5E\x56\x3B\xF4\x93\x2C\x70\xE1\x24\x50\x34\xC3\x54\x67\xEF\x2E\xFD\x4D\x64\xEB\xF8\x19\x68\x34\x67\xE2\xBF", 46 | nonce: None, 47 | msg: b"\xDD\xAF\x35\xA1\x93\x61\x7A\xBA\xCC\x41\x73\x49\xAE\x20\x41\x31\x12\xE6\xFA\x4E\x89\xA9\x7E\xA2\x0A\x9E\xEE\xE6\x4B\x55\xD3\x9A\x21\x92\x99\x2A\x27\x4F\xC1\xA8\x36\xBA\x3C\x23\xA3\xFE\xEB\xBD\x45\x4D\x44\x23\x64\x3C\xE8\x0E\x2A\x9A\xC9\x4F\xA5\x4C\xA4\x9F", 48 | sig: b"\xDC\x2A\x44\x59\xE7\x36\x96\x33\xA5\x2B\x1B\xF2\x77\x83\x9A\x00\x20\x10\x09\xA3\xEF\xBF\x3E\xCB\x69\xBE\xA2\x18\x6C\x26\xB5\x89\x09\x35\x1F\xC9\xAC\x90\xB3\xEC\xFD\xFB\xC7\xC6\x64\x31\xE0\x30\x3D\xCA\x17\x9C\x13\x8A\xC1\x7A\xD9\xBE\xF1\x17\x73\x31\xA7\x04", 49 | pass: true 50 | }, 51 | ]; 52 | -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | //! Support for encoding and decoding serialization formats (hex and Base64) 2 | //! with implementations that do not branch on potentially secret data, such 3 | //! as cryptographic keys. 4 | 5 | #[macro_use] 6 | mod macros; 7 | 8 | pub use subtle_encoding::{Base64, Hex, Identity}; 9 | mod decode; 10 | #[cfg(feature = "alloc")] 11 | mod encode; 12 | pub mod error; 13 | #[cfg(feature = "pkcs8")] 14 | pub mod pkcs8; 15 | 16 | #[cfg(feature = "alloc")] 17 | pub use self::encode::Encode; 18 | #[cfg(feature = "pkcs8")] 19 | pub use self::pkcs8::FromPkcs8; 20 | pub use self::{decode::Decode, error::Error}; 21 | 22 | /// Mode to use for newly created files 23 | #[cfg(all(unix, feature = "std"))] 24 | pub const FILE_MODE: u32 = 0o600; 25 | -------------------------------------------------------------------------------- /src/encoding/decode.rs: -------------------------------------------------------------------------------- 1 | //! Support for decoding keys and signatures in hex or Base64. 2 | //! 3 | //! Uses a constant-time implementation which is suitable for use with 4 | //! secret keys. 5 | 6 | use super::error::Error; 7 | #[cfg(feature = "std")] 8 | use super::error::ErrorKind; 9 | #[cfg(feature = "std")] 10 | use std::{fs::File, io::Read, path::Path}; 11 | use subtle_encoding::Encoding; 12 | #[cfg(feature = "std")] 13 | use zeroize::Zeroize; 14 | 15 | /// Decode keys/signatures from encoded data (e.g. hex, Base64). 16 | /// Uses constant time encoder/decoder implementations. 17 | pub trait Decode: Sized { 18 | /// Decode the given byte slice using the provided `Encoding`, returning 19 | /// the decoded value or a `Error`. 20 | fn decode(encoded: &[u8], encoding: &E) -> Result; 21 | 22 | /// Decode the given string-alike type with the provided `Encoding`, 23 | /// returning the decoded value or a `Error`. 24 | fn decode_from_str(encoded_str: S, encoding: &E) -> Result 25 | where 26 | S: AsRef, 27 | E: Encoding, 28 | { 29 | Self::decode(encoded_str.as_ref().as_bytes(), encoding) 30 | } 31 | 32 | /// Decode the data read from the given `io::Read` type with the provided 33 | /// `Encoding`, returning the decoded value or a `Error`. 34 | #[cfg(feature = "std")] 35 | fn decode_from_reader(reader: &mut R, encoding: &E) -> Result 36 | where 37 | R: Read, 38 | E: Encoding, 39 | { 40 | let mut bytes = vec![]; 41 | reader.read_to_end(bytes.as_mut())?; 42 | 43 | let result = Self::decode(&bytes, encoding); 44 | bytes.zeroize(); 45 | result 46 | } 47 | 48 | /// Read a file at the given path, decoding the data it contains using 49 | /// the provided `Encoding`, returning the decoded value or a `Error`. 50 | #[cfg(feature = "std")] 51 | fn decode_from_file(path: P, encoding: &E) -> Result 52 | where 53 | P: AsRef, 54 | E: Encoding, 55 | { 56 | let path = path.as_ref(); 57 | let mut file = File::open(path).map_err(|e| { 58 | Error::new( 59 | ErrorKind::Io, 60 | Some(&format!("couldn't open {}: {}", path.display(), e)), 61 | ) 62 | })?; 63 | 64 | Self::decode_from_reader(&mut file, encoding) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/encoding/encode.rs: -------------------------------------------------------------------------------- 1 | //! Support for decoding keys and signatures in hex or Base64. 2 | //! 3 | //! Uses a constant-time implementation which is suitable for use with 4 | //! secret keys. 5 | 6 | #[cfg(feature = "std")] 7 | use super::error::ErrorKind; 8 | #[cfg(all(unix, feature = "std"))] 9 | use super::FILE_MODE; 10 | use crate::encoding::Error; 11 | use alloc::{string::String, vec::Vec}; 12 | #[cfg(feature = "std")] 13 | use std::{fs::File, io::Write, path::Path}; 14 | #[cfg(all(unix, feature = "std"))] 15 | use std::{fs::OpenOptions, os::unix::fs::OpenOptionsExt}; 16 | use subtle_encoding::Encoding; 17 | #[cfg(feature = "std")] 18 | use zeroize::Zeroize; 19 | 20 | /// Serialize keys/signatures with the given encoding (e.g. hex, Base64). 21 | /// Uses constant time encoder/decoder implementations. 22 | pub trait Encode: Sized { 23 | /// Encode `self` to a `Vec` using the provided `Encoding`, returning 24 | /// the encoded value or a `Error`. 25 | fn encode(&self, encoding: &E) -> Vec; 26 | 27 | /// Encode `self` to a `String` using the provided `Encoding`, returning 28 | /// the encoded value or a `Error`. 29 | fn encode_to_string(&self, encoding: &E) -> Result { 30 | String::from_utf8(self.encode(encoding)).map_err(|_| ErrorKind::Encode.into()) 31 | } 32 | 33 | /// Encode `self` with the given `Encoding`, writing the result to the 34 | /// supplied `io::Write` type, returning the number of bytes written or a `Error`. 35 | #[cfg(feature = "std")] 36 | fn encode_to_writer(&self, writer: &mut W, encoding: &E) -> Result 37 | where 38 | W: Write, 39 | E: Encoding, 40 | { 41 | let mut encoded_bytes = self.encode(encoding); 42 | writer.write_all(encoded_bytes.as_ref())?; 43 | encoded_bytes.zeroize(); 44 | Ok(encoded_bytes.len()) 45 | } 46 | 47 | /// Encode `self` and write it to a file at the given path, returning the 48 | /// resulting `File` or a `Error`. 49 | /// 50 | /// If the file does not exist, it will be created with a mode of 51 | /// `FILE_MODE` (i.e. `600`). If the file does exist, it will be erased 52 | /// and replaced. 53 | #[cfg(all(unix, feature = "std"))] 54 | fn encode_to_file(&self, path: P, encoding: &E) -> Result 55 | where 56 | P: AsRef, 57 | E: Encoding, 58 | { 59 | let path = path.as_ref(); 60 | let mut file = OpenOptions::new() 61 | .create(true) 62 | .write(true) 63 | .truncate(true) 64 | .mode(FILE_MODE) 65 | .open(path) 66 | .map_err(|e| { 67 | Error::new( 68 | ErrorKind::Io, 69 | Some(&format!("couldn't create {}: {}", path.display(), e)), 70 | ) 71 | })?; 72 | 73 | self.encode_to_writer(&mut file, encoding)?; 74 | Ok(file) 75 | } 76 | 77 | /// Encode `self` and write it to a file at the given path, returning the 78 | /// resulting `File` or a `Error`. 79 | /// 80 | /// If the file does not exist, it will be created. 81 | #[cfg(all(not(unix), feature = "std"))] 82 | fn encode_to_file(&self, path: P, encoding: &E) -> Result 83 | where 84 | P: AsRef, 85 | E: Encoding, 86 | { 87 | let path = path.as_ref(); 88 | let mut file = File::create(path).map_err(|e| { 89 | Error::new( 90 | ErrorKind::Io, 91 | Some(&format!("couldn't create {}: {}", path.display(), e)), 92 | ) 93 | })?; 94 | 95 | self.encode_to_writer(&mut file, encoding)?; 96 | Ok(file) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/encoding/error.rs: -------------------------------------------------------------------------------- 1 | //! Encoding errors 2 | 3 | #[cfg(feature = "alloc")] 4 | use alloc::{borrow::ToOwned, string::String}; 5 | use core::fmt::{self, Display}; 6 | #[cfg(feature = "std")] 7 | use std::io; 8 | 9 | /// Encoding error 10 | #[derive(Clone, Debug)] 11 | pub struct Error { 12 | /// Kind of error 13 | kind: ErrorKind, 14 | 15 | /// Optional message to associate with the error 16 | #[cfg(feature = "alloc")] 17 | msg: Option, 18 | } 19 | 20 | impl Error { 21 | /// Create a new error of the given kind 22 | #[cfg_attr(not(feature = "alloc"), allow(unused_variables))] 23 | pub fn new(kind: ErrorKind, msg: Option<&str>) -> Self { 24 | Self { 25 | kind, 26 | #[cfg(feature = "alloc")] 27 | msg: msg.map(ToOwned::to_owned), 28 | } 29 | } 30 | 31 | /// Obtain the error's `ErrorKind` 32 | pub fn kind(&self) -> ErrorKind { 33 | self.kind 34 | } 35 | 36 | /// Get the message associated with this error (if available) 37 | #[cfg(feature = "alloc")] 38 | pub fn msg(&self) -> Option<&str> { 39 | self.msg.as_ref().map(AsRef::as_ref) 40 | } 41 | } 42 | 43 | impl Display for Error { 44 | #[cfg(not(feature = "alloc"))] 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | write!(f, "{}", self.kind()) 47 | } 48 | 49 | #[cfg(feature = "alloc")] 50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | if let Some(msg) = &self.msg { 52 | write!(f, "{}: {}", self.kind(), msg) 53 | } else { 54 | write!(f, "{}", self.kind()) 55 | } 56 | } 57 | } 58 | 59 | impl From for Error { 60 | fn from(kind: ErrorKind) -> Self { 61 | Error::new(kind, None) 62 | } 63 | } 64 | 65 | #[cfg(feature = "std")] 66 | impl From for Error { 67 | fn from(_err: io::Error) -> Self { 68 | ErrorKind::Io.into() 69 | } 70 | } 71 | 72 | impl From for Error { 73 | fn from(_err: subtle_encoding::Error) -> Self { 74 | // TODO(tarcieri): preserve more error information here? 75 | ErrorKind::Encode.into() 76 | } 77 | } 78 | 79 | /// Kinds of errors 80 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 81 | pub enum ErrorKind { 82 | /// Decoding error 83 | Decode, 84 | 85 | /// Encoding error 86 | Encode, 87 | 88 | /// Input/output error 89 | Io, 90 | } 91 | 92 | impl Display for ErrorKind { 93 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 94 | let msg = match self { 95 | ErrorKind::Decode => "decode error", 96 | ErrorKind::Encode => "encode error", 97 | ErrorKind::Io => "i/o error", 98 | }; 99 | 100 | write!(f, "{}", msg) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/encoding/macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros for performing arithmetic which checks for overflow/underflow in 2 | //! in a more succinct manner 3 | 4 | #![allow(unused_macros)] 5 | 6 | /// Checked addition 7 | macro_rules! add { 8 | ($a:expr, $b:expr) => { 9 | $a.checked_add($b).expect("overflow") 10 | }; 11 | } 12 | 13 | /// Checked subtraction 14 | macro_rules! sub { 15 | ($a:expr, $b:expr) => { 16 | $a.checked_sub($b).expect("underflow") 17 | }; 18 | } 19 | 20 | /// Checked multiplication 21 | macro_rules! mul { 22 | ($a:expr, $b:expr) => { 23 | $a.checked_mul($b).expect("overflow") 24 | }; 25 | } 26 | 27 | /// Checked division 28 | macro_rules! div { 29 | ($a:expr, $b:expr) => { 30 | $a.checked_div($b).expect("overflow") 31 | }; 32 | } 33 | 34 | /// Checked right shift 35 | macro_rules! shr { 36 | ($a:expr, $b:expr) => { 37 | $a.checked_shr($b).expect("overflow") 38 | }; 39 | } 40 | 41 | /// Checked left shift 42 | macro_rules! shl { 43 | ($a:expr, $b:expr) => { 44 | $a.checked_shl($b).expect("overflow") 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/encoding/pkcs8.rs: -------------------------------------------------------------------------------- 1 | //! Support for the **PKCS#8** private key format described in [RFC 5208] 2 | //! and [RFC 5915]. 3 | //! 4 | //! [RFC 5208]: https://tools.ietf.org/html/rfc5208 5 | //! [RFC 5915]: https://tools.ietf.org/html/rfc5915 6 | 7 | #[cfg(all(unix, feature = "std"))] 8 | use super::FILE_MODE; 9 | use crate::encoding::error::Error; 10 | #[cfg(feature = "std")] 11 | use crate::encoding::error::ErrorKind; 12 | #[cfg(feature = "alloc")] 13 | use alloc::vec::Vec; 14 | #[cfg(feature = "std")] 15 | use std::{ 16 | fs::File, 17 | io::{Read, Write}, 18 | path::Path, 19 | }; 20 | #[cfg(all(unix, feature = "std"))] 21 | use std::{fs::OpenOptions, os::unix::fs::OpenOptionsExt}; 22 | #[cfg(feature = "std")] 23 | use zeroize::Zeroize; 24 | 25 | /// Load this type from a **PKCS#8** private key 26 | pub trait FromPkcs8: Sized { 27 | /// Load from the given **PKCS#8**-encoded private key, returning `Self` 28 | /// or an error if the given data couldn't be loaded. 29 | fn from_pkcs8>(secret_key: K) -> Result; 30 | 31 | /// Read **PKCS#8** data from the given `std::io::Read`. 32 | #[cfg(feature = "std")] 33 | fn read_pkcs8(mut reader: R) -> Result { 34 | let mut bytes = vec![]; 35 | reader.read_to_end(&mut bytes)?; 36 | 37 | let result = Self::from_pkcs8(&bytes); 38 | bytes.zeroize(); 39 | result 40 | } 41 | 42 | /// Read **PKCS#8** data from the file at the given path. 43 | #[cfg(feature = "std")] 44 | fn from_pkcs8_file>(path: P) -> Result { 45 | let path = path.as_ref(); 46 | let file = File::open(path).map_err(|e| { 47 | Error::new( 48 | ErrorKind::Io, 49 | Some(&format!("couldn't open {}: {}", path.display(), e)), 50 | ) 51 | })?; 52 | Self::read_pkcs8(file) 53 | } 54 | } 55 | 56 | /// Generate a random **PKCS#8** private key of this type 57 | #[cfg(feature = "std")] 58 | pub trait GeneratePkcs8: Sized + FromPkcs8 { 59 | /// Randomly generate a **PKCS#8** private key for this type loadable 60 | /// via `from_pkcs8()`. 61 | fn generate_pkcs8() -> Result; 62 | 63 | /// Write randomly generated **PKCS#8** private key to the file at the 64 | /// given path. 65 | /// 66 | /// If the file does not exist, it will be created with a mode of 67 | /// `FILE_MODE` (i.e. `600`). If the file does exist, it will be erased 68 | /// and replaced. 69 | #[cfg(unix)] 70 | fn generate_pkcs8_file>(path: P) -> Result { 71 | let path = path.as_ref(); 72 | let secret_key = Self::generate_pkcs8()?; 73 | 74 | let mut file = OpenOptions::new() 75 | .create(true) 76 | .write(true) 77 | .truncate(true) 78 | .mode(FILE_MODE) 79 | .open(path) 80 | .map_err(|e| { 81 | Error::new( 82 | ErrorKind::Io, 83 | Some(&format!("couldn't create {}: {}", path.display(), e)), 84 | ) 85 | })?; 86 | 87 | file.write_all(secret_key.as_ref())?; 88 | Ok(file) 89 | } 90 | 91 | /// Encode `self` and write it to a file at the given path, returning the 92 | /// resulting `File` or a `Error`. 93 | /// 94 | /// If the file does not exist, it will be created. 95 | #[cfg(not(unix))] 96 | fn generate_pkcs8_file>(path: P) -> Result { 97 | let path = path.as_ref(); 98 | let secret_key = Self::generate_pkcs8()?; 99 | let mut file = File::create(path).map_err(|e| { 100 | Error::new( 101 | ErrorKind::Io, 102 | Some(&format!("couldn't create {}: {}", path.display(), e)), 103 | ) 104 | })?; 105 | 106 | file.write_all(secret_key.as_ref())?; 107 | Ok(file) 108 | } 109 | } 110 | 111 | /// **PKCS#8** keypairs containing public keys and secret keys 112 | #[cfg(feature = "alloc")] 113 | pub struct SecretKey(Vec); 114 | 115 | #[cfg(feature = "alloc")] 116 | impl SecretKey { 117 | /// Create a new **PKCS#8** `SecretKey` from the given bytes. 118 | // TODO: parse the document and verify it's well-formed 119 | pub fn from_bytes(secret_key_bytes: &[u8]) -> Result { 120 | Ok(SecretKey(secret_key_bytes.to_vec())) 121 | } 122 | } 123 | 124 | #[cfg(feature = "alloc")] 125 | impl AsRef<[u8]> for SecretKey { 126 | fn as_ref(&self) -> &[u8] { 127 | self.0.as_ref() 128 | } 129 | } 130 | 131 | #[cfg(feature = "alloc")] 132 | impl Drop for SecretKey { 133 | fn drop(&mut self) { 134 | self.0.clear() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Signatory: a multi-provider digital signature library 2 | //! 3 | //! This crate provides a thread-and-object-safe API for both creating and 4 | //! verifying elliptic curve digital signatures, using either software-based 5 | //! or hardware-based providers. 6 | //! 7 | //! The following algorithms are supported: 8 | //! 9 | //! - [ecdsa]: Elliptic Curve Digital Signature Algorithm ([FIPS 186-4]) 10 | //! - [ed25519]: Edwards Digital Signature Algorithm (EdDSA) instantiated using 11 | //! the twisted Edwards form of Curve25519 ([RFC 8032]). 12 | //! 13 | //! ## Providers 14 | //! 15 | //! There are several backend providers available, which are each available 16 | //! in their own crates: 17 | //! 18 | //! - [signatory-dalek]: Ed25519 signing/verification using the pure-Rust 19 | //! [ed25519-dalek] crate. 20 | //! - [signatory-ring]: ECDSA and Ed25519 signing/verification provider 21 | //! for the [*ring*] cryptography library. 22 | //! - [signatory-secp256k1]: ECDSA signing/verification for the secp256k1 23 | //! elliptic curve (commonly used by Bitcoin and other cryptocurrrencies) 24 | //! which wraps the [libsecp256k1] library from Bitcoin Core. 25 | //! - [signatory-sodiumoxide]: Ed25519 signing/verification with the 26 | //! [sodiumoxide] crate, a Rust wrapper for libsodium (NOTE: requires 27 | //! libsodium to be installed on the system) 28 | //! - [yubihsm-rs]: ECDSA and Ed25519 signing provider support for 29 | //! private keys stored in a `YubiHSM2` hardware device, via the 30 | //! Signatory signers types in the [yubihsm-rs] crate 31 | //! ([yubihsm::ecdsa::Signer] and [yubihsm::ed25519::Signer]). 32 | //! 33 | //! ## Signing API 34 | //! 35 | //! - [Signer]: trait for signing 36 | //! - [DigestSigner]: trait for signing digests 37 | //! 38 | //! ## Verifier API 39 | //! 40 | //! - [Verifier]: trait for verifying 41 | //! - [DigestVerifier]: trait for verifying digests 42 | //! 43 | //! [FIPS 186-4]: https://csrc.nist.gov/publications/detail/fips/186/4/final 44 | //! [RFC 8032]: https://tools.ietf.org/html/rfc8032 45 | //! [ecdsa]: https://docs.rs/signatory/latest/signatory/ecdsa/index.html 46 | //! [ed25519]: https://docs.rs/signatory/latest/signatory/ed25519/index.html 47 | //! [signatory-dalek]: https://docs.rs/crate/signatory-dalek/ 48 | //! [ed25519-dalek]: https://docs.rs/crate/ed25519-dalek/ 49 | //! [signatory-ring]: https://docs.rs/crate/signatory-ring/ 50 | //! [*ring*]: https://github.com/briansmith/ring 51 | //! [signatory-secp256k1]: https://docs.rs/crate/signatory-secp256k1/ 52 | //! [libsecp256k1]: https://docs.rs/crate/secp256k1 53 | //! [signatory-sodiumoxide]: https://docs.rs/crate/signatory-sodiumoxide/ 54 | //! [sodiumoxide]: https://docs.rs/crate/sodiumoxide/ 55 | //! [yubihsm-rs]: https://docs.rs/crate/yubihsm/ 56 | //! [yubihsm::ecdsa::Signer]: https://docs.rs/yubihsm/latest/yubihsm/ecdsa/struct.Signer.html 57 | //! [yubihsm::ed25519::Signer]: https://docs.rs/yubihsm/latest/yubihsm/ed25519/struct.Signer.html 58 | //! [Signer]: https://docs.rs/signatory/latest/signatory/trait.Signer.html 59 | //! [DigestSigner]: https://docs.rs/signatory/latest/signatory/trait.DigestSigner.html 60 | //! [Verifier]: https://docs.rs/signatory/latest/signatory/trait.Verifier.html 61 | //! [DigestVerifier]: https://docs.rs/signatory/latest/signatory/trait.DigestVerifier.html 62 | 63 | #![no_std] 64 | #![forbid(unsafe_code)] 65 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 66 | #![doc( 67 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/signatory/develop/img/signatory-rustacean.png", 68 | html_root_url = "https://docs.rs/signatory/0.19.0" 69 | )] 70 | 71 | #[cfg(feature = "alloc")] 72 | extern crate alloc; 73 | 74 | #[cfg(any(feature = "std", test))] 75 | #[macro_use] 76 | extern crate std; 77 | 78 | #[cfg(feature = "ecdsa")] 79 | pub mod ecdsa; 80 | #[cfg(feature = "ed25519")] 81 | #[macro_use] 82 | pub mod ed25519; 83 | #[cfg(feature = "encoding")] 84 | pub mod encoding; 85 | pub mod public_key; 86 | #[cfg(feature = "test-vectors")] 87 | pub mod test_vector; 88 | #[cfg(feature = "generic-array")] 89 | pub use generic_array; 90 | #[cfg(feature = "sha2")] 91 | pub use sha2; 92 | pub use signature; 93 | -------------------------------------------------------------------------------- /src/public_key.rs: -------------------------------------------------------------------------------- 1 | //! Traits for public keys 2 | 3 | use core::fmt::Debug; 4 | use signature::Error; 5 | 6 | /// Signers which know their public keys (to be implemented by Signatory 7 | /// providers) 8 | pub trait PublicKeyed: Send + Sync { 9 | /// Public key which can verify signatures created by this signer 10 | fn public_key(&self) -> Result; 11 | } 12 | 13 | /// Common trait for all public keys 14 | pub trait PublicKey: AsRef<[u8]> + Debug + Sized + Eq + Ord {} 15 | -------------------------------------------------------------------------------- /src/test_vector.rs: -------------------------------------------------------------------------------- 1 | //! Test vector structure for signatures 2 | 3 | #[cfg(feature = "alloc")] 4 | mod pkcs8; 5 | 6 | #[cfg(feature = "alloc")] 7 | pub use pkcs8::ToPkcs8; 8 | 9 | /// Signature test vector 10 | pub struct TestVector { 11 | /// Algorithm name 12 | pub alg: TestVectorAlgorithm, 13 | 14 | /// Secret key (i.e. seed) 15 | pub sk: &'static [u8], 16 | 17 | /// Public key in compressed Edwards-y form 18 | pub pk: &'static [u8], 19 | 20 | /// Random nonce value (i.e. ECDSA's `k` value) 21 | pub nonce: Option<&'static [u8]>, 22 | 23 | /// Message to be signed 24 | pub msg: &'static [u8], 25 | 26 | /// Expected signature 27 | pub sig: &'static [u8], 28 | 29 | /// Expected to pass or fail 30 | pub pass: bool, 31 | } 32 | 33 | /// Algorithms for which we have test vectors 34 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 35 | pub enum TestVectorAlgorithm { 36 | /// NIST P-256 (a.k.a. prime256v1, secp256r1) elliptic curve 37 | NistP256, 38 | 39 | /// NIST P-384 (a.k.a. secp384r1) elliptic curve 40 | NistP384, 41 | 42 | /// secp256k1 elliptic curve 43 | Secp256k1, 44 | 45 | /// "edwards25519" elliptic curve 46 | Ed25519, 47 | } 48 | -------------------------------------------------------------------------------- /src/test_vector/pkcs8.rs: -------------------------------------------------------------------------------- 1 | //! Hax PKCS#8 serializers for test vectors 2 | 3 | use super::TestVectorAlgorithm; 4 | use alloc::vec::Vec; 5 | 6 | /// PKCS#8 header for a NIST P-256 private key 7 | const P256_PKCS8_HEADER: &[u8] = 8 | b"\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\ 9 | \x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20"; 10 | 11 | /// PKCS#8 interstitial part for a NIST P-256 private key 12 | const P256_PKCS8_PUBKEY_PREFIX: &[u8] = b"\xa1\x44\x03\x42\x00\x04"; 13 | 14 | /// PKCS#8 header for a NIST P-384 private key 15 | const P384_PKCS8_HEADER: &[u8] = 16 | b"\x30\x81\xb6\x02\x01\x00\x30\x10\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\ 17 | \x05\x2b\x81\x04\x00\x22\x04\x81\x9e\x30\x81\x9b\x02\x01\x01\x04\x30"; 18 | 19 | /// PKCS#8 interstitial part for a NIST P-384 private key 20 | const P384_PKCS8_PUBKEY_PREFIX: &[u8] = b"\xa1\x64\x03\x62\x00\x04"; 21 | 22 | /// Serialize test vector as PKCS#8 23 | pub trait ToPkcs8 { 24 | /// Serialize this test vector as a PKCS#8 document 25 | fn to_pkcs8(&self, alg: TestVectorAlgorithm) -> Vec; 26 | } 27 | 28 | #[cfg(feature = "ecdsa")] 29 | impl ToPkcs8 for ecdsa::test_vectors::TestVector { 30 | fn to_pkcs8(&self, alg: TestVectorAlgorithm) -> Vec { 31 | // TODO: better serializer than giant hardcoded bytestring literals, like a PKCS#8 library, 32 | // or at least a less bogus internal PKCS#8 implementation 33 | let mut pkcs8_document = match alg { 34 | TestVectorAlgorithm::NistP256 => P256_PKCS8_HEADER, 35 | TestVectorAlgorithm::NistP384 => P384_PKCS8_HEADER, 36 | other => panic!("unsupported test vector algorithm: {:?}", other), 37 | } 38 | .to_vec(); 39 | 40 | pkcs8_document.extend_from_slice(&self.sk); 41 | pkcs8_document.extend_from_slice(match alg { 42 | TestVectorAlgorithm::NistP256 => P256_PKCS8_PUBKEY_PREFIX, 43 | TestVectorAlgorithm::NistP384 => P384_PKCS8_PUBKEY_PREFIX, 44 | _ => panic!("this shouldn't be!"), 45 | }); 46 | pkcs8_document.extend_from_slice(&self.pk); 47 | 48 | pkcs8_document 49 | } 50 | } 51 | --------------------------------------------------------------------------------