├── .gitignore ├── signatory ├── .gitignore ├── img │ ├── signatory.png │ └── signatory-rustacean.png ├── src │ ├── key.rs │ ├── key │ │ ├── store.rs │ │ ├── info.rs │ │ ├── ring.rs │ │ ├── name.rs │ │ └── handle.rs │ ├── ed25519.rs │ ├── ecdsa.rs │ ├── ecdsa │ │ ├── keyring.rs │ │ ├── nistp256.rs │ │ ├── nistp384.rs │ │ └── secp256k1.rs │ ├── lib.rs │ ├── ed25519 │ │ ├── keyring.rs │ │ ├── verify.rs │ │ └── sign.rs │ ├── algorithm.rs │ └── error.rs ├── LICENSE-MIT ├── tests │ └── fs.rs ├── Cargo.toml ├── README.md └── CHANGELOG.md ├── .typos.toml ├── Cargo.toml ├── .github ├── dependabot.yml └── workflows │ ├── security_audit.yml │ ├── iqhttp.yml │ ├── signatory.yml │ ├── secrecy.yml │ ├── canonical-path.yml │ ├── subtle-encoding.yml │ ├── workspace.yml │ ├── hkd32.yml │ └── bip32.yml ├── .cargo └── audit.toml ├── bip32 ├── src │ ├── mnemonic.rs │ ├── extended_key │ │ └── attrs.rs │ ├── mnemonic │ │ ├── seed.rs │ │ ├── language.rs │ │ ├── bits.rs │ │ └── phrase.rs │ ├── error.rs │ ├── child_number.rs │ ├── derivation_path.rs │ ├── lib.rs │ └── extended_key.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── CHANGELOG.md └── tests │ └── bip39_vectors.rs ├── hkd32 ├── src │ ├── mnemonic.rs │ ├── mnemonic │ │ ├── seed.rs │ │ ├── language.rs │ │ ├── bits.rs │ │ └── phrase.rs │ ├── lib.rs │ ├── pathbuf.rs │ └── key_material.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── tests │ ├── test_vectors.rs │ └── bip39_vectors.rs └── CHANGELOG.md ├── iqhttp ├── CHANGELOG.md ├── src │ ├── lib.rs │ ├── error.rs │ ├── serializers.rs │ ├── query.rs │ └── https_client.rs ├── LICENSE-MIT ├── Cargo.toml └── README.md ├── canonical-path ├── Cargo.toml ├── CHANGES.md └── README.md ├── secrecy ├── Cargo.toml ├── LICENSE-MIT └── README.md ├── AUTHORS.md ├── subtle-encoding ├── LICENSE-MIT ├── Cargo.toml ├── src │ ├── lib.rs │ ├── identity.rs │ ├── error.rs │ └── bech32 │ │ ├── base32.rs │ │ └── checksum.rs ├── README.md └── CHANGES.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.swp 3 | -------------------------------------------------------------------------------- /signatory/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /signatory/img/signatory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqlusioninc/crates/HEAD/signatory/img/signatory.png -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | ".git/", 4 | "bip32/tests/bip32_vectors.rs", 5 | ] 6 | -------------------------------------------------------------------------------- /signatory/img/signatory-rustacean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqlusioninc/crates/HEAD/signatory/img/signatory-rustacean.png -------------------------------------------------------------------------------- /signatory/src/key.rs: -------------------------------------------------------------------------------- 1 | //! Key-related functionality 2 | 3 | pub(crate) mod handle; 4 | pub(crate) mod info; 5 | pub(crate) mod name; 6 | pub(crate) mod ring; 7 | pub(crate) mod store; 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | #"bip32", 5 | "canonical-path", 6 | "hkd32", 7 | "iqhttp", 8 | "secrecy", 9 | "signatory", 10 | "subtle-encoding", 11 | ] 12 | exclude = ["bip32"] 13 | -------------------------------------------------------------------------------- /signatory/src/key/store.rs: -------------------------------------------------------------------------------- 1 | //! Key storage providers. 2 | 3 | #[cfg(feature = "std")] 4 | pub(crate) mod fs; 5 | 6 | /// Trait for generating PKCS#8-encoded private keys. 7 | pub trait GeneratePkcs8 { 8 | /// Randomly generate a new PKCS#8 private key. 9 | fn generate_pkcs8() -> pkcs8::SecretDocument; 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.cargo/audit.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/RustSec/cargo-audit/blob/master/audit.toml.example 2 | 3 | [advisories] 4 | ignore = [ 5 | "RUSTSEC-2023-0052", # webpki 6 | "RUSTSEC-2024-0336", # rustls 7 | "RUSTSEC-2025-0009", # ring 8 | "RUSTSEC-2025-0010", # ring 9 | ] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...] 10 | -------------------------------------------------------------------------------- /bip32/src/mnemonic.rs: -------------------------------------------------------------------------------- 1 | //! Support for BIP39 mnemonic phrases. 2 | //! 3 | //! Adapted from the `bip39` crate. 4 | //! Copyright © 2017-2018 Stephen Oliver with contributions by Maciej Hirsz. 5 | 6 | mod bits; 7 | mod language; 8 | mod phrase; 9 | 10 | #[cfg(feature = "bip39")] 11 | pub(crate) mod seed; 12 | 13 | pub use self::{language::Language, phrase::Phrase}; 14 | 15 | #[cfg(feature = "bip39")] 16 | pub use self::seed::Seed; 17 | -------------------------------------------------------------------------------- /signatory/src/key/info.rs: -------------------------------------------------------------------------------- 1 | //! Information about a key in a keystore 2 | 3 | use crate::{Algorithm, KeyName}; 4 | 5 | /// Information/metadata about a particular key. 6 | #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] 7 | pub struct KeyInfo { 8 | /// Name of the key. 9 | pub name: KeyName, 10 | 11 | /// Algorithm of this key (if recognized). 12 | pub algorithm: Option, 13 | 14 | /// Is this key encrypted (i.e. under a password)? 15 | pub encrypted: bool, 16 | } 17 | -------------------------------------------------------------------------------- /hkd32/src/mnemonic.rs: -------------------------------------------------------------------------------- 1 | //! Support for BIP39 mnemonic phrases. 2 | //! 3 | //! These enable deriving `hkd32::KeyMaterial` from a 24-word BIP39 phrase. 4 | //! 5 | //! Adapted from the `bip39` crate. 6 | //! Copyright © 2017-2018 Stephen Oliver with contributions by Maciej Hirsz. 7 | 8 | mod bits; 9 | mod language; 10 | mod phrase; 11 | 12 | #[cfg(feature = "bip39")] 13 | pub(crate) mod seed; 14 | 15 | pub use self::{language::Language, phrase::Phrase}; 16 | 17 | #[cfg(feature = "bip39")] 18 | pub use self::seed::Seed; 19 | -------------------------------------------------------------------------------- /iqhttp/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.1.1 (2022-04-23) 8 | ### Added 9 | - `HttpsClient::{add_header, headers_mut}` methods ([#981]) 10 | 11 | ### Changed 12 | - Rust 2021 edition upgrade ([#889]) 13 | 14 | [#889]: https://github.com/iqlusioninc/crates/pull/889 15 | [#981]: https://github.com/iqlusioninc/crates/pull/981 16 | 17 | ## 0.1.0 (2021-05-10) 18 | - Initial release 19 | -------------------------------------------------------------------------------- /bip32/src/extended_key/attrs.rs: -------------------------------------------------------------------------------- 1 | //! Extended key attributes. 2 | 3 | use crate::{ChainCode, ChildNumber, Depth, KeyFingerprint}; 4 | 5 | /// Extended key attributes: fields common to extended keys including depth, 6 | /// fingerprints, child numbers, and chain codes. 7 | #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] 8 | pub struct ExtendedKeyAttrs { 9 | /// Depth in the key derivation hierarchy. 10 | pub depth: Depth, 11 | 12 | /// Parent fingerprint. 13 | pub parent_fingerprint: KeyFingerprint, 14 | 15 | /// Child number. 16 | pub child_number: ChildNumber, 17 | 18 | /// Chain code. 19 | pub chain_code: ChainCode, 20 | } 21 | -------------------------------------------------------------------------------- /signatory/src/ed25519.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 digital signature algorithm support. 2 | 3 | mod keyring; 4 | mod sign; 5 | mod verify; 6 | 7 | pub use self::{ 8 | keyring::KeyRing, 9 | sign::{Ed25519Signer, SigningKey}, 10 | verify::VerifyingKey, 11 | }; 12 | pub use ed25519_dalek::ed25519::Signature; 13 | 14 | /// Ed25519 Object Identifier (OID). 15 | pub const ALGORITHM_OID: pkcs8::ObjectIdentifier = 16 | pkcs8::ObjectIdentifier::new_unwrap("1.3.101.112"); 17 | 18 | /// Ed25519 Algorithm Identifier. 19 | pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifierRef<'static> = pkcs8::AlgorithmIdentifierRef { 20 | oid: ALGORITHM_OID, 21 | parameters: None, 22 | }; 23 | -------------------------------------------------------------------------------- /signatory/src/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! Elliptic Curve Digital Signature Algorithm (ECDSA) support. 2 | 3 | #[cfg(feature = "nistp256")] 4 | pub mod nistp256; 5 | 6 | #[cfg(feature = "nistp384")] 7 | pub mod nistp384; 8 | 9 | #[cfg(feature = "secp256k1")] 10 | pub mod secp256k1; 11 | 12 | mod keyring; 13 | 14 | pub use self::keyring::KeyRing; 15 | pub use ecdsa::{elliptic_curve, Signature}; 16 | 17 | #[cfg(feature = "nistp256")] 18 | pub use {self::nistp256::NistP256Signer, p256::NistP256}; 19 | 20 | #[cfg(feature = "nistp384")] 21 | pub use {self::nistp384::NistP384Signer, p384::NistP384}; 22 | 23 | #[cfg(feature = "secp256k1")] 24 | pub use {self::secp256k1::Secp256k1Signer, k256::Secp256k1}; 25 | -------------------------------------------------------------------------------- /canonical-path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "canonical-path" 3 | description = "Path and PathBuf-like types for representing canonical filesystem paths" 4 | version = "2.1.0-pre" 5 | authors = ["Tony Arcieri "] 6 | license = "Apache-2.0" 7 | homepage = "https://github.com/iqlusioninc/crates/" 8 | repository = "https://github.com/iqlusioninc/crates/tree/main/canonical-path" 9 | readme = "README.md" 10 | categories = ["filesystem"] 11 | keywords = ["filesystem", "path", "canonicalization"] 12 | edition = "2021" 13 | rust-version = "1.63" 14 | 15 | [badges] 16 | maintenance = { status = "passively-maintained" } 17 | 18 | [dev-dependencies] 19 | tempfile = "3" 20 | -------------------------------------------------------------------------------- /iqhttp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 3 | #![forbid(unsafe_code)] 4 | #![warn( 5 | clippy::unwrap_used, 6 | missing_docs, 7 | rust_2018_idioms, 8 | unused_qualifications 9 | )] 10 | 11 | #[cfg(feature = "serde")] 12 | pub mod serializers; 13 | 14 | mod error; 15 | mod https_client; 16 | mod query; 17 | 18 | pub use self::{ 19 | error::{Error, Result}, 20 | https_client::HttpsClient, 21 | query::Query, 22 | }; 23 | pub use hyper::{self, header, HeaderMap, Uri}; 24 | 25 | /// User-Agent to send in HTTP request 26 | pub const USER_AGENT: &str = concat!("iqhttp/", env!("CARGO_PKG_VERSION")); 27 | 28 | /// HTTP request path type 29 | // TODO(tarcieri): real path type 30 | pub type Path = str; 31 | -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | pull_request: 4 | paths: 5 | - Cargo.lock 6 | - .cargo/audit.toml 7 | push: 8 | branches: main 9 | paths: 10 | - Cargo.lock 11 | schedule: 12 | - cron: "0 0 * * *" 13 | 14 | jobs: 15 | security_audit: 16 | name: Security Audit 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | override: true 24 | profile: minimal 25 | - uses: actions/cache@v4 26 | with: 27 | path: ~/.cargo/bin 28 | key: ${{ runner.os }}-cargo-audit-v0.15.2 29 | - uses: actions-rs/audit-check@v1 30 | with: 31 | token: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /bip32/src/mnemonic/seed.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 seed values 2 | 3 | use zeroize::Zeroize; 4 | 5 | /// BIP39 seeds. 6 | // TODO(tarcieri): support for 32-byte seeds 7 | pub struct Seed(pub(crate) [u8; Seed::SIZE]); 8 | 9 | impl Seed { 10 | /// Number of bytes of PBKDF2 output to extract. 11 | pub const SIZE: usize = 64; 12 | 13 | /// Create a new seed from the given bytes. 14 | pub fn new(bytes: [u8; Seed::SIZE]) -> Self { 15 | Seed(bytes) 16 | } 17 | 18 | /// Get the inner secret byte slice 19 | pub fn as_bytes(&self) -> &[u8; Seed::SIZE] { 20 | &self.0 21 | } 22 | } 23 | 24 | impl AsRef<[u8]> for Seed { 25 | fn as_ref(&self) -> &[u8] { 26 | self.as_bytes() 27 | } 28 | } 29 | 30 | impl Drop for Seed { 31 | fn drop(&mut self) { 32 | self.0.zeroize(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hkd32/src/mnemonic/seed.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 seed values 2 | 3 | use zeroize::Zeroize; 4 | 5 | /// BIP39 seeds. 6 | // TODO(tarcieri): support for 32-byte seeds 7 | pub struct Seed(pub(crate) [u8; Seed::SIZE]); 8 | 9 | impl Seed { 10 | /// Number of bytes of PBKDF2 output to extract. 11 | pub const SIZE: usize = 64; 12 | 13 | /// Create a new seed from the given bytes. 14 | pub fn new(bytes: [u8; Seed::SIZE]) -> Self { 15 | Seed(bytes) 16 | } 17 | 18 | /// Get the inner secret byte slice 19 | pub fn as_bytes(&self) -> &[u8; Seed::SIZE] { 20 | &self.0 21 | } 22 | } 23 | 24 | impl AsRef<[u8]> for Seed { 25 | fn as_ref(&self) -> &[u8] { 26 | self.as_bytes() 27 | } 28 | } 29 | 30 | impl Drop for Seed { 31 | fn drop(&mut self) { 32 | self.0.zeroize(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /canonical-path/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## [2.0.2] (2019-05-24) 2 | 3 | - Test all crates on Rust 1.35.0 ([#206]) 4 | 5 | ## [2.0.1] (2019-05-22) 6 | 7 | - Canonicalize `current_exe()` ([#198]) 8 | 9 | ## [2.0.0] (2019-05-20) 10 | 11 | - Clean up and modernize ([#193]) 12 | - Update to 2018 edition ([#138]) 13 | 14 | ## 1.0.0 (2018-10-03) 15 | 16 | - Initial 1.0 release 17 | 18 | ## 0.1.1 (2018-04-09) 19 | 20 | - Fix link to CircleCI badge in `Cargo.toml` 21 | 22 | ## 0.1.0 (2018-04-09) 23 | 24 | - Initial release 25 | 26 | [2.0.2]: https://github.com/iqlusioninc/crates/pull/207 27 | [#206]: https://github.com/iqlusioninc/crates/pull/206 28 | [2.0.1]: https://github.com/iqlusioninc/crates/pull/199 29 | [#198]: https://github.com/iqlusioninc/crates/pull/198 30 | [2.0.0]: https://github.com/iqlusioninc/crates/pull/194 31 | [#193]: https://github.com/iqlusioninc/crates/pull/193 32 | [#138]: https://github.com/iqlusioninc/crates/pull/138 33 | -------------------------------------------------------------------------------- /signatory/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 | -------------------------------------------------------------------------------- /secrecy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "secrecy" 3 | description = """ 4 | Wrapper types and traits for secret management which help ensure 5 | they aren't accidentally copied, logged, or otherwise exposed 6 | (as much as possible), and also ensure secrets are securely wiped 7 | from memory when dropped. 8 | """ 9 | version = "0.10.3" 10 | authors = ["Tony Arcieri "] 11 | license = "Apache-2.0 OR MIT" 12 | homepage = "https://github.com/iqlusioninc/crates/" 13 | repository = "https://github.com/iqlusioninc/crates/tree/main/secrecy" 14 | readme = "README.md" 15 | categories = ["cryptography", "memory-management", "no-std", "os"] 16 | keywords = ["clear", "memory", "secret", "secure", "wipe"] 17 | edition = "2021" 18 | rust-version = "1.60" 19 | 20 | [dependencies] 21 | zeroize = { version = "1.6", default-features = false, features = ["alloc"] } 22 | 23 | # optional dependencies 24 | serde = { version = "1", optional = true, default-features = false, features = ["alloc"] } 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | rustdoc-args = ["--cfg", "docsrs"] 29 | -------------------------------------------------------------------------------- /.github/workflows/iqhttp.yml: -------------------------------------------------------------------------------- 1 | name: iqhttp 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "iqhttp/**" 7 | - "Cargo.*" 8 | - ".github/workflows/iphttp.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: iqhttp 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | platform: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | toolchain: 29 | - 1.70.0 # MSRV 30 | - stable 31 | runs-on: ${{ matrix.platform }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | override: true 38 | profile: minimal 39 | - run: cargo test --release 40 | - run: cargo test --release --features json 41 | - run: cargo test --release --features proxy 42 | - run: cargo test --release --features serde 43 | - run: cargo test --release --all-features 44 | -------------------------------------------------------------------------------- /bip32/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 iqlusion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | All code in the iqlusion crates repository is Copyright © 2018 iqlusion, except 4 | for contributions of individual authors below, who have agreed to license their 5 | contributions under the terms of the [Apache License, Version 2.0] 6 | (included in this repository in the toplevel [LICENSE] file). 7 | 8 | [Apache License, Version 2.0]: https://www.apache.org/licenses/LICENSE-2.0 9 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 10 | 11 | * Anthony Arcieri ([@tarcieri](https://github.com/tarcieri)) 12 | * Amr Ali ([@amrali](https://github.com/amrali)) 13 | * Armin Ronacher ([@mitsuhiko](https://github.com/mitsuhiko)) 14 | * Boats ([@withoutboats](https://github.com/withoutboats)) 15 | * David Tolnay ([@dtolnay](https://github.com/dtolnay)) 16 | * Kai Ren ([@tyranron](https://github.com/tyranron)) 17 | * Murarth ([@murarth](https://github.com/murarth)) 18 | * Niclas Schwarzlose ([@aticu](https://github.com/aticu)) 19 | * Yin Guanhao ([@sopium](https://github.com/sopium)) 20 | * Will Speak ([@iwillspeak](https://github.com/iwillspeak)) 21 | * Zach Reizner ([@zachreizner](https://github.com/zachreizner)) 22 | -------------------------------------------------------------------------------- /signatory/src/ecdsa/keyring.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA keyring. 2 | 3 | use crate::{Algorithm, Error, KeyHandle, LoadPkcs8, Result}; 4 | 5 | #[cfg(feature = "nistp256")] 6 | use super::nistp256; 7 | 8 | #[cfg(feature = "secp256k1")] 9 | use super::secp256k1; 10 | 11 | /// ECDSA keyring. 12 | #[derive(Debug, Default)] 13 | pub struct KeyRing { 14 | /// ECDSA/P-256 keys. 15 | #[cfg(feature = "nistp256")] 16 | pub nistp256: nistp256::KeyRing, 17 | 18 | /// ECDSA/secp256k1 keys. 19 | #[cfg(feature = "secp256k1")] 20 | pub secp256k1: secp256k1::KeyRing, 21 | } 22 | 23 | impl LoadPkcs8 for KeyRing { 24 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result { 25 | match Algorithm::try_from(private_key.algorithm)? { 26 | #[cfg(feature = "nistp256")] 27 | Algorithm::EcdsaNistP256 => self.nistp256.load_pkcs8(private_key), 28 | #[cfg(feature = "secp256k1")] 29 | Algorithm::EcdsaSecp256k1 => self.secp256k1.load_pkcs8(private_key), 30 | #[allow(unreachable_patterns)] 31 | _ => Err(Error::AlgorithmInvalid), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /secrecy/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 iqlusion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /signatory/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | #![doc( 5 | html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/crates/main/signatory/img/signatory-rustacean.svg" 6 | )] 7 | #![forbid(unsafe_code)] 8 | #![warn( 9 | clippy::unwrap_used, 10 | missing_docs, 11 | rust_2018_idioms, 12 | unused_qualifications 13 | )] 14 | 15 | extern crate alloc; 16 | 17 | #[cfg(feature = "std")] 18 | extern crate std; 19 | 20 | #[cfg(feature = "ecdsa")] 21 | pub mod ecdsa; 22 | 23 | #[cfg(feature = "ed25519")] 24 | pub mod ed25519; 25 | 26 | mod algorithm; 27 | mod error; 28 | mod key; 29 | 30 | pub use self::{ 31 | algorithm::Algorithm, 32 | error::{Error, Result}, 33 | key::{ 34 | handle::KeyHandle, 35 | info::KeyInfo, 36 | name::KeyName, 37 | ring::{KeyRing, LoadPkcs8}, 38 | store::GeneratePkcs8, 39 | }, 40 | }; 41 | pub use pkcs8; 42 | pub use signature; 43 | 44 | #[cfg(feature = "std")] 45 | pub use key::store::fs::FsKeyStore; 46 | 47 | /// Map type. 48 | pub type Map = alloc::collections::BTreeMap; 49 | -------------------------------------------------------------------------------- /subtle-encoding/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 iqlusion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /iqhttp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2022 iqlusion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /hkd32/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Stephen Oliver 4 | Copyright (c) 2019 iqlusion 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /iqhttp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iqhttp" 3 | version = "0.3.0-pre" 4 | description = """ 5 | iqlusion's HTTPS toolkit. Provides a high-level wrapper around hyper, with 6 | built-in SSL/TLS support based on rustls. 7 | """ 8 | authors = ["Tony Arcieri ", "Shella Stephens "] 9 | homepage = "https://github.com/iqlusioninc/crates/" 10 | repository = "https://github.com/iqlusioninc/crates/tree/main/iqhttp" 11 | license = "Apache-2.0 OR MIT" 12 | categories = ["network-programming", "web-programming::http-client"] 13 | keywords = ["api", "client", "http", "rest", "web"] 14 | readme = "README.md" 15 | edition = "2021" 16 | rust-version = "1.63" 17 | 18 | [dependencies] 19 | hyper = "0.14.10" 20 | hyper-rustls = { version = "0.22", features = ["rustls-native-certs"] } 21 | 22 | # optional dependencies 23 | hyper-proxy = { version = "=0.9.1", optional = true } # carefully vet changes before bumping version 24 | serde = { version = "1", optional = true } 25 | serde_json = { version = "1", optional = true } 26 | 27 | [features] 28 | json = ["serde", "serde_json"] 29 | proxy = ["hyper-proxy"] 30 | 31 | [package.metadata.docs.rs] 32 | all-features = true 33 | rustdoc-args = ["--cfg", "docsrs"] 34 | -------------------------------------------------------------------------------- /.github/workflows/signatory.yml: -------------------------------------------------------------------------------- 1 | name: signatory 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "signatory/**" 7 | - "Cargo.*" 8 | - ".github/workflows/signatory.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: signatory 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | jobs: 20 | test: 21 | strategy: 22 | matrix: 23 | platform: 24 | - ubuntu-latest 25 | - macos-latest 26 | - windows-latest 27 | toolchain: 28 | - 1.70.0 # MSRV 29 | - stable 30 | runs-on: ${{ matrix.platform }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | override: true 37 | profile: minimal 38 | - run: cargo test --release --no-default-features 39 | - run: cargo test --release 40 | - run: cargo test --release --features ecdsa 41 | - run: cargo test --release --features ed25519 42 | - run: cargo test --release --features nistp256 43 | - run: cargo test --release --features secp256k1 44 | - run: cargo test --release --all-features 45 | -------------------------------------------------------------------------------- /signatory/tests/fs.rs: -------------------------------------------------------------------------------- 1 | //! Filesystem keystore integration test 2 | 3 | #![cfg(feature = "secp256k1")] 4 | 5 | use signatory::{ 6 | ecdsa::secp256k1::{Signature, SigningKey}, 7 | signature::{Signer, Verifier}, 8 | FsKeyStore, GeneratePkcs8, KeyName, KeyRing, 9 | }; 10 | 11 | /// Integration test for loading a key from a keystore 12 | #[test] 13 | fn integration() { 14 | let dir = tempfile::tempdir().unwrap(); 15 | let key_store = FsKeyStore::create_or_open(&dir.path()).unwrap(); 16 | let example_key = SigningKey::generate_pkcs8(); 17 | 18 | let key_name = "example".parse::().unwrap(); 19 | key_store.store(&key_name, &example_key).unwrap(); 20 | 21 | let mut key_ring = KeyRing::new(); 22 | let key_handle = key_store.import(&key_name, &mut key_ring).unwrap(); 23 | 24 | let signing_key = key_ring.ecdsa.secp256k1.iter().next().unwrap(); 25 | let verifying_key = key_handle.ecdsa_secp256k1().unwrap(); 26 | assert_eq!(signing_key.verifying_key(), verifying_key); 27 | 28 | let example_message = "Hello, world!"; 29 | let signature: Signature = signing_key.sign(example_message.as_bytes()); 30 | assert!(verifying_key 31 | .verify(example_message.as_bytes(), &signature) 32 | .is_ok()); 33 | } 34 | -------------------------------------------------------------------------------- /subtle-encoding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subtle-encoding" 3 | description = """ 4 | Encoders and decoders for common data encodings (base64, bech32, hex) 5 | which avoid data-dependent branching/table lookups and therefore 6 | provide "best effort" constant time. Useful for encoding/decoding 7 | secret values such as cryptographic keys. 8 | """ 9 | version = "0.6.0-pre" 10 | authors = ["Tony Arcieri "] 11 | license = "Apache-2.0 OR MIT" 12 | homepage = "https://github.com/iqlusioninc/crates/" 13 | repository = "https://github.com/iqlusioninc/crates/tree/main/subtle-encoding" 14 | readme = "README.md" 15 | categories = ["cryptography", "encoding", "no-std"] 16 | keywords = ["base64", "bech32", "constant-time", "hex", "security"] 17 | edition = "2021" 18 | rust-version = "1.60" 19 | 20 | [dependencies.zeroize] 21 | version = "1" 22 | optional = true 23 | default-features = false 24 | 25 | [features] 26 | default = ["base64", "hex", "std"] 27 | alloc = [] 28 | base64 = ["zeroize"] 29 | bech32-preview = ["alloc", "zeroize"] 30 | hex = [] 31 | std = ["alloc", "zeroize"] 32 | 33 | [badges] 34 | maintenance = { status = "passively-maintained" } 35 | 36 | [package.metadata.docs.rs] 37 | features = ["bech32-preview"] 38 | -------------------------------------------------------------------------------- /.github/workflows/secrecy.yml: -------------------------------------------------------------------------------- 1 | name: secrecy 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "secrecy/**" 7 | - "Cargo.*" 8 | - ".github/workflows/secrecy.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: secrecy 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | platform: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | toolchain: 29 | - 1.61.0 # MSRV 30 | - stable 31 | runs-on: ${{ matrix.platform }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | override: true 38 | profile: minimal 39 | - run: cargo test --release 40 | - run: cargo test --release --all-features 41 | 42 | wasm: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | target: wasm32-unknown-unknown 50 | override: true 51 | profile: minimal 52 | - run: cargo build --target wasm32-unknown-unknown 53 | -------------------------------------------------------------------------------- /.github/workflows/canonical-path.yml: -------------------------------------------------------------------------------- 1 | name: canonical-path 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "canonical-path/**" 7 | - "Cargo.*" 8 | - ".github/workflows/canonical-path.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: canonical-path 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | platform: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | toolchain: 29 | - 1.70.0 # MSRV 30 | - stable 31 | runs-on: ${{ matrix.platform }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | override: true 38 | profile: minimal 39 | - run: cargo test --release 40 | - run: cargo test --release --all-features 41 | 42 | wasm: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | target: wasm32-unknown-unknown 50 | override: true 51 | profile: minimal 52 | - run: cargo build --target wasm32-unknown-unknown 53 | -------------------------------------------------------------------------------- /.github/workflows/subtle-encoding.yml: -------------------------------------------------------------------------------- 1 | name: subtle-encoding 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "subtle-encoding/**" 7 | - "Cargo.*" 8 | - ".github/workflows/subtle-encoding.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: subtle-encoding 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | platform: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | toolchain: 29 | - 1.60.0 # MSRV 30 | - stable 31 | runs-on: ${{ matrix.platform }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | override: true 38 | profile: minimal 39 | - run: cargo test --release 40 | - run: cargo test --release --all-features 41 | 42 | wasm: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | target: wasm32-unknown-unknown 50 | override: true 51 | profile: minimal 52 | - run: cargo build --target wasm32-unknown-unknown 53 | -------------------------------------------------------------------------------- /signatory/src/ed25519/keyring.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 keyring. 2 | 3 | use super::{SigningKey, VerifyingKey}; 4 | use crate::{Error, KeyHandle, LoadPkcs8, Map, Result}; 5 | 6 | /// Ed25519 keyring. 7 | #[derive(Debug, Default)] 8 | pub struct KeyRing { 9 | keys: Map, 10 | } 11 | 12 | impl KeyRing { 13 | /// Create new Ed25519 keyring. 14 | pub fn new() -> Self { 15 | Self::default() 16 | } 17 | 18 | /// Get the [`SigningKey`] that corresponds to the provided [`VerifyingKey`] 19 | /// (i.e. public key) 20 | pub fn get(&self, verifying_key: &VerifyingKey) -> Option<&SigningKey> { 21 | self.keys.get(verifying_key) 22 | } 23 | 24 | /// Iterate over the keys in the keyring. 25 | pub fn iter(&self) -> impl Iterator { 26 | self.keys.values() 27 | } 28 | } 29 | 30 | impl LoadPkcs8 for KeyRing { 31 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result { 32 | let signing_key = SigningKey::try_from(private_key)?; 33 | let verifying_key = signing_key.verifying_key(); 34 | 35 | if self.keys.contains_key(&verifying_key) { 36 | return Err(Error::DuplicateKey); 37 | } 38 | 39 | self.keys.insert(verifying_key, signing_key); 40 | Ok(KeyHandle::Ed25519(verifying_key)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /subtle-encoding/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Encoders and decoders for common data encodings (base64, hex) which avoid 2 | //! branching or performing table lookups based on their inputs 3 | //! (a.k.a. "constant time-ish"). 4 | //! 5 | //! ## Supported Encodings 6 | //! 7 | //! - [hex] 8 | //! - [base64] 9 | //! - [bech32] (WARNING: preview! Not constant time yet) 10 | //! 11 | //! [hex]: https://docs.rs/subtle-encoding/latest/subtle_encoding/hex/index.html 12 | //! [base64]: https://docs.rs/subtle-encoding/latest/subtle_encoding/base64/index.html 13 | //! [bech32]: https://docs.rs/subtle-encoding/latest/subtle_encoding/bech32/index.html 14 | 15 | #![no_std] 16 | #![forbid(unsafe_code)] 17 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 18 | 19 | #[cfg(feature = "alloc")] 20 | #[macro_use] 21 | extern crate alloc; 22 | 23 | #[cfg(any(feature = "std", test))] 24 | extern crate std; 25 | 26 | #[macro_use] 27 | mod error; 28 | 29 | #[cfg(feature = "base64")] 30 | pub mod base64; 31 | #[cfg(feature = "bech32-preview")] 32 | pub mod bech32; 33 | pub mod encoding; 34 | #[cfg(feature = "hex")] 35 | pub mod hex; 36 | pub mod identity; 37 | 38 | #[cfg(feature = "base64")] 39 | pub use crate::base64::Base64; 40 | pub use crate::encoding::Encoding; 41 | pub use crate::error::Error; 42 | #[cfg(feature = "hex")] 43 | pub use crate::hex::Hex; 44 | pub use crate::identity::{Identity, IDENTITY}; 45 | -------------------------------------------------------------------------------- /.github/workflows/workspace.yml: -------------------------------------------------------------------------------- 1 | name: Workspace 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: main 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | override: true 17 | profile: minimal 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: check 21 | 22 | clippy: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: 1.81.0 # pinned to prevent breakages 29 | components: clippy 30 | override: true 31 | profile: minimal 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: clippy 35 | args: --all --all-features -- -D warnings 36 | 37 | rustfmt: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | components: rustfmt 45 | override: true 46 | profile: minimal 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | 52 | typos: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: crate-ci/typos@v1.33.1 57 | -------------------------------------------------------------------------------- /signatory/src/key/ring.rs: -------------------------------------------------------------------------------- 1 | //! Signature key ring. 2 | 3 | use crate::{Algorithm, Error, KeyHandle, Result}; 4 | 5 | #[cfg(feature = "ecdsa")] 6 | use crate::ecdsa; 7 | 8 | #[cfg(feature = "ed25519")] 9 | use crate::ed25519; 10 | 11 | /// Signature key ring which can contain signing keys for all supported algorithms. 12 | #[derive(Debug, Default)] 13 | pub struct KeyRing { 14 | /// ECDSA key ring. 15 | #[cfg(feature = "ecdsa")] 16 | pub ecdsa: ecdsa::KeyRing, 17 | 18 | /// Ed25519 key ring. 19 | #[cfg(feature = "ed25519")] 20 | pub ed25519: ed25519::KeyRing, 21 | } 22 | 23 | impl KeyRing { 24 | /// Create a new keyring. 25 | pub fn new() -> Self { 26 | Self::default() 27 | } 28 | } 29 | 30 | /// Support for loading PKCS#8 private keys. 31 | pub trait LoadPkcs8 { 32 | /// Load a PKCS#8 key into the key ring. 33 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result; 34 | } 35 | 36 | impl LoadPkcs8 for KeyRing { 37 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result { 38 | #[allow(unused_variables)] 39 | let algorithm = Algorithm::try_from(private_key.algorithm)?; 40 | 41 | #[cfg(feature = "ecdsa")] 42 | if algorithm.is_ecdsa() { 43 | return self.ecdsa.load_pkcs8(private_key); 44 | } 45 | 46 | #[cfg(feature = "ed25519")] 47 | if algorithm == Algorithm::Ed25519 { 48 | return self.ed25519.load_pkcs8(private_key); 49 | } 50 | 51 | Err(Error::AlgorithmInvalid) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /signatory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signatory" 3 | description = "Multi-provider elliptic curve digital signature library with ECDSA and Ed25519 support" 4 | version = "0.27.1" 5 | license = "Apache-2.0 OR MIT" 6 | authors = ["Tony Arcieri "] 7 | homepage = "https://github.com/iqlusioninc/crates" 8 | repository = "https://github.com/iqlusioninc/crates/tree/main/signatory" 9 | readme = "README.md" 10 | categories = ["authentication", "cryptography"] 11 | keywords = ["cryptography", "ecdsa", "ed25519", "signing", "signatures"] 12 | edition = "2021" 13 | rust-version = "1.65" 14 | 15 | [dependencies] 16 | pkcs8 = { version = "0.10", features = ["alloc", "pem"] } 17 | rand_core = "0.6" 18 | signature = "2" 19 | zeroize = "1.5" 20 | 21 | # optional dependencies 22 | ecdsa = { version = "0.16", optional = true, features = ["pem", "pkcs8"] } 23 | ed25519-dalek = { version = "2", optional = true, default-features = false } 24 | k256 = { version = "0.13", optional = true, features = ["ecdsa", "sha256"] } 25 | p256 = { version = "0.13", optional = true, features = ["ecdsa", "sha256"] } 26 | p384 = { version = "0.13", optional = true, features = ["ecdsa", "sha384"] } 27 | 28 | [dev-dependencies] 29 | tempfile = "3" 30 | 31 | [features] 32 | default = ["std"] 33 | ed25519 = ["dep:ed25519-dalek"] 34 | nistp256 = ["dep:p256", "ecdsa"] 35 | nistp384 = ["dep:p384", "ecdsa"] 36 | secp256k1 = ["dep:k256", "ecdsa"] 37 | std = ["pkcs8/std", "rand_core/std", "signature/std"] 38 | 39 | [package.metadata.docs.rs] 40 | all-features = true 41 | rustdoc-args = ["--cfg", "docsrs"] 42 | -------------------------------------------------------------------------------- /subtle-encoding/src/identity.rs: -------------------------------------------------------------------------------- 1 | //! Identity `Encoding`: output is identical to input 2 | 3 | use super::{ 4 | Encoding, 5 | Error::{self, LengthInvalid}, 6 | }; 7 | 8 | /// `Encoding` which does not transform data and returns the original input. 9 | #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] 10 | pub struct Identity {} 11 | 12 | /// Constant `Identity` encoding that can be used in lieu of calling `default()` 13 | pub const IDENTITY: &Identity = &Identity {}; 14 | 15 | impl Encoding for Identity { 16 | fn encode_to_slice(&self, src: &[u8], dst: &mut [u8]) -> Result { 17 | ensure!(self.encoded_len(src) == dst.len(), LengthInvalid); 18 | dst.copy_from_slice(src); 19 | Ok(src.len()) 20 | } 21 | 22 | fn encoded_len(&self, bytes: &[u8]) -> usize { 23 | bytes.len() 24 | } 25 | 26 | fn decode_to_slice(&self, src: &[u8], dst: &mut [u8]) -> Result { 27 | ensure!(self.decoded_len(src)? == dst.len(), LengthInvalid); 28 | dst.copy_from_slice(src); 29 | Ok(src.len()) 30 | } 31 | 32 | fn decoded_len(&self, bytes: &[u8]) -> Result { 33 | Ok(bytes.len()) 34 | } 35 | } 36 | 37 | // TODO(tarcieri): `no_std` tests 38 | #[cfg(feature = "alloc")] 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | const TEST_DATA: &[u8] = b"Testing 1, 2, 3..."; 44 | 45 | #[test] 46 | fn test_encode() { 47 | assert_eq!(IDENTITY.encode(TEST_DATA), TEST_DATA); 48 | } 49 | 50 | #[test] 51 | fn test_decode() { 52 | assert_eq!(IDENTITY.decode(TEST_DATA).unwrap(), TEST_DATA); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /signatory/src/key/name.rs: -------------------------------------------------------------------------------- 1 | //! Key names: unambiguous identifier strings. 2 | 3 | use crate::{Error, Result}; 4 | use alloc::string::String; 5 | use core::{ 6 | fmt::{self, Display}, 7 | ops::Deref, 8 | str::FromStr, 9 | }; 10 | 11 | /// Key names. 12 | /// 13 | /// These are constrained to the following characters: 14 | /// - Letters: `a-z`, `A-Z` 15 | /// - Numbers: `0-9` 16 | /// - Delimiters: `-`, `_` 17 | #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] 18 | pub struct KeyName(String); 19 | 20 | impl KeyName { 21 | /// Create a new key name from the given string. 22 | pub fn new(name: impl Into) -> Result { 23 | let name = name.into(); 24 | 25 | if !name 26 | .as_bytes() 27 | .iter() 28 | .all(|&byte| matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_')) 29 | { 30 | return Err(Error::KeyNameInvalid); 31 | } 32 | 33 | Ok(Self(name)) 34 | } 35 | } 36 | 37 | impl AsRef for KeyName { 38 | fn as_ref(&self) -> &str { 39 | &self.0 40 | } 41 | } 42 | 43 | #[cfg(feature = "std")] 44 | impl AsRef for KeyName { 45 | fn as_ref(&self) -> &std::path::Path { 46 | self.0.as_ref() 47 | } 48 | } 49 | 50 | impl Deref for KeyName { 51 | type Target = str; 52 | 53 | fn deref(&self) -> &str { 54 | &self.0 55 | } 56 | } 57 | 58 | impl Display for KeyName { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | f.write_str(&self.0) 61 | } 62 | } 63 | 64 | impl FromStr for KeyName { 65 | type Err = Error; 66 | 67 | fn from_str(name: &str) -> Result { 68 | Self::new(name) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /hkd32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hkd32" 3 | version = "0.8.0-pre" 4 | description = """ 5 | HMAC-based Hierarchical Key Derivation: deterministically derive 6 | a hierarchy of symmetric keys from initial keying material through 7 | repeated applications of the Hash-based Message Authentication Code 8 | (HMAC) construction. Optionally supports storing root derivation 9 | passwords as a 24-word mnemonic phrase (i.e. BIP39). 10 | """ 11 | authors = ["Tony Arcieri "] 12 | license = "Apache-2.0 OR MIT" 13 | homepage = "https://github.com/iqlusioninc/crates/" 14 | repository = "https://github.com/iqlusioninc/crates/tree/main/hkd32" 15 | readme = "README.md" 16 | categories = ["cryptography", "no-std"] 17 | keywords = ["crypto", "bip32", "bip39", "derivation", "mnemonic"] 18 | edition = "2021" 19 | rust-version = "1.63" 20 | 21 | [dependencies] 22 | hmac = { version = "0.12", default-features = false } 23 | rand_core = { version = "0.6", default-features = false } 24 | sha2 = { version = "0.10", default-features = false } 25 | zeroize = { version = "1", default-features = false } 26 | 27 | # optional dependencies 28 | once_cell = { version = "1", optional = true } 29 | pbkdf2 = { version = "0.12", optional = true, default-features = false, features = ["hmac"] } 30 | subtle-encoding = { version = "=0.6.0-pre", optional = true, default-features = false, path = "../subtle-encoding" } 31 | 32 | [dev-dependencies] 33 | hex-literal = "0.4" 34 | rand_core = { version = "0.6", features = ["std"] } 35 | 36 | [features] 37 | default = ["alloc", "bech32"] 38 | alloc = ["zeroize/alloc"] 39 | bech32 = ["alloc", "subtle-encoding/bech32-preview"] 40 | bip39 = ["mnemonic", "pbkdf2"] 41 | mnemonic = ["alloc", "once_cell"] 42 | std = [ ] 43 | 44 | [package.metadata.docs.rs] 45 | all-features = true 46 | rustdoc-args = ["--cfg", "docsrs"] 47 | -------------------------------------------------------------------------------- /iqhttp/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | 3 | use std::fmt::{self, Display}; 4 | 5 | /// Result type 6 | pub type Result = std::result::Result; 7 | 8 | /// Error type 9 | #[derive(Debug)] 10 | #[non_exhaustive] 11 | pub enum Error { 12 | /// JSON errors 13 | #[cfg(feature = "json")] 14 | Json(serde_json::Error), 15 | 16 | /// Invalid header value 17 | HeaderValue, 18 | 19 | /// HTTP errors 20 | Http(hyper::http::Error), 21 | 22 | /// Hyper errors 23 | // TODO(tarcieri): rename this variant, possibly extracting the error? 24 | Hyper(hyper::Error), 25 | 26 | /// Proxy errors 27 | #[cfg(feature = "proxy")] 28 | Proxy(std::io::Error), 29 | } 30 | 31 | impl Display for Error { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | #[cfg(feature = "json")] 35 | Error::Json(err) => err.fmt(f), 36 | Error::HeaderValue => f.write_str("invalid header value"), 37 | Error::Http(err) => err.fmt(f), 38 | Error::Hyper(err) => err.fmt(f), 39 | #[cfg(feature = "proxy")] 40 | Error::Proxy(err) => err.fmt(f), 41 | } 42 | } 43 | } 44 | 45 | impl std::error::Error for Error {} 46 | 47 | impl From for Error { 48 | fn from(_: hyper::header::InvalidHeaderValue) -> Error { 49 | Error::HeaderValue 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(err: hyper::http::Error) -> Error { 55 | Error::Http(err) 56 | } 57 | } 58 | 59 | impl From for Error { 60 | fn from(err: hyper::Error) -> Error { 61 | Error::Hyper(err) 62 | } 63 | } 64 | 65 | #[cfg(feature = "json")] 66 | impl From for Error { 67 | fn from(err: serde_json::Error) -> Error { 68 | Error::Json(err) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /iqhttp/src/serializers.rs: -------------------------------------------------------------------------------- 1 | //! Serde serialization helpers. 2 | 3 | /// Helpers for serializing fields containing a [`Uri`][`crate::Uri`]. 4 | /// 5 | /// Annotate fields with `#[serde(with = "iqhttp::serializers::uri)]` to use these. 6 | pub mod uri { 7 | use crate::Uri; 8 | use serde::{de, ser, Deserialize, Serialize}; 9 | 10 | /// Deserialize [`Uri`]. 11 | pub fn deserialize<'de, D>(deserializer: D) -> Result 12 | where 13 | D: de::Deserializer<'de>, 14 | { 15 | String::deserialize(deserializer)? 16 | .parse() 17 | .map_err(de::Error::custom) 18 | } 19 | 20 | /// Serialize [`Uri`]. 21 | pub fn serialize(uri: &Uri, serializer: S) -> Result 22 | where 23 | S: ser::Serializer, 24 | { 25 | uri.to_string().serialize(serializer) 26 | } 27 | } 28 | 29 | /// Helpers for serializing fields containing an optional [`Uri`][`crate::Uri`]. 30 | /// 31 | /// Annotate fields with `#[serde(with = "iqhttp::serializers::uri_optional)]` to use these. 32 | pub mod uri_optional { 33 | use crate::Uri; 34 | use serde::{de, ser, Deserialize}; 35 | 36 | /// Deserialize an optional [`Uri`]. 37 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 38 | where 39 | D: de::Deserializer<'de>, 40 | { 41 | Option::::deserialize(deserializer)? 42 | .map(|uri| uri.parse().map_err(de::Error::custom)) 43 | .transpose() 44 | } 45 | 46 | /// Serialize an optional [`Uri`]. 47 | pub fn serialize(maybe_uri: &Option, serializer: S) -> Result 48 | where 49 | S: ser::Serializer, 50 | { 51 | match maybe_uri { 52 | Some(uri) => serializer.serialize_some(&uri.to_string()), 53 | None => serializer.serialize_none(), 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/hkd32.yml: -------------------------------------------------------------------------------- 1 | name: hkd32 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "hkd32/**" 7 | - "Cargo.*" 8 | - ".github/workflows/hkd32.yml" 9 | push: 10 | branches: main 11 | 12 | defaults: 13 | run: 14 | working-directory: hkd32 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | rust: 26 | - 1.63.0 # MSRV 27 | - stable 28 | target: 29 | - armv7a-none-eabi 30 | - thumbv7em-none-eabi 31 | - wasm32-unknown-unknown 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: ${{ matrix.rust }} 37 | target: ${{ matrix.target }} 38 | override: true 39 | profile: minimal 40 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features 41 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc 42 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bech32 43 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,bech32 44 | 45 | test: 46 | strategy: 47 | matrix: 48 | platform: 49 | - ubuntu-latest 50 | - macos-latest 51 | - windows-latest 52 | toolchain: 53 | - 1.70.0 # MSRV 54 | - stable 55 | runs-on: ${{ matrix.platform }} 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: actions-rs/toolchain@v1 59 | with: 60 | toolchain: ${{ matrix.toolchain }} 61 | override: true 62 | profile: minimal 63 | - run: cargo test --release 64 | - run: cargo test --release --all-features 65 | -------------------------------------------------------------------------------- /bip32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bip32" 3 | version = "0.6.0-pre.1" 4 | description = """ 5 | BIP32 hierarchical key derivation implemented in a generic, no_std-friendly 6 | manner. Supports deriving keys using the pure Rust k256 crate or the 7 | C library-backed secp256k1 crate 8 | """ 9 | authors = ["Tony Arcieri "] 10 | license = "Apache-2.0 OR MIT" 11 | homepage = "https://github.com/iqlusioninc/crates/" 12 | repository = "https://github.com/iqlusioninc/crates/tree/main/bip32" 13 | categories = ["cryptography", "no-std"] 14 | keywords = ["crypto", "bip32", "bip39", "derivation", "mnemonic"] 15 | readme = "README.md" 16 | edition = "2021" 17 | rust-version = "1.81" 18 | 19 | [dependencies] 20 | bs58 = { version = "0.5", default-features = false, features = ["check"] } 21 | hmac = { version = "=0.13.0-pre.4", default-features = false } 22 | rand_core = { version = "0.6", default-features = false } 23 | ripemd = { version = "=0.2.0-pre.4", default-features = false } 24 | sha2 = { version = "=0.11.0-pre.4", default-features = false } 25 | subtle = { version = "2", default-features = false } 26 | zeroize = { version = "1", default-features = false } 27 | 28 | # optional dependencies 29 | k256 = { version = "=0.14.0-pre.2", optional = true, default-features = false, features = ["ecdsa", "sha256"] } 30 | once_cell = { version = "1", optional = true } 31 | pbkdf2 = { version = "=0.13.0-pre.1", optional = true, default-features = false, features = ["hmac"] } 32 | secp256k1-ffi = { package = "secp256k1", version = "0.31", optional = true, default-features = false } 33 | 34 | [dev-dependencies] 35 | hex-literal = "0.4" 36 | rand_core = { version = "0.6", features = ["std"] } 37 | 38 | [features] 39 | default = ["bip39", "secp256k1", "std"] 40 | alloc = ["secp256k1-ffi?/alloc", "zeroize/alloc"] 41 | bip39 = ["mnemonic", "pbkdf2", "std"] 42 | mnemonic = ["alloc", "once_cell"] 43 | secp256k1 = ["k256"] 44 | std = ["alloc"] 45 | 46 | [package.metadata.docs.rs] 47 | all-features = true 48 | rustdoc-args = ["--cfg", "docsrs"] 49 | -------------------------------------------------------------------------------- /iqhttp/README.md: -------------------------------------------------------------------------------- 1 | # iqhttp iqlusion 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | [![Apache 2.0 Licensed][license-image]][license-link] 7 | ![MSRV][rustc-image] 8 | 9 | iqlusion's HTTPS toolkit. Provides a high-level wrapper around [`hyper`], with 10 | built-in SSL/TLS support based on [`rustls`]. 11 | 12 | [Documentation][docs-link] 13 | 14 | ## Minimum Supported Rust Version 15 | 16 | Rust **1.63** 17 | 18 | ## License 19 | 20 | Copyright © 2021-2022 iqlusion 21 | 22 | **iqhttp** is distributed under the terms of either the MIT license 23 | or the Apache License (Version 2.0), at your option. 24 | 25 | See [LICENSE] (Apache License, Version 2.0) file in the `iqlusioninc/crates` 26 | toplevel directory of this repository or [LICENSE-MIT] for details. 27 | 28 | ## Contribution 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally 31 | submitted for inclusion in the work by you shall be dual licensed as above, 32 | without any additional terms or conditions. 33 | 34 | [//]: # (badges) 35 | 36 | [crate-image]: https://img.shields.io/crates/v/iqhttp.svg?logo=rust 37 | [crate-link]: https://crates.io/crates/iqhttp 38 | [docs-image]: https://docs.rs/iqhttp/badge.svg 39 | [docs-link]: https://docs.rs/iqhttp/ 40 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/iqhttp.yml/badge.svg 41 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/iqhttp.yml 42 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 43 | [license-link]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 44 | [rustc-image]: https://img.shields.io/badge/rustc-1.63+-blue.svg 45 | 46 | [//]: # (general links) 47 | 48 | [`hyper`]: https://github.com/hyperium/hyper 49 | [`rustls`]: https://github.com/ctz/rustls 50 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 51 | [LICENSE-MIT]: https://github.com/iqlusioninc/crates/blob/main/iqhttp/LICENSE-MIT 52 | -------------------------------------------------------------------------------- /subtle-encoding/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type 2 | 3 | #[cfg(all(feature = "alloc", not(feature = "std")))] 4 | use alloc::string::FromUtf8Error; 5 | use core::fmt; 6 | #[cfg(feature = "std")] 7 | use std::{io, string::FromUtf8Error}; 8 | 9 | /// Error type 10 | #[derive(Clone, Eq, PartialEq, Debug)] 11 | pub enum Error { 12 | /// Checksum fdoes not match expected value 13 | ChecksumInvalid, 14 | 15 | /// Data is not encoded correctly 16 | EncodingInvalid, 17 | 18 | /// Error performing I/O operation 19 | IoError, 20 | 21 | /// Input or output buffer is an incorrect length 22 | LengthInvalid, 23 | 24 | /// Padding missing/invalid 25 | PaddingInvalid, 26 | 27 | /// Trailing whitespace detected 28 | // TODO: handle trailing whitespace? 29 | TrailingWhitespace, 30 | } 31 | 32 | impl fmt::Display for Error { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | let description = match self { 35 | Error::ChecksumInvalid => "checksum mismatch", 36 | Error::EncodingInvalid => "bad encoding", 37 | Error::IoError => "I/O error", 38 | Error::LengthInvalid => "invalid length", 39 | Error::PaddingInvalid => "padding invalid", 40 | Error::TrailingWhitespace => "trailing whitespace", 41 | }; 42 | 43 | write!(f, "{}", description) 44 | } 45 | } 46 | 47 | #[cfg(feature = "std")] 48 | impl std::error::Error for Error {} 49 | 50 | /// Assert that the provided condition is true, or else return the given error 51 | macro_rules! ensure { 52 | ($condition:expr, $err:path) => { 53 | if !($condition) { 54 | return Err($err); 55 | } 56 | }; 57 | } 58 | 59 | #[cfg(feature = "std")] 60 | impl From for Error { 61 | fn from(_err: io::Error) -> Error { 62 | // TODO: preserve cause or error message? 63 | Error::IoError 64 | } 65 | } 66 | 67 | #[cfg(feature = "alloc")] 68 | impl From for Error { 69 | fn from(_err: FromUtf8Error) -> Error { 70 | // TODO: preserve cause or error message? 71 | Error::EncodingInvalid 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/bip32.yml: -------------------------------------------------------------------------------- 1 | name: bip32 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "bip32/**" 7 | - "hkd32/**" 8 | - "Cargo.*" 9 | - ".github/workflows/bip32.yml" 10 | push: 11 | branches: main 12 | 13 | defaults: 14 | run: 15 | working-directory: bip32 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | RUSTFLAGS: "-Dwarnings" 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | rust: 27 | - 1.81.0 # MSRV 28 | - stable 29 | target: 30 | - armv7a-none-eabi 31 | - thumbv7em-none-eabi 32 | - wasm32-unknown-unknown 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | target: ${{ matrix.target }} 39 | override: true 40 | profile: minimal 41 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features 42 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc 43 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features secp256k1 44 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,secp256k1 45 | 46 | test: 47 | strategy: 48 | matrix: 49 | platform: 50 | - ubuntu-latest 51 | - macos-latest 52 | - windows-latest 53 | toolchain: 54 | - 1.81.0 # MSRV 55 | - stable 56 | runs-on: ${{ matrix.platform }} 57 | steps: 58 | - uses: actions/checkout@v4 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: ${{ matrix.toolchain }} 62 | override: true 63 | profile: minimal 64 | - run: cargo test --release --no-default-features 65 | - run: cargo test --release --no-default-features --features secp256k1,bip39 66 | - run: cargo test --release --no-default-features --features secp256k1-ffi,bip39 67 | - run: cargo test --release 68 | - run: cargo test --release --all-features 69 | -------------------------------------------------------------------------------- /subtle-encoding/src/bech32/base32.rs: -------------------------------------------------------------------------------- 1 | //! Base32 encoding support 2 | 3 | use crate::error::Error; 4 | use alloc::vec::Vec; 5 | 6 | /// Encode binary data as base32 7 | pub fn encode(data: &[u8]) -> Vec { 8 | convert(data, 8, 5).unwrap() 9 | } 10 | 11 | /// Decode data from base32 12 | pub fn decode(data: &[u8]) -> Result, Error> { 13 | convert(data, 5, 8) 14 | } 15 | 16 | fn convert(data: &[u8], src_base: u32, dst_base: u32) -> Result, Error> { 17 | let mut acc = 0u32; 18 | let mut bits = 0u32; 19 | let mut result = vec![]; // TODO: calculate size and use with_capacity 20 | let max = (1u32 << dst_base) - 1; 21 | 22 | for value in data { 23 | let v = u32::from(*value); 24 | ensure!(v >> src_base == 0, Error::EncodingInvalid); 25 | 26 | acc = (acc << src_base) | v; 27 | bits += src_base; 28 | 29 | while bits >= dst_base { 30 | bits -= dst_base; 31 | result.push(((acc >> bits) & max) as u8); 32 | } 33 | } 34 | 35 | if src_base > dst_base { 36 | if bits > 0 { 37 | result.push(((acc << (dst_base - bits)) & max) as u8); 38 | } 39 | } else if bits >= src_base || ((acc << (dst_base - bits)) & max) != 0 { 40 | return Err(Error::PaddingInvalid); 41 | } 42 | 43 | Ok(result) 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::{decode, encode}; 49 | use crate::error::Error; 50 | 51 | const EXAMPLE_ENCODED: &[u8] = &[0, 4, 1, 0, 6, 1, 8, 9, 2, 4, 16, 20, 3, 0, 8]; 52 | const EXAMPLE_DECODED: &[u8] = &[1, 2, 3, 5, 9, 17, 33, 65, 129]; 53 | 54 | #[test] 55 | fn encode_base32() { 56 | assert_eq!(EXAMPLE_ENCODED, encode(EXAMPLE_DECODED).as_slice()); 57 | } 58 | 59 | #[test] 60 | fn decode_valid_base32() { 61 | assert_eq!(EXAMPLE_DECODED, decode(EXAMPLE_ENCODED).unwrap().as_slice()); 62 | } 63 | 64 | #[test] 65 | fn decode_padding_error() { 66 | let encoded_len = EXAMPLE_ENCODED.len(); 67 | assert_eq!( 68 | Err(Error::PaddingInvalid), 69 | decode(&EXAMPLE_ENCODED[..encoded_len - 1]) 70 | ); 71 | } 72 | 73 | #[test] 74 | fn decode_range_error() { 75 | assert_eq!( 76 | Err(Error::EncodingInvalid), 77 | decode(EXAMPLE_DECODED) // decode the already decoded data 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /signatory/src/key/handle.rs: -------------------------------------------------------------------------------- 1 | //! Handle to a particular key. 2 | 3 | #[cfg(feature = "ecdsa")] 4 | #[allow(unused_imports)] 5 | use crate::ecdsa; 6 | 7 | #[cfg(feature = "ed25519")] 8 | use crate::ed25519; 9 | 10 | /// Handle to a particular key. 11 | /// 12 | /// Uniquely identifies a particular key in the keyring. 13 | #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] 14 | #[non_exhaustive] 15 | pub enum KeyHandle { 16 | /// ECDSA/P-256. 17 | #[cfg(feature = "nistp256")] 18 | EcdsaNistP256(ecdsa::nistp256::VerifyingKey), 19 | 20 | /// ECDSA/P-384. 21 | #[cfg(feature = "nistp384")] 22 | EcdsaNistP384(ecdsa::nistp384::VerifyingKey), 23 | 24 | /// ECDSA/secp256k1. 25 | #[cfg(feature = "secp256k1")] 26 | EcdsaSecp256k1(ecdsa::secp256k1::VerifyingKey), 27 | 28 | /// Ed25519. 29 | #[cfg(feature = "ed25519")] 30 | Ed25519(ed25519::VerifyingKey), 31 | } 32 | 33 | impl KeyHandle { 34 | /// Get ECDSA/P-256 verifying key, if this is an ECDSA/P-256 key. 35 | #[cfg(feature = "nistp256")] 36 | pub fn ecdsa_nistp256(&self) -> Option { 37 | match self { 38 | KeyHandle::EcdsaNistP256(pk) => Some(*pk), 39 | #[allow(unreachable_patterns)] 40 | _ => None, 41 | } 42 | } 43 | 44 | /// Get ECDSA/P-384 verifying key, if this is an ECDSA/P-384 key. 45 | #[cfg(feature = "nistp384")] 46 | pub fn ecdsa_nistp384(&self) -> Option { 47 | match self { 48 | KeyHandle::EcdsaNistP384(pk) => Some(*pk), 49 | #[allow(unreachable_patterns)] 50 | _ => None, 51 | } 52 | } 53 | 54 | /// Get ECDSA/secp256k1 verifying key, if this is an ECDSA/secp256k1 key. 55 | #[cfg(feature = "secp256k1")] 56 | pub fn ecdsa_secp256k1(&self) -> Option { 57 | match self { 58 | KeyHandle::EcdsaSecp256k1(pk) => Some(*pk), 59 | #[allow(unreachable_patterns)] 60 | _ => None, 61 | } 62 | } 63 | 64 | /// Get Ed25519 verifying key, if this is an Ed25519 key. 65 | #[cfg(feature = "ed25519")] 66 | pub fn ed25519(&self) -> Option { 67 | match self { 68 | KeyHandle::Ed25519(pk) => Some(*pk), 69 | #[allow(unreachable_patterns)] 70 | _ => None, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /canonical-path/README.md: -------------------------------------------------------------------------------- 1 | # canonical-path.rs 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Apache 2.0 Licensed][license-image]][license-link] 6 | ![MSRV][rustc-image] 7 | [![Build Status][build-image]][build-link] 8 | 9 | `std::fs::Path` and `PathBuf`-like types for representing canonical 10 | filesystem paths. 11 | 12 | In the same way a `str` "guarantees" a `&[u8]` contains only valid UTF-8 data, 13 | `CanonicalPath` and `CanonicalPathBuf` guarantee that the paths they represent 14 | are canonical, or at least, were canonical at the time they were created. 15 | 16 | [Documentation][docs-link] 17 | 18 | ## Minimum Supported Rust Version 19 | 20 | Rust **1.63** or newer. 21 | 22 | In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope 23 | for this crate's SemVer guarantees), however when we do it will be accompanied by 24 | a minor version bump. 25 | 26 | ## License 27 | 28 | Copyright © 2018-2023 iqlusion 29 | 30 | Licensed under the Apache License, Version 2.0 (the "License"); 31 | you may not use this file except in compliance with the License. 32 | You may obtain a copy of the License at 33 | 34 | https://www.apache.org/licenses/LICENSE-2.0 35 | 36 | Unless required by applicable law or agreed to in writing, software 37 | distributed under the License is distributed on an "AS IS" BASIS, 38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | See the License for the specific language governing permissions and 40 | limitations under the License. 41 | 42 | ## Contribution 43 | 44 | Unless you explicitly state otherwise, any contribution intentionally 45 | submitted for inclusion in the work by you shall be dual licensed as above, 46 | without any additional terms or conditions. 47 | 48 | [//]: # (badges) 49 | 50 | [crate-image]: https://img.shields.io/crates/v/canonical-path.svg 51 | [crate-link]: https://crates.io/crates/canonical-path 52 | [docs-image]: https://docs.rs/canonical-path/badge.svg 53 | [docs-link]: https://docs.rs/canonical-path/ 54 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 55 | [license-link]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 56 | [rustc-image]: https://img.shields.io/badge/rustc-1.63+-blue.svg 57 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/canonical-path.yml/badge.svg 58 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/canonical-path.yml 59 | -------------------------------------------------------------------------------- /iqhttp/src/query.rs: -------------------------------------------------------------------------------- 1 | //! HTTP query strings. 2 | 3 | use crate::Uri; 4 | use std::{ 5 | collections::BTreeMap as Map, 6 | fmt::{self, Display}, 7 | iter::FromIterator, 8 | }; 9 | 10 | /// HTTP query string: parameters to a request included in the URL. 11 | /// 12 | /// 13 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 14 | pub struct Query(Map); 15 | 16 | impl Query { 17 | /// Create params 18 | pub fn new() -> Self { 19 | Self::default() 20 | } 21 | 22 | /// Add params 23 | pub fn add(&mut self, field: impl Into, value: impl Into) -> bool { 24 | // TODO(tarcieri): return result? 25 | self.0.insert(field.into(), value.into()).is_none() 26 | } 27 | 28 | /// Compute [`Uri`] 29 | // TODO(tarcieri): factor this onto a prospective `Path` type 30 | pub(crate) fn to_request_uri(&self, hostname: &str, path: &str) -> Uri { 31 | let path_and_query = format!("{}?{}", path, self); 32 | 33 | Uri::builder() 34 | .scheme("https") 35 | .authority(hostname) 36 | .path_and_query(path_and_query.as_str()) 37 | .build() 38 | .expect("error building request from hostname and Query") 39 | } 40 | } 41 | 42 | impl Display for Query { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | for (i, (field, value)) in self.0.iter().enumerate() { 45 | write!(f, "{}={}", field, value)?; 46 | 47 | if i < self.0.len() - 1 { 48 | write!(f, "&")?; 49 | } 50 | } 51 | 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl<'a> FromIterator<&'a (String, String)> for Query { 57 | fn from_iter(iter: I) -> Self 58 | where 59 | I: IntoIterator, 60 | { 61 | let mut params = Self::new(); 62 | 63 | for (field, value) in iter { 64 | params.add(field, value); 65 | } 66 | 67 | params 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::{FromIterator, Query}; 74 | 75 | #[test] 76 | fn params_to_string() { 77 | let params = Query::from_iter(&[ 78 | ("foo".to_owned(), "value_1".to_owned()), 79 | ("bar".to_owned(), "value_2".to_owned()), 80 | ]); 81 | 82 | let serialized_params = params.to_string(); 83 | assert_eq!(&serialized_params, "bar=value_2&foo=value_1"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /signatory/src/algorithm.rs: -------------------------------------------------------------------------------- 1 | //! Algorithms supported by this library. 2 | 3 | use crate::{Error, Result}; 4 | 5 | #[cfg(feature = "ed25519")] 6 | use crate::ed25519; 7 | 8 | /// Signature algorithms. 9 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] 10 | #[non_exhaustive] 11 | pub enum Algorithm { 12 | /// ECDSA with NIST P-256. 13 | #[cfg(feature = "nistp256")] 14 | EcdsaNistP256, 15 | 16 | /// ECDSA with NIST P-384. 17 | #[cfg(feature = "nistp384")] 18 | EcdsaNistP384, 19 | 20 | /// ECDSA with secp256k1. 21 | #[cfg(feature = "secp256k1")] 22 | EcdsaSecp256k1, 23 | 24 | /// Ed25519. 25 | #[cfg(feature = "ed25519")] 26 | Ed25519, 27 | } 28 | 29 | impl Algorithm { 30 | /// Is the algorithm ECDSA? 31 | #[cfg(feature = "ecdsa")] 32 | pub fn is_ecdsa(self) -> bool { 33 | #[cfg(feature = "nistp256")] 34 | if self == Algorithm::EcdsaNistP256 { 35 | return true; 36 | } 37 | 38 | #[cfg(feature = "nistp384")] 39 | if self == Algorithm::EcdsaNistP384 { 40 | return true; 41 | } 42 | 43 | #[cfg(feature = "secp256k1")] 44 | if self == Algorithm::EcdsaSecp256k1 { 45 | return true; 46 | } 47 | 48 | false 49 | } 50 | } 51 | 52 | impl TryFrom> for Algorithm { 53 | type Error = Error; 54 | 55 | #[allow(unused_variables)] 56 | fn try_from(pkcs8_alg_id: pkcs8::AlgorithmIdentifierRef<'_>) -> Result { 57 | #[cfg(feature = "ecdsa")] 58 | if pkcs8_alg_id.oid == ecdsa::elliptic_curve::ALGORITHM_OID { 59 | #[cfg(any(feature = "nistp256", feature = "secp256k1"))] 60 | use pkcs8::AssociatedOid; 61 | 62 | #[cfg(feature = "nistp256")] 63 | if pkcs8_alg_id.parameters_oid() == Ok(crate::ecdsa::NistP256::OID) { 64 | return Ok(Self::EcdsaNistP256); 65 | } 66 | 67 | #[cfg(feature = "nistp384")] 68 | if pkcs8_alg_id.parameters_oid() == Ok(crate::ecdsa::NistP384::OID) { 69 | return Ok(Self::EcdsaNistP384); 70 | } 71 | 72 | #[cfg(feature = "secp256k1")] 73 | if pkcs8_alg_id.parameters_oid() == Ok(crate::ecdsa::Secp256k1::OID) { 74 | return Ok(Self::EcdsaSecp256k1); 75 | } 76 | } 77 | 78 | #[cfg(feature = "ed25519")] 79 | if pkcs8_alg_id == ed25519::ALGORITHM_ID { 80 | return Ok(Self::Ed25519); 81 | } 82 | 83 | Err(Error::AlgorithmInvalid) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /signatory/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 digital signature library with support for elliptic curve digital 10 | signature algorithms, namely ECDSA ([FIPS 186‑4]) and Ed25519 ([RFC 8032]). 11 | 12 | [Documentation][docs-link] 13 | 14 | ## About 15 | 16 | This crate provides a thread-and-object-safe API for both creating and 17 | verifying elliptic curve digital signatures, using either software-based 18 | or hardware-based providers. 19 | 20 | The following algorithms are supported: 21 | 22 | - [ECDSA]: Elliptic Curve Digital Signature Algorithm ([FIPS 186‑4]) 23 | - [Ed25519]: Edwards Digital Signature Algorithm (EdDSA) instantiated using 24 | the twisted Edwards form of Curve25519 ([RFC 8032]). 25 | 26 | ## Minimum Supported Rust Version 27 | 28 | Rust **1.65** or newer. 29 | 30 | In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope 31 | for this crate's SemVer guarantees), however when we do it will be accompanied by 32 | a minor version bump. 33 | 34 | ## License 35 | 36 | **Signatory** is distributed under your choice of the terms of the MIT license 37 | and/or the Apache License (Version 2.0). 38 | 39 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 40 | 41 | ## Contribution 42 | 43 | Unless you explicitly state otherwise, any contribution intentionally 44 | submitted for inclusion in the work by you shall be dual licensed as above, 45 | without any additional terms or conditions. 46 | 47 | [//]: # (badges) 48 | 49 | [logo]: https://storage.googleapis.com/iqlusion-production-web/github/signatory/signatory.svg 50 | [crate-image]: https://img.shields.io/crates/v/signatory.svg 51 | [crate-link]: https://crates.io/crates/signatory 52 | [docs-image]: https://docs.rs/signatory/badge.svg 53 | [docs-link]: https://docs.rs/signatory/ 54 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 55 | [rustc-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg 56 | [build-image]: https://github.com/iqlusioninc/crates/workflows/signatory/badge.svg?branch=main&event=push 57 | [build-link]: https://github.com/iqlusioninc/crates/actions 58 | 59 | [//]: # (general links) 60 | 61 | [ECDSA]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm 62 | [Ed25519]: https://en.wikipedia.org/wiki/EdDSA#Ed25519 63 | [FIPS 186‑4]: https://csrc.nist.gov/publications/detail/fips/186/4/final 64 | [RFC 8032]: https://tools.ietf.org/html/rfc8032 65 | -------------------------------------------------------------------------------- /bip32/src/mnemonic/language.rs: -------------------------------------------------------------------------------- 1 | //! Wordlist support 2 | //! 3 | //! NOTE: This implementation is not constant time and may leak information 4 | //! via timing side-channels! 5 | //! 6 | //! Adapted from the `bip39` crate 7 | 8 | use super::bits::{Bits, Bits11}; 9 | use alloc::{collections::BTreeMap, vec::Vec}; 10 | 11 | /// Supported languages. 12 | /// 13 | /// Presently only English is specified by the BIP39 standard 14 | #[derive(Copy, Clone, Debug, Default)] 15 | pub enum Language { 16 | /// English is presently the only supported language 17 | #[default] 18 | English, 19 | } 20 | 21 | impl Language { 22 | /// Get the word list for this language 23 | pub(crate) fn wordlist(&self) -> &'static WordList { 24 | match *self { 25 | Language::English => &lazy::WORDLIST_ENGLISH, 26 | } 27 | } 28 | 29 | /// Get a wordmap that allows word -> index lookups in the word list 30 | pub(crate) fn wordmap(&self) -> &'static WordMap { 31 | match *self { 32 | Language::English => &lazy::WORDMAP_ENGLISH, 33 | } 34 | } 35 | } 36 | 37 | pub(crate) struct WordMap { 38 | inner: BTreeMap<&'static str, Bits11>, 39 | } 40 | 41 | pub(crate) struct WordList { 42 | inner: Vec<&'static str>, 43 | } 44 | 45 | impl WordMap { 46 | pub fn get_bits(&self, word: &str) -> Option { 47 | self.inner.get(word).cloned() 48 | } 49 | } 50 | 51 | impl WordList { 52 | pub fn get_word(&self, bits: Bits11) -> &'static str { 53 | self.inner[bits.bits() as usize] 54 | } 55 | } 56 | 57 | // TODO(tarcieri): use `const fn` instead of `Lazy` 58 | mod lazy { 59 | use super::{Bits11, WordList, WordMap}; 60 | use alloc::vec::Vec; 61 | use once_cell::sync::Lazy; 62 | 63 | /// lazy generation of the word list 64 | fn gen_wordlist(lang_words: &'static str) -> WordList { 65 | let inner: Vec<_> = lang_words.split_whitespace().collect(); 66 | 67 | debug_assert!(inner.len() == 2048, "Invalid wordlist length"); 68 | 69 | WordList { inner } 70 | } 71 | 72 | /// lazy generation of the word map 73 | fn gen_wordmap(wordlist: &WordList) -> WordMap { 74 | let inner = wordlist 75 | .inner 76 | .iter() 77 | .enumerate() 78 | .map(|(i, item)| (*item, Bits11::from(i as u16))) 79 | .collect(); 80 | 81 | WordMap { inner } 82 | } 83 | 84 | pub(crate) static WORDLIST_ENGLISH: Lazy = 85 | Lazy::new(|| gen_wordlist(include_str!("langs/english.txt"))); 86 | 87 | pub(crate) static WORDMAP_ENGLISH: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_ENGLISH)); 88 | } 89 | -------------------------------------------------------------------------------- /hkd32/src/mnemonic/language.rs: -------------------------------------------------------------------------------- 1 | //! Wordlist support 2 | //! 3 | //! NOTE: This implementation is not constant time and may leak information 4 | //! via timing side-channels! 5 | //! 6 | //! Adapted from the `bip39` crate 7 | 8 | use super::bits::{Bits, Bits11}; 9 | use crate::Error; 10 | use alloc::{collections::BTreeMap, vec::Vec}; 11 | 12 | /// Supported languages. 13 | /// 14 | /// Presently only English is specified by the BIP39 standard 15 | #[derive(Copy, Clone, Debug, Default)] 16 | pub enum Language { 17 | /// English is presently the only supported language 18 | #[default] 19 | English, 20 | } 21 | 22 | impl Language { 23 | /// Get the word list for this language 24 | pub(crate) fn wordlist(&self) -> &'static WordList { 25 | match *self { 26 | Language::English => &lazy::WORDLIST_ENGLISH, 27 | } 28 | } 29 | 30 | /// Get a wordmap that allows word -> index lookups in the word list 31 | pub(crate) fn wordmap(&self) -> &'static WordMap { 32 | match *self { 33 | Language::English => &lazy::WORDMAP_ENGLISH, 34 | } 35 | } 36 | } 37 | 38 | pub(crate) struct WordMap { 39 | inner: BTreeMap<&'static str, Bits11>, 40 | } 41 | 42 | pub(crate) struct WordList { 43 | inner: Vec<&'static str>, 44 | } 45 | 46 | impl WordMap { 47 | pub fn get_bits(&self, word: &str) -> Result { 48 | self.inner.get(word).cloned().ok_or(Error) 49 | } 50 | } 51 | 52 | impl WordList { 53 | pub fn get_word(&self, bits: Bits11) -> &'static str { 54 | self.inner[bits.bits() as usize] 55 | } 56 | } 57 | 58 | // TODO(tarcieri): use `const fn` instead of `Lazy` 59 | mod lazy { 60 | use super::{Bits11, WordList, WordMap}; 61 | use alloc::vec::Vec; 62 | use once_cell::sync::Lazy; 63 | 64 | /// lazy generation of the word list 65 | fn gen_wordlist(lang_words: &'static str) -> WordList { 66 | let inner: Vec<_> = lang_words.split_whitespace().collect(); 67 | 68 | debug_assert!(inner.len() == 2048, "Invalid wordlist length"); 69 | 70 | WordList { inner } 71 | } 72 | 73 | /// lazy generation of the word map 74 | fn gen_wordmap(wordlist: &WordList) -> WordMap { 75 | let inner = wordlist 76 | .inner 77 | .iter() 78 | .enumerate() 79 | .map(|(i, item)| (*item, Bits11::from(i as u16))) 80 | .collect(); 81 | 82 | WordMap { inner } 83 | } 84 | 85 | pub(crate) static WORDLIST_ENGLISH: Lazy = 86 | Lazy::new(|| gen_wordlist(include_str!("langs/english.txt"))); 87 | 88 | pub(crate) static WORDMAP_ENGLISH: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_ENGLISH)); 89 | } 90 | -------------------------------------------------------------------------------- /hkd32/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! HMAC-based Hierarchical Key Derivation: deterministically derive a 2 | //! hierarchy of symmetric keys from initial keying material through 3 | //! repeated applications of the Hash-based Message Authentication Code 4 | //! (HMAC) construction. 5 | //! 6 | //! This library implements a fully symmetric construction inspired by 7 | //! [BIP-0032: Hierarchical Deterministic Wallets][bip32]. 8 | //! 9 | //! # Usage 10 | //! 11 | //! To derive a key using HKD32, you'll need the following: 12 | //! 13 | //! - [`KeyMaterial`]: a 32-byte (256-bit) uniformly random value 14 | //! - [`Path`] or [`PathBuf`]: path to the child key 15 | //! 16 | //! Derivation paths can be raw bytestrings but also support a Unix path-like 17 | //! syntax which can be parsed using the `String::parse` method: 18 | //! 19 | //! ```rust 20 | //! let path = "/foo/bar/baz".parse::().unwrap(); 21 | //! ``` 22 | //! 23 | //! # Example 24 | //! 25 | //! ```rust 26 | //! use rand_core::OsRng; 27 | //! 28 | //! // Parent key 29 | //! let input_key_material = hkd32::KeyMaterial::random(&mut OsRng); 30 | //! 31 | //! // Path to the child key 32 | //! let derivation_path = "/foo/bar/baz".parse::().unwrap(); 33 | //! 34 | //! // Derive subkey from the parent key. Call `as_bytes()` on this to obtain 35 | //! // a byte slice containing the derived key. 36 | //! let output_key_material = input_key_material.derive_subkey(derivation_path); 37 | //! ``` 38 | //! 39 | //! [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 40 | 41 | #![no_std] 42 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 43 | #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] 44 | 45 | #[cfg(feature = "alloc")] 46 | #[cfg_attr(any(feature = "bip39", test), macro_use)] 47 | extern crate alloc; 48 | 49 | #[cfg(feature = "std")] 50 | extern crate std; 51 | 52 | #[cfg(feature = "mnemonic")] 53 | pub mod mnemonic; 54 | 55 | mod key_material; 56 | mod path; 57 | 58 | #[cfg(feature = "alloc")] 59 | mod pathbuf; 60 | 61 | pub use self::{key_material::*, path::*}; 62 | 63 | #[cfg(feature = "alloc")] 64 | pub use self::pathbuf::PathBuf; 65 | 66 | /// Delimiter used for strings containing paths 67 | pub const DELIMITER: char = '/'; 68 | 69 | /// Size of input key material and derived keys. 70 | /// 71 | /// Note: the name HKD32 is both a play on this size and "BIP32". 72 | pub const KEY_SIZE: usize = 32; 73 | 74 | /// Opaque error type 75 | #[derive(Copy, Clone, Debug)] 76 | pub struct Error; 77 | 78 | #[cfg(feature = "std")] 79 | impl std::error::Error for Error {} 80 | 81 | use core::fmt; 82 | 83 | impl fmt::Display for Error { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | write!(f, "Error") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /signatory/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type 2 | 3 | use core::fmt::{self, Display}; 4 | 5 | /// Result type 6 | pub type Result = core::result::Result; 7 | 8 | /// Error type 9 | #[derive(Debug)] 10 | #[non_exhaustive] 11 | pub enum Error { 12 | /// Algorithm is invalid. 13 | AlgorithmInvalid, 14 | 15 | /// Duplicate key in keyring. 16 | DuplicateKey, 17 | 18 | /// ECDSA errors. 19 | #[cfg(feature = "ecdsa")] 20 | Ecdsa, 21 | 22 | /// Key name is invalid. 23 | KeyNameInvalid, 24 | 25 | /// I/O errors 26 | #[cfg(feature = "std")] 27 | Io(std::io::Error), 28 | 29 | /// Expected a directory, found something else 30 | #[cfg(feature = "std")] 31 | NotADirectory, 32 | 33 | /// Parse errors for raw/non-PKCS#8 keys. 34 | Parse, 35 | 36 | /// Permissions error, not required mode 37 | #[cfg(feature = "std")] 38 | Permissions, 39 | 40 | /// PKCS#8 errors 41 | Pkcs8(pkcs8::Error), 42 | } 43 | 44 | impl Display for Error { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | match self { 47 | Self::AlgorithmInvalid => f.write_str("invalid algorithm"), 48 | Self::DuplicateKey => f.write_str("duplicate key"), 49 | #[cfg(feature = "ecdsa")] 50 | Self::Ecdsa => f.write_str("ECDSA error"), 51 | Self::KeyNameInvalid => f.write_str("invalid key name"), 52 | #[cfg(feature = "std")] 53 | Self::Io(err) => write!(f, "{}", err), 54 | #[cfg(feature = "std")] 55 | Self::NotADirectory => f.write_str("not a directory"), 56 | Self::Parse => f.write_str("parse error"), 57 | #[cfg(feature = "std")] 58 | Self::Permissions => f.write_str("invalid file permissions"), 59 | Self::Pkcs8(err) => write!(f, "{}", err), 60 | } 61 | } 62 | } 63 | 64 | #[cfg(feature = "std")] 65 | impl std::error::Error for Error {} 66 | 67 | #[cfg(feature = "ecdsa")] 68 | impl From for Error { 69 | fn from(_: ecdsa::Error) -> Error { 70 | Error::Ecdsa 71 | } 72 | } 73 | 74 | impl From for Error { 75 | fn from(err: pkcs8::Error) -> Error { 76 | Error::Pkcs8(err) 77 | } 78 | } 79 | 80 | impl From for Error { 81 | fn from(err: pkcs8::der::Error) -> Error { 82 | Error::Pkcs8(err.into()) 83 | } 84 | } 85 | 86 | impl From for Error { 87 | fn from(err: pkcs8::der::pem::Error) -> Error { 88 | pkcs8::der::Error::from(err).into() 89 | } 90 | } 91 | 92 | #[cfg(feature = "std")] 93 | impl From for Error { 94 | fn from(err: std::io::Error) -> Error { 95 | Error::Io(err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /signatory/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.27.1 (2023-08-14) 8 | ### Changed 9 | - Bump `ed25519-dalek` to v2.0 ([#1167]) 10 | 11 | [#1167]: https://github.com/iqlusioninc/crates/pull/1167 12 | 13 | ## 0.27.0 (2023-04-05) 14 | ### Changed 15 | - Bump `ecdsa`to v0.16 ([#1105]) 16 | - Bump `k256` to v0.13 ([#1105]) 17 | - Bump `p256` to v0.13 ([#1105]) 18 | - Bump `p384` to v0.13 ([#1105]) 19 | - Bump `ed25519-dalek` to 2.0.0-rc.2 ([#1113]) 20 | 21 | [#1105]: https://github.com/iqlusioninc/crates/pull/1105 22 | [#1113]: https://github.com/iqlusioninc/crates/pull/1113 23 | 24 | ## 0.26.0 (2022-08-19) 25 | ### Added 26 | - `Send + Sync` bounds to inner `Box` for signer types ([#1037]) 27 | - ECDSA/P-384 support ([#1039]) 28 | 29 | [#1037]: https://github.com/iqlusioninc/crates/pull/1037 30 | [#1039]: https://github.com/iqlusioninc/crates/pull/1039 31 | 32 | ## 0.25.0 (2022-05-17) 33 | ### Changed 34 | - Bump `ecdsa` to v0.14 ([#994]) 35 | - Bump `k256` to v0.11 ([#994]) 36 | - Bump `p256` to v0.11 ([#994]) 37 | - Bump `pkcs8` to v0.9 ([#994]) 38 | - Bump `sha2` to v0.10 ([#994]) 39 | - MSRV 1.57 ([#994]) 40 | 41 | [#994]: https://github.com/iqlusioninc/crates/pull/994 42 | 43 | ## 0.24.0 (2022-01-05) 44 | ### Changed 45 | - Rust 2021 edition upgrade ([#889]) 46 | - Bump `k256` dependency to v0.10 ([#938]) 47 | 48 | [#889]: https://github.com/iqlusioninc/crates/pull/889 49 | [#938]: https://github.com/iqlusioninc/crates/pull/938 50 | 51 | ## 0.23.2 (2021-08-02) 52 | ### Added 53 | - `ed25519::VerifyingKey::to_bytes` ([#834]) 54 | 55 | [#834]: https://github.com/iqlusioninc/crates/pull/834 56 | 57 | ## 0.23.1 (2021-07-21) 58 | ### Added 59 | - `Algorithm::EcdsaNistP256` and `Algorithm::Ed25519` variants ([#817]) 60 | 61 | [#817]: https://github.com/iqlusioninc/crates/pull/817 62 | 63 | ## 0.23.0 (2021-07-20) 64 | ### Changed 65 | This release is effectively a complete rewrite of Signatory with a brand-new 66 | API, and as such contains changes too numerous to document. For that reason, 67 | we are pushing the reset button on the changelog. 68 | 69 | It still provides the same original set of functionality, including ECDSA and 70 | Ed25519 signatures, but temporarily drops support for P-384 and hardware-backed 71 | digital signature providers. 72 | 73 | The plan is to eventually add this functionality back. The new implementation 74 | is fundamentally built on the same original codebase, but refactored and 75 | extracted into other Rust crates. Given that, we hope to achieve feature 76 | parity with the original implementation quickly. 77 | -------------------------------------------------------------------------------- /hkd32/README.md: -------------------------------------------------------------------------------- 1 | # HMAC-based Hierarchical Key Derivation iqlusion 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Apache 2.0 Licensed][license-image]][license-link] 6 | ![MSRV][rustc-image] 7 | [![Build Status][build-image]][build-link] 8 | 9 | `hkd32` is a Rust library which implements a hierarchical deterministic 10 | symmetric key derivation construction inspired by 11 | [BIP-0032: Hierarchical Deterministic Wallets][bip32]. 12 | 13 | It can be used to deterministically derive a hierarchy of symmetric keys 14 | from initial keying material (or when the `mnemonic` feature is enabled, 15 | through a 24-word [BIP39] passphrase) by repeatedly applying the 16 | Hash-based Message Authentication Code (HMAC). 17 | 18 | This construction is specialized for deriving 32-byte (256-bit) keys from 19 | an initial 32-bytes of input key material. 20 | 21 | [Documentation][docs-link] 22 | 23 | ## Minimum Supported Rust Version 24 | 25 | - Rust **1.63** 26 | 27 | ## License 28 | 29 | Copyright © 2019-2025 iqlusion 30 | 31 | Includes code from the `bip39` crate. Copyright © 2017-2018 Stephen Oliver, 32 | with contributions by Maciej Hirsz. 33 | 34 | **hkd32** is distributed under the terms of either the MIT license 35 | or the Apache License (Version 2.0), at your option. 36 | 37 | See [LICENSE] (Apache License, Version 2.0) file in the `iqlusioninc/crates` 38 | toplevel directory of this repository or [LICENSE-MIT] for details. 39 | 40 | ## Contribution 41 | 42 | Unless you explicitly state otherwise, any contribution intentionally 43 | submitted for inclusion in the work by you shall be dual licensed as above, 44 | without any additional terms or conditions. 45 | 46 | [//]: # (badges) 47 | 48 | [crate-image]: https://img.shields.io/crates/v/hkd32.svg?logo=rust 49 | [crate-link]: https://crates.io/crates/hkd32 50 | [docs-image]: https://docs.rs/hkd32/badge.svg 51 | [docs-link]: https://docs.rs/hkd32/ 52 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 53 | [license-link]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 54 | [rustc-image]: https://img.shields.io/badge/rustc-1.63+-blue.svg 55 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/hkd32.yml/badge.svg 56 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/hkd32.yml 57 | 58 | [//]: # (general links) 59 | 60 | [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 61 | [bip39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki 62 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 63 | [LICENSE-MIT]: https://github.com/iqlusioninc/crates/blob/main/hkd32/LICENSE-MIT 64 | -------------------------------------------------------------------------------- /bip32/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type. 2 | 3 | use core::fmt::{self, Display}; 4 | 5 | /// Result type. 6 | pub type Result = core::result::Result; 7 | 8 | /// Error type. 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | #[non_exhaustive] 11 | pub enum Error { 12 | /// Base58 errors. 13 | Base58, 14 | 15 | /// BIP39-related errors. 16 | Bip39, 17 | 18 | /// Child number-related errors. 19 | ChildNumber, 20 | 21 | /// Cryptographic errors. 22 | Crypto, 23 | 24 | /// Decoding errors (not related to Base58). 25 | Decode, 26 | 27 | /// Maximum derivation depth exceeded. 28 | Depth, 29 | 30 | /// Seed length invalid. 31 | SeedLength, 32 | } 33 | 34 | impl Display for Error { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | match self { 37 | Error::Base58 => f.write_str("base58 error"), 38 | Error::Bip39 => f.write_str("bip39 error"), 39 | Error::ChildNumber => f.write_str("invalid child number"), 40 | Error::Crypto => f.write_str("cryptographic error"), 41 | Error::Decode => f.write_str("decoding error"), 42 | Error::Depth => f.write_str("maximum derivation depth exceeded"), 43 | Error::SeedLength => f.write_str("seed length invalid"), 44 | } 45 | } 46 | } 47 | 48 | #[cfg(feature = "std")] 49 | impl std::error::Error for Error {} 50 | 51 | impl From for Error { 52 | fn from(_: bs58::decode::Error) -> Error { 53 | Error::Base58 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(_: bs58::encode::Error) -> Error { 59 | Error::Base58 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(_: core::array::TryFromSliceError) -> Error { 65 | Error::Decode 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(_: hmac::digest::InvalidLength) -> Error { 71 | Error::Crypto 72 | } 73 | } 74 | 75 | #[cfg(feature = "secp256k1")] 76 | impl From for Error { 77 | fn from(_: k256::elliptic_curve::Error) -> Error { 78 | Error::Crypto 79 | } 80 | } 81 | 82 | #[cfg(feature = "secp256k1")] 83 | impl From for Error { 84 | fn from(_: k256::ecdsa::Error) -> Error { 85 | Error::Crypto 86 | } 87 | } 88 | 89 | #[cfg(feature = "secp256k1-ffi")] 90 | impl From for Error { 91 | fn from(_: secp256k1_ffi::Error) -> Error { 92 | Error::Crypto 93 | } 94 | } 95 | 96 | #[cfg(feature = "secp256k1-ffi")] 97 | impl From for Error { 98 | fn from(_: secp256k1_ffi::scalar::OutOfRangeError) -> Error { 99 | Error::Crypto 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to the [iqlusion crates] repository! 4 | 5 | [iqlusion crates]: https://github.com/iqlusioninc/crates 6 | 7 | To contribute to this repository, here is all you need to do: 8 | 9 | 1. Review [CODE_OF_CONDUCT.md] 10 | 2. Fork the project on GitHub and create a commit adding yourself to [AUTHORS.md] 11 | 3. Create one or more additional commits including your contributions, then open 12 | a [pull request] along with the commit adding yourself to [AUTHORS.md]. 13 | 14 | [CODE_OF_CONDUCT.md]: https://github.com/iqlusioninc/crates/blob/main/CODE_OF_CONDUCT.md 15 | [AUTHORS.md]: https://github.com/iqlusioninc/crates/blob/main/AUTHORS.md 16 | [pull request]: https://help.github.com/articles/about-pull-requests/ 17 | 18 | ## Code of Conduct 19 | 20 | First, we please ask you to review the [CODE_OF_CONDUCT.md], as we would like to 21 | make this a friendly, cordial, and harassment-free project where anyone can 22 | contribute regardless of race, gender, or sexual orientation. 23 | 24 | If you observe harassment which you do not think is being addressed, please 25 | [contact us] and we will seek to remedy the situation. 26 | 27 | [contact us]: mailto:oss@iqlusion.io 28 | 29 | ## Add Yourself to AUTHORS.md 30 | 31 | Before we can accept a PR, we need you to add yourself to the [AUTHORS.md] file, 32 | along with a statement that you are willing to license your contributions under 33 | the terms of the [Apache License, Version 2.0] (included in this repository in 34 | the toplevel [LICENSE] file). 35 | 36 | [Apache License, Version 2.0]: https://www.apache.org/licenses/LICENSE-2.0 37 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 38 | 39 | To do this, edit the [AUTHORS.md] file, inserting your name in the list of 40 | contributors (in roughly alphabetical order by last name, preferably) along with 41 | the name of the crate or crates you are contributing to. 42 | 43 | Commit the [AUTHORS.md] file alone (i.e. do not modify other files in the same 44 | commit, although it is fine to include this commit as part of your first PR to 45 | the project), and use the following commit message: 46 | 47 | ``` 48 | AUTHORS.md: adding [MY GITHUB USERNAME] and licensing my contributions 49 | 50 | I, [LEGAL NAME HERE], hereby agree to license all contributions I make 51 | to this project under the terms of the Apache License, Version 2.0. 52 | ``` 53 | 54 | Please replace `[MY GITHUB USERNAME]` with the GitHub username you are sending 55 | the PR from, including the `@` symbol (e.g. `@defunkt`), and also replacing 56 | `[LEGAL NAME HERE]` with your full legal name. 57 | [See this commit for an example](https://github.com/iqlusioninc/crates/commit/3f5e3d53c6960bd41e8b3832cea04ab47dae3cb9). 58 | 59 | If you have concerns about including your legal name in this file but would 60 | still like to contribute, please [contact us] and we can discuss other potential 61 | arrangements. 62 | -------------------------------------------------------------------------------- /bip32/README.md: -------------------------------------------------------------------------------- 1 | # BIP32: HD Wallets 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Apache 2.0 Licensed][license-image]][license-link] 6 | ![MSRV][rustc-image] 7 | [![Safety Dance][safety-image]][safety-link] 8 | [![Build Status][build-image]][build-link] 9 | 10 | BIP32 hierarchical key derivation implemented in a generic, `no_std`-friendly 11 | manner. Supports deriving keys using the pure Rust `k256` crate or the 12 | C library-backed `secp256k1` crate. 13 | 14 | ![Diagram](https://raw.githubusercontent.com/bitcoin/bips/4bc05ff903cb47eb18ce58a9836de1ac13ecf1b7/bip-0032/derivation.png) 15 | 16 | [Documentation][docs-link] 17 | 18 | ## About 19 | 20 | BIP32 is an algorithm for generating a hierarchy of elliptic curve keys, 21 | a.k.a. "wallets", from a single seed value. A related algorithm also 22 | implemented by this crate, BIP39, provides a way to derive the seed value 23 | from a set of 24-words from a preset list, a.k.a. a "mnemonic". 24 | 25 | ## Minimum Supported Rust Version 26 | 27 | Rust **1.81** or newer. 28 | 29 | In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope 30 | for this crate's SemVer guarantees), however when we do it will be accompanied by 31 | a minor version bump. 32 | 33 | ## License 34 | 35 | Copyright © 2020-2024 iqlusion 36 | 37 | **bip32.rs** is distributed under the terms of either the MIT license 38 | or the Apache License (Version 2.0), at your option. 39 | 40 | See [LICENSE-APACHE] (Apache License, Version 2.0) and [LICENSE-MIT] for 41 | further details. 42 | 43 | ## Contribution 44 | 45 | Unless you explicitly state otherwise, any contribution intentionally 46 | submitted for inclusion in the work by you shall be dual licensed as above, 47 | without any additional terms or conditions. 48 | 49 | [//]: # (badges) 50 | 51 | [crate-image]: https://img.shields.io/crates/v/bip32.svg?logo=rust 52 | [crate-link]: https://crates.io/crates/bip32 53 | [docs-image]: https://docs.rs/bip32/badge.svg 54 | [docs-link]: https://docs.rs/bip32/ 55 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 56 | [license-link]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 57 | [rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg 58 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 59 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 60 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/bip32.yml/badge.svg 61 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/bip32.yml 62 | 63 | [//]: # (links) 64 | 65 | [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 66 | [libsecp256k1 C library]: https://github.com/bitcoin-core/secp256k1 67 | [`secp256k1` Rust crate]: https://github.com/rust-bitcoin/rust-secp256k1/ 68 | [LICENSE-APACHE]: https://github.com/iqlusioninc/crates/blob/main/bip32/LICENSE-APACHE 69 | [LICENSE-MIT]: https://github.com/iqlusioninc/crates/blob/main/bip32/LICENSE-MIT 70 | -------------------------------------------------------------------------------- /subtle-encoding/README.md: -------------------------------------------------------------------------------- 1 | # subtle-encoding iqlusion 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | ![Apache 2.0/MIT Licensed][license-image] 6 | ![MSRV][rustc-image] 7 | [![Safety Dance][safety-image]][safety-link] 8 | [![Build Status][build-image]][build-link] 9 | 10 | Rust crate for encoding/decoding binary data to/from **base64** and **hex** 11 | encodings while avoiding data-dependent branching/table lookups, and therefore 12 | providing "best effort" constant-time operation. 13 | 14 | Also includes a non-constant-time Bech32 encoder/decoder gated under the 15 | `bech32-preview` Cargo feature (with a goal of eventually making it 16 | constant-time). 17 | 18 | Useful for encoding/decoding secret values such as cryptographic keys. 19 | 20 | [Documentation] 21 | 22 | ## Minimum Supported Rust Version 23 | 24 | Rust **1.56** or newer. 25 | 26 | In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope 27 | for this crate's SemVer guarantees), however when we do it will be accompanied by 28 | a minor version bump. 29 | 30 | ## Security Notice 31 | 32 | While this crate takes care to avoid data-dependent branching, that does not 33 | actually make it "constant time", which is an architecture-dependent property. 34 | 35 | This crate is a "best effort" attempt at providing a constant time encoding 36 | library, however it presently provides no guarantees, nor has it been 37 | independently audited for security vulnerabilities. 38 | 39 | Use at your own risk. 40 | 41 | ## License 42 | 43 | Copyright © 2018-2021 iqlusion 44 | 45 | **subtle-encoding** is distributed under the terms of either the MIT license 46 | or the Apache License (Version 2.0), at your option. 47 | 48 | See [LICENSE] (Apache License, Version 2.0) file in the `iqlusioninc/crates` 49 | toplevel directory of this repository or [LICENSE-MIT] for details. 50 | 51 | [//]: # (badges) 52 | 53 | [crate-image]: https://img.shields.io/crates/v/subtle-encoding.svg 54 | [crate-link]: https://crates.io/crates/subtle-encoding 55 | [docs-image]: https://docs.rs/subtle-encoding/badge.svg 56 | [docs-link]: https://docs.rs/subtle-encoding/ 57 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 58 | [rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg 59 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 60 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 61 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/subtle-encoding.yml/badge.svg 62 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/subtle-encoding.yml 63 | 64 | [//]: # (general links) 65 | 66 | [Documentation]: https://docs.rs/subtle-encoding/ 67 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 68 | [LICENSE-MIT]: https://github.com/iqlusioninc/crates/blob/main/subtle-encoding/LICENSE-MIT 69 | -------------------------------------------------------------------------------- /hkd32/tests/test_vectors.rs: -------------------------------------------------------------------------------- 1 | //! Temporary test vectors (known to match a previous implementation) 2 | // TODO(tarcieri): switch to BIP32 test vectors 3 | 4 | use hkd32::*; 5 | 6 | fn parse_path(path: &str) -> PathBuf { 7 | path.parse() 8 | .unwrap_or_else(|_| panic!("couldn't parse path: {:?}", path)) 9 | } 10 | 11 | fn test_key() -> KeyMaterial { 12 | KeyMaterial::new([ 13 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 14 | 25, 26, 27, 28, 29, 30, 31, 15 | ]) 16 | } 17 | 18 | #[cfg(feature = "mnemonic")] 19 | fn test_mnemonic() -> mnemonic::Phrase { 20 | // This phrase is the BIP39 equipvalent of `test_key()` above 21 | let bip39_phrase: &str = 22 | "abandon amount liar amount expire adjust cage candy arch gather drum bullet \ 23 | absurd math era live bid rhythm alien crouch range attend journey unaware"; 24 | 25 | mnemonic::Phrase::new(bip39_phrase, Default::default()).unwrap() 26 | } 27 | 28 | /// Root path outputs the original IKM 29 | #[test] 30 | fn test_vector_0_empty_path() { 31 | let output_key = test_key().derive_subkey(parse_path("/")); 32 | 33 | assert_eq!( 34 | output_key.as_bytes(), 35 | [ 36 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 37 | 24, 25, 26, 27, 28, 29, 30, 31 38 | ] 39 | ); 40 | } 41 | 42 | #[test] 43 | fn test_vector_1() { 44 | let output_key = test_key().derive_subkey(parse_path("/1")); 45 | 46 | assert_eq!( 47 | output_key.as_bytes(), 48 | [ 49 | 132, 75, 58, 18, 91, 107, 10, 110, 128, 162, 98, 177, 192, 212, 50, 101, 136, 46, 46, 50 | 83, 179, 150, 64, 68, 250, 57, 101, 1, 227, 159, 148, 20 51 | ] 52 | ); 53 | } 54 | 55 | #[test] 56 | fn test_vector_2() { 57 | let output_key = test_key().derive_subkey(parse_path("/1/2")); 58 | 59 | assert_eq!( 60 | output_key.as_bytes(), 61 | [ 62 | 110, 41, 196, 37, 188, 239, 92, 14, 14, 8, 176, 199, 3, 232, 46, 214, 237, 183, 11, 63 | 238, 110, 19, 100, 64, 191, 71, 221, 96, 0, 165, 202, 6 64 | ] 65 | ); 66 | } 67 | 68 | #[test] 69 | fn test_vector_3() { 70 | let output_key = test_key().derive_subkey(parse_path("/1/2/3")); 71 | 72 | assert_eq!( 73 | output_key.as_bytes(), 74 | [ 75 | 17, 67, 145, 251, 66, 229, 67, 213, 30, 37, 15, 106, 223, 215, 34, 87, 221, 46, 192, 76 | 225, 50, 153, 127, 65, 168, 152, 14, 237, 100, 231, 142, 3 77 | ] 78 | ); 79 | } 80 | 81 | #[cfg(feature = "mnemonic")] 82 | #[test] 83 | fn test_mnemonic_derivation() { 84 | let mnemonic = test_mnemonic(); 85 | assert_eq!(test_key().as_bytes(), mnemonic.entropy()); 86 | 87 | let output_key = mnemonic.derive_subkey(parse_path("/1/2/3")); 88 | 89 | assert_eq!( 90 | output_key.as_bytes(), 91 | [ 92 | 17, 67, 145, 251, 66, 229, 67, 213, 30, 37, 15, 106, 223, 215, 34, 87, 221, 46, 192, 93 | 225, 50, 153, 127, 65, 168, 152, 14, 237, 100, 231, 142, 3 94 | ] 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /subtle-encoding/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## [0.5.1] (2020-02-08) 2 | 3 | - Remove Bech32 max length restriction ([#357]) 4 | 5 | ## [0.5.0] (2019-10-13) 6 | 7 | - Upgrade to `zeroize` v1.0.0 ([#279]) 8 | 9 | ## [0.4.1] (2019-10-07) 10 | 11 | - Upgrade to `zeroize` v1.0.0-pre ([#268]) 12 | 13 | ## [0.4.0] (2019-08-19) 14 | 15 | - Remove `failure` ([#245]) 16 | - Use `alloc` for heap allocations; MSRV 1.36+ ([#245]) 17 | 18 | ## [0.3.7] (2019-05-19) 19 | 20 | - `zeroize` v0.9.0 ([#215]) 21 | 22 | ## [0.3.6] (2019-05-19) 23 | 24 | - `zeroize` v0.8.0 ([#189]) 25 | 26 | ## [0.3.5] (2019-05-19) 27 | 28 | - `zeroize` v0.7.0 ([#186]) 29 | 30 | ## [0.3.4] (2019-03-23) 31 | 32 | - `zeroize` v0.6.0 ([#170]) 33 | - Make internals of Bech32 type private ([#165]) 34 | 35 | ## [0.3.3] (2019-03-12) 36 | 37 | - Return errors for undersize decode buffers and trailing whitespace ([#163]) 38 | 39 | ## [0.3.2] (2018-12-25) 40 | 41 | - Fix `#![no_std]` support ([#158]) 42 | 43 | ## [0.3.1] (2018-12-25) 44 | 45 | - Update to zeroize 0.5 ([#149]) 46 | 47 | ## [0.3.0] (2018-11-25) 48 | 49 | - Fix critical encode/decode bug in `release` builds ([#126]) 50 | - Non-constant-time Bech32 implementation via `bech32-preview` feature ([#113]) 51 | 52 | ## 0.2.3 (2018-10-12) 53 | 54 | - Bump zeroize dependency to 0.4 55 | 56 | ## 0.2.2 (2018-10-11) 57 | 58 | - Bump zeroize dependency to 0.3 59 | 60 | ## 0.2.1 (2018-10-09) 61 | 62 | - Re-export `IDENTITY` from the crate root 63 | 64 | ## 0.2.0 (2018-10-08) 65 | 66 | - hex: support for encoding/decoding upper case 67 | 68 | ## 0.1.1 (2018-10-05) 69 | 70 | - Fix build when using `--no-default-features` 71 | 72 | ## 0.1.0 (2018-10-03) 73 | 74 | - Initial release 75 | 76 | [0.5.1]: https://github.com/iqlusioninc/crates/pull/358 77 | [#357]: https://github.com/iqlusioninc/crates/pull/357 78 | [0.5.0]: https://github.com/iqlusioninc/crates/pull/283 79 | [#279]: https://github.com/iqlusioninc/crates/pull/279 80 | [0.4.1]: https://github.com/iqlusioninc/crates/pull/269 81 | [#268]: https://github.com/iqlusioninc/crates/pull/268 82 | [0.4.0]: https://github.com/iqlusioninc/crates/pull/249 83 | [#215]: https://github.com/iqlusioninc/crates/pull/245 84 | [0.3.7]: https://github.com/iqlusioninc/crates/pull/218 85 | [#215]: https://github.com/iqlusioninc/crates/pull/215 86 | [0.3.5]: https://github.com/iqlusioninc/crates/pull/187 87 | [#186]: https://github.com/iqlusioninc/crates/pull/186 88 | [0.3.4]: https://github.com/iqlusioninc/crates/pull/171 89 | [#170]: https://github.com/iqlusioninc/crates/pull/170 90 | [#165]: https://github.com/iqlusioninc/crates/pull/165 91 | [0.3.3]: https://github.com/iqlusioninc/crates/pull/164 92 | [#163]: https://github.com/iqlusioninc/crates/pull/163 93 | [0.3.2]: https://github.com/iqlusioninc/crates/pull/160 94 | [#158]: https://github.com/iqlusioninc/crates/pull/158 95 | [0.3.1]: https://github.com/iqlusioninc/crates/pull/155 96 | [#149]: https://github.com/iqlusioninc/crates/pull/149 97 | [0.3.0]: https://github.com/iqlusioninc/crates/pull/129 98 | [#126]: https://github.com/iqlusioninc/crates/pull/126 99 | [#113]: https://github.com/iqlusioninc/crates/pull/113 100 | -------------------------------------------------------------------------------- /hkd32/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.7.0 (2022-05-10) 8 | ### Added 9 | - Impl for `std::error::Error` ([#932]) 10 | 11 | ### Changed 12 | - Rust 2021 edition upgrade ([#889]) 13 | - Bump `pbkdf2` to 0.11.0 ([#983]) 14 | - Bump `hmac` to v0.12 ([#994]) 15 | - Bump `sha2` to v0.10 ([#994]) 16 | 17 | [#889]: https://github.com/iqlusioninc/crates/pull/889 18 | [#932]: https://github.com/iqlusioninc/crates/pull/932 19 | [#983]: https://github.com/iqlusioninc/crates/pull/983 20 | [#994]: https://github.com/iqlusioninc/crates/pull/994 21 | 22 | ## 0.6.0 (2021-06-17) 23 | ### Added 24 | - `Seed::new` method ([#729]) 25 | 26 | ### Changed 27 | - `hmac` to v0.11 ([#704]) 28 | - `pbkdf2` to v0.8 ([#704]) 29 | - Rename `SEED_SIZE` to `Seed::SIZE` ([#728]) 30 | - MSRV 1.56 ([#755]) 31 | - Bump `rand_core` to v0.6 ([#759]) 32 | 33 | ### Removed 34 | - `Seed::derive_subkey` ([#748]) 35 | - `BIP39_BASE_DERIVATION_KEY` constant ([#748]) 36 | 37 | [#704]: https://github.com/iqlusioninc/crates/pull/704 38 | [#728]: https://github.com/iqlusioninc/crates/pull/728 39 | [#729]: https://github.com/iqlusioninc/crates/pull/729 40 | [#748]: https://github.com/iqlusioninc/crates/pull/748 41 | [#755]: https://github.com/iqlusioninc/crates/pull/755 42 | [#759]: https://github.com/iqlusioninc/crates/pull/759 43 | 44 | ## 0.5.0 (2020-10-19) 45 | - Replace `getrandom` with `rand_core` ([#540]) 46 | - Replace `lazy_static` with `once_cell` ([#539]) 47 | - Bump `hmac` to v0.10; pbkdf2 to v0.6 ([#538]) 48 | - MSRV 1.44+ ([#515]) 49 | 50 | [#540]: https://github.com/iqlusioninc/crates/pull/540 51 | [#539]: https://github.com/iqlusioninc/crates/pull/539 52 | [#538]: https://github.com/iqlusioninc/crates/pull/538 53 | [#515]: https://github.com/iqlusioninc/crates/pull/515 54 | 55 | ## 0.4.0 (2020-06-17) 56 | - Update `hmac` to v0.8 ([#435]) 57 | - Update `pbkdf2` to v0.4 ([#435]) 58 | - Update `sha2` to v0.9 ([#435]) 59 | 60 | [#435]: https://github.com/iqlusioninc/crates/pull/435 61 | 62 | ## 0.3.1 (2019-10-13) 63 | - Upgrade to `subtle-encoding` v0.5.0 ([#283]) 64 | 65 | [#283]: https://github.com/iqlusioninc/crates/pull/283 66 | 67 | ## 0.3.0 (2019-10-13) 68 | - Split out `bip39` cargo feature ([#280]) 69 | - Upgrade to `zeroize` v1.0.0 ([#279]) 70 | 71 | [#280]: https://github.com/iqlusioninc/crates/pull/280 72 | [#279]: https://github.com/iqlusioninc/crates/pull/279 73 | 74 | ## 0.2.0 (2019-08-20) 75 | - Vendor (simplified) BIP39 implementation from `tiny-bip39` ([#251]) 76 | - `subtle-encoding` v0.4.0 ([#249]) 77 | - `zeroize` v0.10.0 ([#248]) 78 | 79 | [#251]: https://github.com/iqlusioninc/crates/pull/251 80 | [#249]: https://github.com/iqlusioninc/crates/pull/249 81 | [#248]: https://github.com/iqlusioninc/crates/pull/248 82 | 83 | ## 0.1.2 (2019-07-23) 84 | - Fix docs typo ([#235]) 85 | 86 | [#235]: https://github.com/iqlusioninc/crates/pull/235 87 | 88 | ## 0.1.1 (2019-07-21) 89 | - Fix docs typo ([#232]) 90 | 91 | [#232]: https://github.com/iqlusioninc/crates/pull/232 92 | 93 | ## 0.1.0 (2019-07-21) 94 | - Initial release 95 | -------------------------------------------------------------------------------- /signatory/src/ed25519/verify.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 keys. 2 | 3 | use super::{Signature, ALGORITHM_ID, ALGORITHM_OID}; 4 | use crate::{Error, Result}; 5 | use core::cmp::Ordering; 6 | use pkcs8::{der::asn1, EncodePublicKey}; 7 | use signature::Verifier; 8 | 9 | /// Ed25519 verifying key. 10 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 11 | pub struct VerifyingKey { 12 | inner: ed25519_dalek::VerifyingKey, 13 | } 14 | 15 | impl VerifyingKey { 16 | /// Size of a serialized Ed25519 verifying key in bytes. 17 | pub const BYTE_SIZE: usize = 32; 18 | 19 | /// Parse an Ed25519 public key from raw bytes 20 | /// (i.e. compressed Edwards-y coordinate) 21 | pub fn from_bytes(bytes: &[u8]) -> Result { 22 | ed25519_dalek::VerifyingKey::try_from(bytes) 23 | .map(|inner| VerifyingKey { inner }) 24 | .map_err(|_| Error::Parse) 25 | } 26 | 27 | /// Serialize this key as a byte array. 28 | pub fn to_bytes(self) -> [u8; Self::BYTE_SIZE] { 29 | self.inner.to_bytes() 30 | } 31 | } 32 | 33 | impl AsRef<[u8; Self::BYTE_SIZE]> for VerifyingKey { 34 | fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] { 35 | self.inner.as_bytes() 36 | } 37 | } 38 | 39 | impl From<&ed25519_dalek::SigningKey> for VerifyingKey { 40 | fn from(signing_key: &ed25519_dalek::SigningKey) -> VerifyingKey { 41 | Self { 42 | inner: signing_key.verifying_key(), 43 | } 44 | } 45 | } 46 | 47 | impl EncodePublicKey for VerifyingKey { 48 | fn to_public_key_der(&self) -> pkcs8::spki::Result { 49 | pkcs8::SubjectPublicKeyInfoRef { 50 | algorithm: ALGORITHM_ID, 51 | subject_public_key: asn1::BitStringRef::new(0, self.inner.as_bytes())?, 52 | } 53 | .try_into() 54 | } 55 | } 56 | 57 | impl TryFrom> for VerifyingKey { 58 | type Error = pkcs8::spki::Error; 59 | 60 | fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { 61 | spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?; 62 | 63 | if spki.algorithm.parameters.is_some() { 64 | return Err(pkcs8::spki::Error::OidUnknown { 65 | oid: spki.algorithm.parameters_oid()?, 66 | }); 67 | } 68 | 69 | spki.subject_public_key 70 | .as_bytes() 71 | .and_then(|bytes| Self::from_bytes(bytes).ok()) 72 | .ok_or(pkcs8::spki::Error::KeyMalformed) 73 | } 74 | } 75 | 76 | impl TryFrom<&[u8]> for VerifyingKey { 77 | type Error = Error; 78 | 79 | fn try_from(bytes: &[u8]) -> Result { 80 | Self::from_bytes(bytes) 81 | } 82 | } 83 | 84 | impl Verifier for VerifyingKey { 85 | fn verify(&self, msg: &[u8], sig: &Signature) -> signature::Result<()> { 86 | self.inner.verify(msg, sig) 87 | } 88 | } 89 | 90 | impl Ord for VerifyingKey { 91 | fn cmp(&self, other: &Self) -> Ordering { 92 | self.inner.as_bytes().cmp(other.inner.as_bytes()) 93 | } 94 | } 95 | 96 | impl PartialOrd for VerifyingKey { 97 | fn partial_cmp(&self, other: &Self) -> Option { 98 | Some(self.cmp(other)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /secrecy/README.md: -------------------------------------------------------------------------------- 1 | # secrecy.rs 🤐 iqlusion 2 | 3 | [![Crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | ![Apache 2.0/MIT Licensed][license-image] 6 | ![MSRV][rustc-image] 7 | [![Safety Dance][safety-image]][safety-link] 8 | [![Build Status][build-image]][build-link] 9 | 10 | A simple secret-keeping library for Rust. 11 | 12 | [Documentation][docs-link] 13 | 14 | ## About 15 | 16 | **secrecy** is a *simple*, safe (i.e. `forbid(unsafe_code)` library which 17 | provides wrapper types and traits for secret management in Rust, namely the 18 | `SecretBox` type for wrapping another value in a "secret cell" which attempts 19 | to limit exposure (only available through a special `ExposeSecret` trait). 20 | 21 | This helps to ensure secrets aren't accidentally copied, logged, or otherwise 22 | exposed (as much as possible), and also ensures secrets are securely wiped 23 | from memory when dropped. 24 | 25 | ## Minimum Supported Rust Version 26 | 27 | Rust **1.60** or newer. 28 | 29 | In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope 30 | for this crate's SemVer guarantees), however when we do it will be accompanied by 31 | a minor version bump. 32 | 33 | ## serde support 34 | 35 | Optional `serde` support for parsing owned secret values is available, gated 36 | under the `serde` cargo feature. 37 | 38 | It uses the `Deserialize` and `DeserializeOwned` traits to implement 39 | deserializing secret types which also impl these traits. 40 | 41 | This doesn't guarantee `serde` (or code providing input to `serde`) won't 42 | accidentally make additional copies of the secret, but does the best it can 43 | with what it is given and tries to minimize risk of exposure as much as 44 | possible. 45 | 46 | ## License 47 | 48 | Copyright © 2019-2024 iqlusion 49 | 50 | **secrecy** is distributed under the terms of either the MIT license 51 | or the Apache License (Version 2.0), at your option. 52 | 53 | See [LICENSE] (Apache License, Version 2.0) file in the `iqlusioninc/crates` 54 | toplevel directory of this repository or [LICENSE-MIT] for details. 55 | 56 | ## Contribution 57 | 58 | Unless you explicitly state otherwise, any contribution intentionally 59 | submitted for inclusion in the work by you shall be dual licensed as above, 60 | without any additional terms or conditions. 61 | 62 | [//]: # (badges) 63 | 64 | [crate-image]: https://img.shields.io/crates/v/secrecy.svg?logo=rust 65 | [crate-link]: https://crates.io/crates/secrecy 66 | [docs-image]: https://docs.rs/secrecy/badge.svg 67 | [docs-link]: https://docs.rs/secrecy/ 68 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 69 | [rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg 70 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 71 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 72 | [build-image]: https://github.com/iqlusioninc/crates/actions/workflows/secrecy.yml/badge.svg 73 | [build-link]: https://github.com/iqlusioninc/crates/actions/workflows/secrecy.yml 74 | 75 | [//]: # (general links) 76 | 77 | [LICENSE]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 78 | [LICENSE-MIT]: https://github.com/iqlusioninc/crates/blob/main/secrecy/LICENSE-MIT 79 | -------------------------------------------------------------------------------- /subtle-encoding/src/bech32/checksum.rs: -------------------------------------------------------------------------------- 1 | use zeroize::Zeroize; 2 | 3 | use self::polymod::Polymod; 4 | use super::Error; 5 | 6 | /// Size of checksum in bytes 7 | pub const CHECKSUM_SIZE: usize = 6; 8 | 9 | /// Checksum value used to verify data integrity 10 | pub(crate) struct Checksum([u8; CHECKSUM_SIZE]); 11 | 12 | impl Checksum { 13 | /// Create a checksum for the given human-readable part (hrp) and binary data 14 | pub fn new(hrp: &[u8], data: &[u8]) -> Self { 15 | let mut p = Polymod::compute(hrp, data); 16 | let mut checksum = [0u8; CHECKSUM_SIZE]; 17 | p.input_slice(&checksum); 18 | 19 | let value = p.finish() ^ 1; 20 | for (i, byte) in checksum.iter_mut().enumerate().take(CHECKSUM_SIZE) { 21 | *byte = ((value >> (5 * (5 - i))) & 0x1f) as u8; 22 | } 23 | 24 | Checksum(checksum) 25 | } 26 | 27 | /// Verify this checksum matches the given human-readable part (hrp) and binary data 28 | pub fn verify(hrp: &[u8], data: &[u8]) -> Result<(), Error> { 29 | if Polymod::compute(hrp, data).finish() == 1 { 30 | Ok(()) 31 | } else { 32 | Err(Error::ChecksumInvalid) 33 | } 34 | } 35 | } 36 | 37 | impl AsRef<[u8]> for Checksum { 38 | fn as_ref(&self) -> &[u8] { 39 | self.0.as_ref() 40 | } 41 | } 42 | 43 | impl Drop for Checksum { 44 | fn drop(&mut self) { 45 | self.0.as_mut().zeroize() 46 | } 47 | } 48 | 49 | mod polymod { 50 | /// bech32 generator coefficients 51 | const COEFFICIENTS: [u32; 5] = [ 52 | 0x3b6a_57b2, 53 | 0x2650_8e6d, 54 | 0x1ea1_19fa, 55 | 0x3d42_33dd, 56 | 0x2a14_62b3, 57 | ]; 58 | 59 | /// Perform polynomial calculation against the given generator coefficients 60 | pub(crate) struct Polymod(u32); 61 | 62 | impl Default for Polymod { 63 | fn default() -> Polymod { 64 | Polymod(1) 65 | } 66 | } 67 | 68 | impl Polymod { 69 | pub fn compute(hrp: &[u8], data: &[u8]) -> Self { 70 | let mut p = Polymod::default(); 71 | 72 | for b in hrp { 73 | p.input_byte(*b >> 5); 74 | } 75 | 76 | p.input_byte(0); 77 | 78 | for b in hrp { 79 | p.input_byte(*b & 0x1f); 80 | } 81 | 82 | p.input_slice(data); 83 | p 84 | } 85 | 86 | pub fn input_slice(&mut self, slice: &[u8]) { 87 | for b in slice { 88 | self.input_byte(*b) 89 | } 90 | } 91 | 92 | pub fn input_byte(&mut self, byte: u8) { 93 | let b = (self.0 >> 25) as u8; 94 | self.0 = (self.0 & 0x1ff_ffff) << 5 ^ u32::from(byte); 95 | 96 | for (i, c) in COEFFICIENTS.iter().enumerate() { 97 | if (b >> i) & 1 == 1 { 98 | self.0 ^= *c 99 | } 100 | } 101 | } 102 | 103 | pub fn finish(self) -> u32 { 104 | self.0 105 | } 106 | } 107 | 108 | impl Drop for Polymod { 109 | fn drop(&mut self) { 110 | // TODO: secure zeroize (integers not yet supported by `zeroize` crate) 111 | self.0 = 0; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /signatory/src/ed25519/sign.rs: -------------------------------------------------------------------------------- 1 | //! Ed25519 keys. 2 | 3 | use super::{Signature, VerifyingKey, ALGORITHM_ID, ALGORITHM_OID}; 4 | use crate::{key::store::GeneratePkcs8, Error, Result}; 5 | use alloc::boxed::Box; 6 | use core::fmt; 7 | use ed25519_dalek::SECRET_KEY_LENGTH; 8 | use rand_core::{OsRng, RngCore}; 9 | use signature::Signer; 10 | use zeroize::Zeroizing; 11 | 12 | /// Ed25519 signing key. 13 | pub struct SigningKey { 14 | inner: Box, 15 | } 16 | 17 | impl SigningKey { 18 | /// Initialize from a provided signer object. 19 | /// 20 | /// Use [`SigningKey::from_bytes`] to initialize from a raw private key. 21 | pub fn new(signer: Box) -> Self { 22 | Self { inner: signer } 23 | } 24 | 25 | /// Initialize from a raw scalar value (big endian). 26 | pub fn from_bytes(bytes: &[u8]) -> Result { 27 | let signing_key = ed25519_dalek::SigningKey::try_from(bytes).map_err(|_| Error::Parse)?; 28 | Ok(Self::new(Box::new(signing_key))) 29 | } 30 | 31 | /// Get the verifying key that corresponds to this signing key. 32 | pub fn verifying_key(&self) -> VerifyingKey { 33 | self.inner.verifying_key() 34 | } 35 | } 36 | 37 | // TODO(tarcieri): use upstream decoder from `ed25519` crate. 38 | // See: https://docs.rs/ed25519/latest/ed25519/pkcs8/struct.KeypairBytes.html 39 | impl TryFrom> for SigningKey { 40 | type Error = pkcs8::Error; 41 | 42 | fn try_from(private_key: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { 43 | private_key.algorithm.assert_algorithm_oid(ALGORITHM_OID)?; 44 | 45 | if private_key.algorithm.parameters.is_some() { 46 | return Err(pkcs8::Error::ParametersMalformed); 47 | } 48 | 49 | Self::from_bytes(private_key.private_key).map_err(|_| pkcs8::Error::KeyMalformed) 50 | } 51 | } 52 | 53 | #[cfg(feature = "std")] 54 | impl GeneratePkcs8 for SigningKey { 55 | /// Randomly generate a new PKCS#8 private key. 56 | fn generate_pkcs8() -> pkcs8::SecretDocument { 57 | let mut private_key = Zeroizing::new([0u8; SECRET_KEY_LENGTH]); 58 | OsRng.fill_bytes(&mut *private_key); 59 | pkcs8::SecretDocument::encode_msg(&pkcs8::PrivateKeyInfo::new(ALGORITHM_ID, &*private_key)) 60 | .expect("DER encoding error") 61 | } 62 | } 63 | 64 | impl Signer for SigningKey { 65 | fn try_sign(&self, msg: &[u8]) -> signature::Result { 66 | self.inner.try_sign(msg) 67 | } 68 | } 69 | 70 | impl TryFrom<&[u8]> for SigningKey { 71 | type Error = Error; 72 | 73 | fn try_from(bytes: &[u8]) -> Result { 74 | Self::from_bytes(bytes) 75 | } 76 | } 77 | 78 | impl fmt::Debug for SigningKey { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | f.debug_struct("SigningKey") 81 | .field("verifying_key", &self.verifying_key()) 82 | .finish() 83 | } 84 | } 85 | 86 | /// Ed25519 signer 87 | pub trait Ed25519Signer: Signer { 88 | /// Get the ECDSA verifying key for this signer 89 | fn verifying_key(&self) -> VerifyingKey; 90 | } 91 | 92 | impl Ed25519Signer for T 93 | where 94 | T: Signer, 95 | VerifyingKey: for<'a> From<&'a T>, 96 | { 97 | fn verifying_key(&self) -> VerifyingKey { 98 | self.into() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io). 59 | All complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /bip32/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.5.3 (2025-01-28) 8 | ### Fixed 9 | - `no_std` support with `secp256k1` crate ([#1254]) 10 | 11 | [#1254]: https://github.com/iqlusioninc/crates/pull/1254 12 | 13 | ## 0.5.2 (2023-07-17) 14 | ### Added 15 | - `PrivateKey::derive_tweak()` and `PublicKey::derive_tweak()` ([#1186]) 16 | 17 | [#1186]: https://github.com/iqlusioninc/crates/pull/1186 18 | 19 | ## 0.5.1 (2023-05-29) 20 | ### Added 21 | - `ExtendedPublicKey::new` ([#1136]) 22 | 23 | ### Changed 24 | - Bump `bs58` to v0.5 ([#1139]) 25 | 26 | [#1136]: https://github.com/iqlusioninc/crates/pull/1136 27 | [#1139]: https://github.com/iqlusioninc/crates/pull/1139 28 | 29 | ## 0.5.0 (2023-03-28) 30 | ### Added 31 | - Support for private `ExtendedKey` conversion to `ExtendedPublicKey` ([#1021]) 32 | 33 | ### Changed 34 | - Upgrade elliptic curve crates; MSRV 1.65 ([#1105]) 35 | - `ecdsa` v0.16 36 | - `ed25519-dalek` v2.0.0-pre.0 37 | - `k256` v0.13 38 | - `p256` v0.13 39 | - `p384` v0.13 40 | - Bump `secp256k1` crate dependency to v0.27 ([#1115]) 41 | 42 | [#1021]: https://github.com/iqlusioninc/crates/pull/1021 43 | [#1105]: https://github.com/iqlusioninc/crates/pull/1105 44 | [#1115]: https://github.com/iqlusioninc/crates/pull/1115 45 | 46 | ## 0.4.0 (2022-05-10) 47 | ### Changed 48 | - Bump `pbkdf2` to 0.11.0 ([#983]) 49 | - Bump `hmac` to v0.12 ([#994]) 50 | - Bump `k256` to v0.11 ([#994]) 51 | - Bump `p256` to v0.11 ([#994]) 52 | - Bump `sha2` to v0.10 ([#994]) 53 | - Replace `ripemd160` dependency with `ripemd` ([#994]) 54 | - MSRV 1.57 ([#994], [#995]) 55 | - Use const panic for `Prefix::from_parts_unchecked` ([#995]) 56 | 57 | [#983]: https://github.com/iqlusioninc/crates/pull/983 58 | [#994]: https://github.com/iqlusioninc/crates/pull/994 59 | [#995]: https://github.com/iqlusioninc/crates/pull/995 60 | 61 | ## 0.3.0 (2022-01-05) 62 | ### Changed 63 | - Rust 2021 edition upgrade ([#889]) 64 | - Decouple from `hkd32` ([#907]) 65 | - Bump `k256` dependency to v0.10 ([#938]) 66 | - Bump `secp256k1` (FFI) dependency to v0.21 ([#942]) 67 | 68 | [#889]: https://github.com/iqlusioninc/crates/pull/889 69 | [#907]: https://github.com/iqlusioninc/crates/pull/907 70 | [#938]: https://github.com/iqlusioninc/crates/pull/938 71 | [#942]: https://github.com/iqlusioninc/crates/pull/942 72 | 73 | ## 0.2.2 (2021-09-07) 74 | ### Changed 75 | - Avoid `AsRef` ambiguity with `generic-array` ([#859]) 76 | 77 | [#859]: https://github.com/iqlusioninc/crates/pull/859 78 | 79 | ## 0.2.1 (2021-06-23) 80 | ### Added 81 | - `From` conversions to `k256::ecdsa::*Key` ([#777]) 82 | 83 | [#777]: https://github.com/iqlusioninc/crates/pull/777 84 | 85 | ## 0.2.0 (2021-06-23) [YANKED] 86 | ### Added 87 | - Non-hardened derivation support with `XPub::derive_child` ([#772]) 88 | 89 | ### Changed 90 | - Rename `XPrv::derive_child_from_seed` => `XPrv::derive_from_path` ([#773]) 91 | 92 | [#772]: https://github.com/iqlusioninc/crates/pull/772 93 | [#773]: https://github.com/iqlusioninc/crates/pull/773 94 | 95 | ## 0.1.1 (2021-06-18) 96 | ### Added 97 | - Documentation improvements and usage example ([#764]) 98 | 99 | [#764]: https://github.com/iqlusioninc/crates/pull/764 100 | 101 | ## 0.1.0 (2021-06-17) 102 | - Initial release 103 | -------------------------------------------------------------------------------- /signatory/src/ecdsa/nistp256.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA/NIST P-256 support. 2 | 3 | pub use p256::ecdsa::{Signature, VerifyingKey}; 4 | 5 | use crate::{ 6 | key::{ring::LoadPkcs8, store::GeneratePkcs8}, 7 | Error, KeyHandle, Map, Result, 8 | }; 9 | use alloc::boxed::Box; 10 | use core::fmt; 11 | use pkcs8::EncodePrivateKey; 12 | use signature::Signer; 13 | 14 | /// ECDSA/P-256 key ring. 15 | #[derive(Debug, Default)] 16 | pub struct KeyRing { 17 | keys: Map, 18 | } 19 | 20 | impl KeyRing { 21 | /// Create new ECDSA/NIST P-256 keyring. 22 | pub fn new() -> Self { 23 | Self::default() 24 | } 25 | 26 | /// Get the [`SigningKey`] that corresponds to the provided [`VerifyingKey`] 27 | /// (i.e. public key) 28 | pub fn get(&self, verifying_key: &VerifyingKey) -> Option<&SigningKey> { 29 | self.keys.get(verifying_key) 30 | } 31 | 32 | /// Iterate over the keys in the keyring. 33 | pub fn iter(&self) -> impl Iterator { 34 | self.keys.values() 35 | } 36 | } 37 | 38 | impl LoadPkcs8 for KeyRing { 39 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result { 40 | let signing_key = SigningKey::try_from(private_key)?; 41 | let verifying_key = signing_key.verifying_key(); 42 | 43 | if self.keys.contains_key(&verifying_key) { 44 | return Err(Error::DuplicateKey); 45 | } 46 | 47 | self.keys.insert(verifying_key, signing_key); 48 | Ok(KeyHandle::EcdsaNistP256(verifying_key)) 49 | } 50 | } 51 | 52 | /// ECDSA/NIST P-256 signing key. 53 | pub struct SigningKey { 54 | inner: Box, 55 | } 56 | 57 | impl SigningKey { 58 | /// Initialize from a provided signer object. 59 | /// 60 | /// Use [`SigningKey::from_bytes`] to initialize from a raw private key. 61 | pub fn new(signer: Box) -> Self { 62 | Self { inner: signer } 63 | } 64 | 65 | /// Initialize from a raw scalar value (big endian). 66 | pub fn from_bytes(bytes: &[u8]) -> Result { 67 | let signing_key = p256::ecdsa::SigningKey::from_slice(bytes)?; 68 | Ok(Self::new(Box::new(signing_key))) 69 | } 70 | 71 | /// Get the verifying key that corresponds to this signing key. 72 | pub fn verifying_key(&self) -> VerifyingKey { 73 | self.inner.verifying_key() 74 | } 75 | } 76 | 77 | impl TryFrom> for SigningKey { 78 | type Error = pkcs8::Error; 79 | 80 | fn try_from(private_key_info: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { 81 | p256::ecdsa::SigningKey::try_from(private_key_info).map(|key| Self::new(Box::new(key))) 82 | } 83 | } 84 | 85 | impl TryFrom<&[u8]> for SigningKey { 86 | type Error = Error; 87 | 88 | fn try_from(bytes: &[u8]) -> Result { 89 | Self::from_bytes(bytes) 90 | } 91 | } 92 | 93 | #[cfg(feature = "std")] 94 | impl GeneratePkcs8 for SigningKey { 95 | /// Randomly generate a new PKCS#8 private key. 96 | fn generate_pkcs8() -> pkcs8::SecretDocument { 97 | p256::SecretKey::random(&mut rand_core::OsRng) 98 | .to_pkcs8_der() 99 | .expect("DER error") 100 | } 101 | } 102 | 103 | impl Signer for SigningKey { 104 | fn try_sign(&self, msg: &[u8]) -> signature::Result { 105 | self.inner.try_sign(msg) 106 | } 107 | } 108 | 109 | impl fmt::Debug for SigningKey { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | f.debug_struct("SigningKey") 112 | .field("verifying_key", &self.verifying_key()) 113 | .finish() 114 | } 115 | } 116 | 117 | /// ECDSA/NIST P-256 signer. 118 | pub trait NistP256Signer: Signer { 119 | /// Get the ECDSA verifying key for this signer 120 | fn verifying_key(&self) -> VerifyingKey; 121 | } 122 | 123 | impl NistP256Signer for T 124 | where 125 | T: Signer, 126 | VerifyingKey: for<'a> From<&'a T>, 127 | { 128 | fn verifying_key(&self) -> VerifyingKey { 129 | self.into() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /signatory/src/ecdsa/nistp384.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA/NIST P-384 support. 2 | 3 | pub use p384::ecdsa::{Signature, VerifyingKey}; 4 | 5 | use crate::{ 6 | key::{ring::LoadPkcs8, store::GeneratePkcs8}, 7 | Error, KeyHandle, Map, Result, 8 | }; 9 | use alloc::boxed::Box; 10 | use core::fmt; 11 | use pkcs8::EncodePrivateKey; 12 | use signature::Signer; 13 | 14 | /// ECDSA/P-384 key ring. 15 | #[derive(Debug, Default)] 16 | pub struct KeyRing { 17 | keys: Map, 18 | } 19 | 20 | impl KeyRing { 21 | /// Create new ECDSA/NIST P-384 keyring. 22 | pub fn new() -> Self { 23 | Self::default() 24 | } 25 | 26 | /// Get the [`SigningKey`] that corresponds to the provided [`VerifyingKey`] 27 | /// (i.e. public key) 28 | pub fn get(&self, verifying_key: &VerifyingKey) -> Option<&SigningKey> { 29 | self.keys.get(verifying_key) 30 | } 31 | 32 | /// Iterate over the keys in the keyring. 33 | pub fn iter(&self) -> impl Iterator { 34 | self.keys.values() 35 | } 36 | } 37 | 38 | impl LoadPkcs8 for KeyRing { 39 | fn load_pkcs8(&mut self, private_key: pkcs8::PrivateKeyInfo<'_>) -> Result { 40 | let signing_key = SigningKey::try_from(private_key)?; 41 | let verifying_key = signing_key.verifying_key(); 42 | 43 | if self.keys.contains_key(&verifying_key) { 44 | return Err(Error::DuplicateKey); 45 | } 46 | 47 | self.keys.insert(verifying_key, signing_key); 48 | Ok(KeyHandle::EcdsaNistP384(verifying_key)) 49 | } 50 | } 51 | 52 | /// ECDSA/NIST P-384 signing key. 53 | pub struct SigningKey { 54 | inner: Box, 55 | } 56 | 57 | impl SigningKey { 58 | /// Initialize from a provided signer object. 59 | /// 60 | /// Use [`SigningKey::from_bytes`] to initialize from a raw private key. 61 | pub fn new(signer: Box) -> Self { 62 | Self { inner: signer } 63 | } 64 | 65 | /// Initialize from a raw scalar value (big endian). 66 | pub fn from_bytes(bytes: &[u8]) -> Result { 67 | let signing_key = p384::ecdsa::SigningKey::from_slice(bytes)?; 68 | Ok(Self::new(Box::new(signing_key))) 69 | } 70 | 71 | /// Get the verifying key that corresponds to this signing key. 72 | pub fn verifying_key(&self) -> VerifyingKey { 73 | self.inner.verifying_key() 74 | } 75 | } 76 | 77 | impl TryFrom> for SigningKey { 78 | type Error = pkcs8::Error; 79 | 80 | fn try_from(private_key_info: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { 81 | p384::ecdsa::SigningKey::try_from(private_key_info).map(|key| Self::new(Box::new(key))) 82 | } 83 | } 84 | 85 | impl TryFrom<&[u8]> for SigningKey { 86 | type Error = Error; 87 | 88 | fn try_from(bytes: &[u8]) -> Result { 89 | Self::from_bytes(bytes) 90 | } 91 | } 92 | 93 | #[cfg(feature = "std")] 94 | impl GeneratePkcs8 for SigningKey { 95 | /// Randomly generate a new PKCS#8 private key. 96 | fn generate_pkcs8() -> pkcs8::SecretDocument { 97 | p384::SecretKey::random(&mut rand_core::OsRng) 98 | .to_pkcs8_der() 99 | .expect("DER error") 100 | } 101 | } 102 | 103 | impl Signer for SigningKey { 104 | fn try_sign(&self, msg: &[u8]) -> signature::Result { 105 | self.inner.try_sign(msg) 106 | } 107 | } 108 | 109 | impl fmt::Debug for SigningKey { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | f.debug_struct("SigningKey") 112 | .field("verifying_key", &self.verifying_key()) 113 | .finish() 114 | } 115 | } 116 | 117 | /// ECDSA/NIST P-384 signer. 118 | pub trait NistP384Signer: Signer { 119 | /// Get the ECDSA verifying key for this signer 120 | fn verifying_key(&self) -> VerifyingKey; 121 | } 122 | 123 | impl NistP384Signer for T 124 | where 125 | T: Signer, 126 | VerifyingKey: for<'a> From<&'a T>, 127 | { 128 | fn verifying_key(&self) -> VerifyingKey { 129 | self.into() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /signatory/src/ecdsa/secp256k1.rs: -------------------------------------------------------------------------------- 1 | //! ECDSA/secp256k1 support. 2 | 3 | pub use k256::ecdsa::{Signature, VerifyingKey}; 4 | 5 | use crate::{ 6 | key::{ring::LoadPkcs8, store::GeneratePkcs8}, 7 | Error, KeyHandle, Map, Result, 8 | }; 9 | use alloc::boxed::Box; 10 | use core::fmt; 11 | use pkcs8::EncodePrivateKey; 12 | use signature::Signer; 13 | 14 | /// ECDSA/secp256k1 keyring. 15 | #[derive(Debug, Default)] 16 | pub struct KeyRing { 17 | keys: Map, 18 | } 19 | 20 | impl KeyRing { 21 | /// Create new ECDSA/secp256k1 keystore. 22 | pub fn new() -> Self { 23 | Self::default() 24 | } 25 | 26 | /// Get the [`SigningKey`] that corresponds to the provided [`VerifyingKey`] 27 | /// (i.e. public key) 28 | pub fn get(&self, verifying_key: &VerifyingKey) -> Option<&SigningKey> { 29 | self.keys.get(verifying_key) 30 | } 31 | 32 | /// Iterate over the keys in the keyring. 33 | pub fn iter(&self) -> impl Iterator { 34 | self.keys.values() 35 | } 36 | } 37 | 38 | impl LoadPkcs8 for KeyRing { 39 | fn load_pkcs8(&mut self, private_key_info: pkcs8::PrivateKeyInfo<'_>) -> Result { 40 | let signing_key = SigningKey::try_from(private_key_info)?; 41 | let verifying_key = signing_key.verifying_key(); 42 | 43 | if self.keys.contains_key(&verifying_key) { 44 | return Err(Error::DuplicateKey); 45 | } 46 | 47 | self.keys.insert(verifying_key, signing_key); 48 | Ok(KeyHandle::EcdsaSecp256k1(verifying_key)) 49 | } 50 | } 51 | 52 | /// ECDSA/secp256k1 signing key. 53 | pub struct SigningKey { 54 | inner: Box, 55 | } 56 | 57 | impl SigningKey { 58 | /// Initialize from a provided signer object. 59 | /// 60 | /// Use [`SigningKey::from_bytes`] to initialize from a raw private key. 61 | pub fn new(signer: Box) -> Self { 62 | Self { inner: signer } 63 | } 64 | 65 | /// Initialize from a raw scalar value (big endian). 66 | pub fn from_bytes(bytes: &[u8]) -> Result { 67 | let signing_key = k256::ecdsa::SigningKey::from_slice(bytes)?; 68 | Ok(Self::new(Box::new(signing_key))) 69 | } 70 | 71 | /// Get the verifying key that corresponds to this signing key. 72 | pub fn verifying_key(&self) -> VerifyingKey { 73 | self.inner.verifying_key() 74 | } 75 | } 76 | 77 | impl TryFrom> for SigningKey { 78 | type Error = pkcs8::Error; 79 | 80 | fn try_from(private_key: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { 81 | k256::ecdsa::SigningKey::try_from(private_key).map(|key| Self::new(Box::new(key))) 82 | } 83 | } 84 | 85 | impl TryFrom<&[u8]> for SigningKey { 86 | type Error = Error; 87 | 88 | fn try_from(bytes: &[u8]) -> Result { 89 | Self::from_bytes(bytes) 90 | } 91 | } 92 | 93 | #[cfg(feature = "std")] 94 | impl GeneratePkcs8 for SigningKey { 95 | /// Randomly generate a new PKCS#8 private key. 96 | fn generate_pkcs8() -> pkcs8::SecretDocument { 97 | k256::SecretKey::random(&mut rand_core::OsRng) 98 | .to_pkcs8_der() 99 | .expect("DER error") 100 | } 101 | } 102 | 103 | impl Signer for SigningKey { 104 | fn try_sign(&self, msg: &[u8]) -> signature::Result { 105 | self.inner.try_sign(msg) 106 | } 107 | } 108 | 109 | impl fmt::Debug for SigningKey { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | f.debug_struct("SigningKey") 112 | .field("verifying_key", &self.verifying_key()) 113 | .finish() 114 | } 115 | } 116 | 117 | /// ECDSA/secp256k1 signer 118 | pub trait Secp256k1Signer: Signer { 119 | /// Get the ECDSA verifying key for this signer 120 | fn verifying_key(&self) -> VerifyingKey; 121 | } 122 | 123 | impl Secp256k1Signer for T 124 | where 125 | T: Signer, 126 | VerifyingKey: for<'a> From<&'a T>, 127 | { 128 | fn verifying_key(&self) -> VerifyingKey { 129 | self.into() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /bip32/src/child_number.rs: -------------------------------------------------------------------------------- 1 | //! Child numbers 2 | 3 | use crate::{Error, Result}; 4 | use core::{ 5 | fmt::{self, Display}, 6 | str::FromStr, 7 | }; 8 | 9 | /// Index of a particular child key for a given (extended) private key. 10 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] 11 | pub struct ChildNumber(pub u32); 12 | 13 | impl ChildNumber { 14 | /// Size of a child number when encoded as bytes. 15 | pub const BYTE_SIZE: usize = 4; 16 | 17 | /// Hardened child keys use indices 2^31 through 2^32-1. 18 | pub const HARDENED_FLAG: u32 = 1 << 31; 19 | 20 | /// Create new [`ChildNumber`] with the given index and hardened flag. 21 | /// 22 | /// Returns an error if it is equal to or greater than [`Self::HARDENED_FLAG`]. 23 | pub fn new(index: u32, hardened: bool) -> Result { 24 | if index >= Self::HARDENED_FLAG { 25 | Err(Error::ChildNumber) 26 | } else if hardened { 27 | Ok(Self(index | Self::HARDENED_FLAG)) 28 | } else { 29 | Ok(Self(index)) 30 | } 31 | } 32 | 33 | /// Parse a child number from the byte encoding. 34 | pub fn from_bytes(bytes: [u8; Self::BYTE_SIZE]) -> Self { 35 | u32::from_be_bytes(bytes).into() 36 | } 37 | 38 | /// Serialize this child number as bytes. 39 | pub fn to_bytes(self) -> [u8; Self::BYTE_SIZE] { 40 | self.0.to_be_bytes() 41 | } 42 | 43 | /// Get the index number for this [`ChildNumber`], i.e. with 44 | /// [`Self::HARDENED_FLAG`] cleared. 45 | pub fn index(self) -> u32 { 46 | self.0 & !Self::HARDENED_FLAG 47 | } 48 | 49 | /// Is this child number within the hardened range? 50 | pub fn is_hardened(&self) -> bool { 51 | self.0 & Self::HARDENED_FLAG != 0 52 | } 53 | } 54 | 55 | impl Display for ChildNumber { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | write!(f, "{}", self.index())?; 58 | 59 | if self.is_hardened() { 60 | f.write_str("\'")?; 61 | } 62 | 63 | Ok(()) 64 | } 65 | } 66 | 67 | impl From for ChildNumber { 68 | fn from(n: u32) -> ChildNumber { 69 | ChildNumber(n) 70 | } 71 | } 72 | 73 | impl From for u32 { 74 | fn from(n: ChildNumber) -> u32 { 75 | n.0 76 | } 77 | } 78 | 79 | impl FromStr for ChildNumber { 80 | type Err = Error; 81 | 82 | fn from_str(child: &str) -> Result { 83 | let (child, hardened) = match child.strip_suffix('\'') { 84 | Some(c) => (c, true), 85 | None => match child.strip_suffix('h') { 86 | Some(c) => (c, true), 87 | None => (child, false), 88 | }, 89 | }; 90 | 91 | let index = child.parse().map_err(|_| Error::ChildNumber)?; 92 | ChildNumber::new(index, hardened) 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::ChildNumber; 99 | use crate::Error; 100 | 101 | #[test] 102 | fn parse_non_hardened() { 103 | let n = "42".parse::().unwrap(); 104 | assert_eq!(n, ChildNumber::new(42, false).unwrap()); 105 | assert_eq!(n.index(), 42); 106 | assert!(!n.is_hardened()); 107 | } 108 | 109 | #[test] 110 | fn parse_hardened() { 111 | let n = "42'".parse::().unwrap(); 112 | assert_eq!(n, ChildNumber::new(42, true).unwrap()); 113 | assert_eq!(n.index(), 42); 114 | assert!(n.is_hardened()); 115 | } 116 | 117 | #[test] 118 | fn parse_hardened_h() { 119 | let n = "42h".parse::().unwrap(); 120 | assert_eq!(n, ChildNumber::new(42, true).unwrap()); 121 | assert_eq!(n.index(), 42); 122 | assert!(n.is_hardened()); 123 | } 124 | 125 | #[test] 126 | fn parse_rejects_invalid() { 127 | assert_eq!("42!".parse::(), Err(Error::ChildNumber)); 128 | } 129 | 130 | #[test] 131 | fn index_overflow() { 132 | // Invalid index 133 | let index = ChildNumber::HARDENED_FLAG; 134 | assert_eq!(ChildNumber::new(index, false), Err(Error::ChildNumber)); 135 | assert_eq!(ChildNumber::new(index, true), Err(Error::ChildNumber)); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /bip32/src/mnemonic/bits.rs: -------------------------------------------------------------------------------- 1 | //! Bit manipulation for BIP39 derivation 2 | //! 3 | //! Adapted from the `bip39` crate 4 | 5 | use alloc::{string::String, vec::Vec}; 6 | use core::marker::PhantomData; 7 | 8 | pub(crate) trait IterExt: Iterator { 9 | fn join(&mut self, glue: &str) -> R 10 | where 11 | R: From, 12 | Self::Item: AsRef, 13 | { 14 | let first = match self.next() { 15 | Some(first) => first, 16 | None => return String::new().into(), 17 | }; 18 | 19 | let (lower, _) = self.size_hint(); 20 | 21 | let mut buffer = String::with_capacity(lower * (10 + glue.len())); 22 | 23 | buffer.push_str(first.as_ref()); 24 | 25 | for item in self { 26 | buffer.push_str(glue); 27 | buffer.push_str(item.as_ref()); 28 | } 29 | 30 | buffer.into() 31 | } 32 | 33 | fn bits(self) -> BitIter 34 | where 35 | Out: Bits, 36 | Self::Item: Bits, 37 | Self: Sized, 38 | { 39 | BitIter::new(self) 40 | } 41 | } 42 | 43 | impl IterExt for I {} 44 | 45 | pub(crate) trait Bits { 46 | const SIZE: usize; 47 | 48 | fn bits(self) -> u32; 49 | } 50 | 51 | impl Bits for u8 { 52 | const SIZE: usize = 8; 53 | 54 | fn bits(self) -> u32 { 55 | self as u32 56 | } 57 | } 58 | 59 | impl<'a> Bits for &'a u8 { 60 | const SIZE: usize = 8; 61 | 62 | fn bits(self) -> u32 { 63 | *self as u32 64 | } 65 | } 66 | 67 | #[derive(Clone, Copy, Debug)] 68 | pub struct Bits11(u16); 69 | 70 | impl Bits for Bits11 { 71 | const SIZE: usize = 11; 72 | 73 | fn bits(self) -> u32 { 74 | self.0 as u32 75 | } 76 | } 77 | 78 | impl From for Bits11 { 79 | fn from(val: u16) -> Self { 80 | Bits11(val) 81 | } 82 | } 83 | 84 | impl From for u16 { 85 | fn from(val: Bits11) -> Self { 86 | val.0 87 | } 88 | } 89 | 90 | pub(crate) struct BitWriter { 91 | offset: usize, 92 | remainder: u32, 93 | inner: Vec, 94 | } 95 | 96 | impl BitWriter { 97 | pub fn with_capacity(capacity: usize) -> Self { 98 | let mut bytes = capacity / 8; 99 | 100 | if capacity % 8 != 0 { 101 | bytes += 1; 102 | } 103 | 104 | Self { 105 | offset: 0, 106 | remainder: 0, 107 | inner: Vec::with_capacity(bytes), 108 | } 109 | } 110 | 111 | pub fn push(&mut self, source: B) { 112 | let shift = 32 - B::SIZE; 113 | 114 | self.remainder |= (source.bits() << shift) >> self.offset; 115 | self.offset += B::SIZE; 116 | 117 | while self.offset >= 8 { 118 | self.inner.push((self.remainder >> 24) as u8); 119 | self.remainder <<= 8; 120 | self.offset -= 8; 121 | } 122 | } 123 | 124 | pub fn into_bytes(mut self) -> Vec { 125 | if self.offset != 0 { 126 | self.inner.push((self.remainder >> 24) as u8); 127 | } 128 | 129 | self.inner 130 | } 131 | } 132 | 133 | pub(crate) struct BitIter + Sized> { 134 | _phantom: PhantomData, 135 | source: I, 136 | read: usize, 137 | buffer: u64, 138 | } 139 | 140 | impl BitIter 141 | where 142 | In: Bits, 143 | Out: Bits, 144 | I: Iterator, 145 | { 146 | fn new(source: I) -> Self { 147 | BitIter { 148 | _phantom: PhantomData, 149 | source, 150 | read: 0, 151 | buffer: 0, 152 | } 153 | } 154 | } 155 | 156 | impl Iterator for BitIter 157 | where 158 | In: Bits, 159 | Out: Bits + From, 160 | I: Iterator, 161 | { 162 | type Item = Out; 163 | 164 | fn next(&mut self) -> Option { 165 | while self.read < Out::SIZE { 166 | let bits = self.source.next()?.bits() as u64; 167 | 168 | self.read += In::SIZE; 169 | self.buffer |= bits << (64 - self.read); 170 | } 171 | 172 | let result = (self.buffer >> (64 - Out::SIZE)) as u16; 173 | self.buffer <<= Out::SIZE; 174 | self.read -= Out::SIZE; 175 | 176 | Some(result.into()) 177 | } 178 | 179 | fn size_hint(&self) -> (usize, Option) { 180 | let (lower, upper) = self.source.size_hint(); 181 | 182 | ( 183 | (lower * In::SIZE) / Out::SIZE, 184 | upper.map(|n| (n * In::SIZE) / Out::SIZE), 185 | ) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /hkd32/src/mnemonic/bits.rs: -------------------------------------------------------------------------------- 1 | //! Bit manipulation for BIP39 derivation 2 | //! 3 | //! Adapted from the `bip39` crate 4 | 5 | use alloc::{string::String, vec::Vec}; 6 | use core::marker::PhantomData; 7 | 8 | pub(crate) trait IterExt: Iterator { 9 | fn join(&mut self, glue: &str) -> R 10 | where 11 | R: From, 12 | Self::Item: AsRef, 13 | { 14 | let first = match self.next() { 15 | Some(first) => first, 16 | None => return String::new().into(), 17 | }; 18 | 19 | let (lower, _) = self.size_hint(); 20 | 21 | let mut buffer = String::with_capacity(lower * (10 + glue.len())); 22 | 23 | buffer.push_str(first.as_ref()); 24 | 25 | for item in self { 26 | buffer.push_str(glue); 27 | buffer.push_str(item.as_ref()); 28 | } 29 | 30 | buffer.into() 31 | } 32 | 33 | fn bits(self) -> BitIter 34 | where 35 | Out: Bits, 36 | Self::Item: Bits, 37 | Self: Sized, 38 | { 39 | BitIter::new(self) 40 | } 41 | } 42 | 43 | impl IterExt for I {} 44 | 45 | pub(crate) trait Bits { 46 | const SIZE: usize; 47 | 48 | fn bits(self) -> u32; 49 | } 50 | 51 | impl Bits for u8 { 52 | const SIZE: usize = 8; 53 | 54 | fn bits(self) -> u32 { 55 | self as u32 56 | } 57 | } 58 | 59 | impl<'a> Bits for &'a u8 { 60 | const SIZE: usize = 8; 61 | 62 | fn bits(self) -> u32 { 63 | *self as u32 64 | } 65 | } 66 | 67 | #[derive(Clone, Copy, Debug)] 68 | pub struct Bits11(u16); 69 | 70 | impl Bits for Bits11 { 71 | const SIZE: usize = 11; 72 | 73 | fn bits(self) -> u32 { 74 | self.0 as u32 75 | } 76 | } 77 | 78 | impl From for Bits11 { 79 | fn from(val: u16) -> Self { 80 | Bits11(val) 81 | } 82 | } 83 | 84 | impl From for u16 { 85 | fn from(val: Bits11) -> Self { 86 | val.0 87 | } 88 | } 89 | 90 | pub(crate) struct BitWriter { 91 | offset: usize, 92 | remainder: u32, 93 | inner: Vec, 94 | } 95 | 96 | impl BitWriter { 97 | pub fn with_capacity(capacity: usize) -> Self { 98 | let mut bytes = capacity / 8; 99 | 100 | if capacity % 8 != 0 { 101 | bytes += 1; 102 | } 103 | 104 | Self { 105 | offset: 0, 106 | remainder: 0, 107 | inner: Vec::with_capacity(bytes), 108 | } 109 | } 110 | 111 | pub fn push(&mut self, source: B) { 112 | let shift = 32 - B::SIZE; 113 | 114 | self.remainder |= (source.bits() << shift) >> self.offset; 115 | self.offset += B::SIZE; 116 | 117 | while self.offset >= 8 { 118 | self.inner.push((self.remainder >> 24) as u8); 119 | self.remainder <<= 8; 120 | self.offset -= 8; 121 | } 122 | } 123 | 124 | pub fn into_bytes(mut self) -> Vec { 125 | if self.offset != 0 { 126 | self.inner.push((self.remainder >> 24) as u8); 127 | } 128 | 129 | self.inner 130 | } 131 | } 132 | 133 | pub(crate) struct BitIter + Sized> { 134 | _phantom: PhantomData, 135 | source: I, 136 | read: usize, 137 | buffer: u64, 138 | } 139 | 140 | impl BitIter 141 | where 142 | In: Bits, 143 | Out: Bits, 144 | I: Iterator, 145 | { 146 | fn new(source: I) -> Self { 147 | BitIter { 148 | _phantom: PhantomData, 149 | source, 150 | read: 0, 151 | buffer: 0, 152 | } 153 | } 154 | } 155 | 156 | impl Iterator for BitIter 157 | where 158 | In: Bits, 159 | Out: Bits + From, 160 | I: Iterator, 161 | { 162 | type Item = Out; 163 | 164 | fn next(&mut self) -> Option { 165 | while self.read < Out::SIZE { 166 | let bits = self.source.next()?.bits() as u64; 167 | 168 | self.read += In::SIZE; 169 | self.buffer |= bits << (64 - self.read); 170 | } 171 | 172 | let result = (self.buffer >> (64 - Out::SIZE)) as u16; 173 | self.buffer <<= Out::SIZE; 174 | self.read -= Out::SIZE; 175 | 176 | Some(result.into()) 177 | } 178 | 179 | fn size_hint(&self) -> (usize, Option) { 180 | let (lower, upper) = self.source.size_hint(); 181 | 182 | ( 183 | (lower * In::SIZE) / Out::SIZE, 184 | upper.map(|n| (n * In::SIZE) / Out::SIZE), 185 | ) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /hkd32/src/pathbuf.rs: -------------------------------------------------------------------------------- 1 | //! Key derivation paths. This is the owned type for such paths, analogous 2 | //! to the corresponding type in `std`. 3 | //! 4 | //! This type is only available when the `alloc` feature is enabled. 5 | 6 | use crate::{ 7 | path::{Component, Path}, 8 | Error, DELIMITER, 9 | }; 10 | use alloc::{borrow::ToOwned, str::FromStr, vec::Vec}; 11 | use core::fmt::{self, Debug}; 12 | use core::{borrow::Borrow, ops::Deref}; 13 | use zeroize::Zeroize; 14 | 15 | /// Key derivation paths: location within a key hierarchy which 16 | /// names/identifies a specific key. 17 | /// 18 | /// This is the owned path type. The corresponding reference type is 19 | /// `hkd32::Path` (ala the corresponding types in `std`). 20 | #[derive(Clone, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] 21 | #[repr(transparent)] 22 | pub struct PathBuf(Vec); 23 | 24 | impl PathBuf { 25 | /// Parse a path from its bytestring serialization. 26 | pub fn from_bytes(bytes: B) -> Result 27 | where 28 | B: AsRef<[u8]>, 29 | { 30 | // Use `Path::new` to assert that the path is actually valid 31 | Path::new(bytes.as_ref())?; 32 | 33 | // If it parses successfully, we can use it wholesale 34 | Ok(PathBuf(bytes.as_ref().into())) 35 | } 36 | 37 | /// Create a new `PathBuf` representing the root derivation path. 38 | /// 39 | /// This is also the default value for `PathBuf`. 40 | pub fn new() -> Self { 41 | Self::default() 42 | } 43 | 44 | /// Borrow this `PathBuf` as a `Path` 45 | pub fn as_path(&self) -> &Path { 46 | Path::new(&self.0).unwrap() 47 | } 48 | 49 | /// Extend this key derivation path with additional components. 50 | pub fn extend<'a, I, C>(&mut self, components: I) 51 | where 52 | I: IntoIterator, 53 | C: AsRef>, 54 | { 55 | for component in components { 56 | self.push(component); 57 | } 58 | } 59 | 60 | /// Push an additional component onto this path. 61 | pub fn push<'a, C: AsRef>>(&mut self, component: C) { 62 | self.0.append(&mut component.as_ref().to_bytes()); 63 | } 64 | } 65 | 66 | impl AsRef for PathBuf { 67 | fn as_ref(&self) -> &Path { 68 | self.as_path() 69 | } 70 | } 71 | 72 | impl Borrow for PathBuf { 73 | fn borrow(&self) -> &Path { 74 | self.as_path() 75 | } 76 | } 77 | 78 | impl Debug for PathBuf { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | write!(f, "hkd32::PathBuf")?; 81 | self.debug_components(f) 82 | } 83 | } 84 | 85 | impl Deref for PathBuf { 86 | type Target = Path; 87 | 88 | fn deref(&self) -> &Path { 89 | self.as_path() 90 | } 91 | } 92 | 93 | impl Drop for PathBuf { 94 | fn drop(&mut self) { 95 | self.0.zeroize(); 96 | } 97 | } 98 | 99 | impl FromStr for PathBuf { 100 | type Err = Error; 101 | 102 | /// Parse a derivation path from a string. 103 | /// 104 | /// Derivation path strings look like Unix paths (e.g. "/foo/bar/baz"). 105 | /// They are delimited by a slash character (`/` a.k.a. `hkd32::DELIMITER`) 106 | /// and must start with a leading slash. 107 | /// 108 | /// Empty path components are not allowed (e.g. `//`, `/foo//`) 109 | fn from_str(s: &str) -> Result { 110 | let mut result = Self::new(); 111 | 112 | // Special case for the root path 113 | if s.len() == 1 && s.starts_with(DELIMITER) { 114 | return Ok(result); 115 | } 116 | 117 | let mut components = s.split(DELIMITER); 118 | 119 | // Path strings must start with a leading `/` 120 | if components.next() != Some("") { 121 | return Err(Error); 122 | } 123 | 124 | for component in components { 125 | if !component.is_ascii() { 126 | return Err(Error); 127 | } 128 | 129 | result.push(Component::new(component.as_bytes())?); 130 | } 131 | 132 | Ok(result) 133 | } 134 | } 135 | 136 | impl ToOwned for Path { 137 | type Owned = PathBuf; 138 | 139 | fn to_owned(&self) -> PathBuf { 140 | PathBuf::from_bytes(self.as_bytes()).unwrap() 141 | } 142 | } 143 | 144 | // TODO(tarcieri): remove this impl in favor of `ZeroizeOnDrop` in next breaking release 145 | impl Zeroize for PathBuf { 146 | fn zeroize(&mut self) { 147 | self.0.zeroize(); 148 | } 149 | } 150 | 151 | #[cfg(all(test, feature = "alloc"))] 152 | mod tests { 153 | use super::*; 154 | 155 | #[test] 156 | fn test_root() { 157 | let root_path = PathBuf::new(); 158 | assert_eq!(root_path.components().count(), 0); 159 | assert_eq!(root_path.stringify().unwrap(), "/"); 160 | assert_eq!(&format!("{:?}", root_path), "hkd32::PathBuf(\"/\")"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iqlusion crates 📦 iqlusion 2 | 3 | [![Apache 2.0 Licensed][license-image]][license-link] 4 | [![dependency status][deps-image]][deps-link] 5 | 6 | This repository contains a set of Apache 2.0-licensed packages (a.k.a. "crates") 7 | for the [Rust](https://www.rust-lang.org/) programming language, contributed 8 | to the community by [iqlusion](https://www.iqlusion.io). 9 | 10 | If you are interested in contributing to this repository, please make sure to 11 | read the [CONTRIBUTING.md] and [CODE_OF_CONDUCT.md] files first. 12 | 13 | ## Crates 14 | 15 | This repository contains the following crates: 16 | 17 | | Name | Version | Build | Description | 18 | |-------------------|----------------------------|----------------------------|----------------------------------------| 19 | | [bip32] | ![][bip32-crate] | ![][bip32-build] | Hierarchical key derivation | 20 | | [canonical‑path] | ![][canonical-path-crate] | ![][canonical-path-build] | Canonical filesystem path support | 21 | | [hkd32] | ![][hkd32-crate] | ![][hkd32-build] | HMAC-based Hierarchical Key Derivation | 22 | | [iqhttp] | ![][iqhttp-crate] | ![][iqhttp-build] | HTTP client built on hyper | 23 | | [secrecy] | ![][secrecy-crate] | ![][secrecy-build] | Simple secret-keeping library | 24 | | [signatory] | ![][signatory-crate] | ![][signatory-build] | Signature library with ECDSA+Ed25519 | 25 | | [subtle‑encoding] | ![][subtle-encoding-crate] | ![][subtle-encoding-build] | Constant-time hex/bech32/base64 | 26 | 27 | ## License 28 | 29 | Copyright © 2018-2024 iqlusion 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | https://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. 42 | 43 | ## Contribution 44 | 45 | Unless you explicitly state otherwise, any contribution intentionally 46 | submitted for inclusion in the work by you shall be licensed as above, 47 | without any additional terms or conditions. 48 | 49 | [//]: # (links) 50 | 51 | [CONTRIBUTING.md]: https://github.com/iqlusioninc/crates/blob/main/CONTRIBUTING.md 52 | [CODE_OF_CONDUCT.md]: https://github.com/iqlusioninc/crates/blob/main/CODE_OF_CONDUCT.md 53 | 54 | [//]: # (badges) 55 | 56 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 57 | [license-link]: https://github.com/iqlusioninc/crates/blob/main/LICENSE 58 | [deps-image]: https://deps.rs/repo/github/iqlusioninc/crates/status.svg 59 | [deps-link]: https://deps.rs/repo/github/iqlusioninc/crates 60 | 61 | [//]: # (crates) 62 | 63 | [bip32]: https://github.com/iqlusioninc/crates/tree/main/bip32 64 | [bip32-crate]: https://img.shields.io/crates/v/bip32.svg 65 | [canonical‑path]: https://github.com/iqlusioninc/crates/tree/main/canonical-path 66 | [canonical-path-crate]: https://img.shields.io/crates/v/canonical-path.svg 67 | [hkd32]: https://github.com/iqlusioninc/crates/tree/main/hkd32 68 | [hkd32-crate]: https://img.shields.io/crates/v/hkd32.svg 69 | [iqhttp]: https://github.com/iqlusioninc/crates/tree/main/iqhttp 70 | [iqhttp-crate]: https://img.shields.io/crates/v/iqhttp.svg 71 | [secrecy]: https://github.com/iqlusioninc/crates/tree/main/secrecy 72 | [secrecy-crate]: https://img.shields.io/crates/v/secrecy.svg 73 | [signatory]: https://github.com/iqlusioninc/crates/tree/main/signatory 74 | [signatory-crate]: https://img.shields.io/crates/v/signatory.svg 75 | [subtle‑encoding]: https://github.com/iqlusioninc/crates/tree/main/subtle-encoding 76 | [subtle-encoding-crate]: https://img.shields.io/crates/v/subtle-encoding.svg 77 | 78 | [//]: # (build) 79 | 80 | [bip32-build]: https://github.com/iqlusioninc/crates/actions/workflows/bip32.yml/badge.svg 81 | [canonical-path-build]: https://github.com/iqlusioninc/crates/actions/workflows/canonical-path.yml/badge.svg 82 | [hkd32-build]: https://github.com/iqlusioninc/crates/actions/workflows/hkd32.yml/badge.svg 83 | [iqhttp-build]: https://github.com/iqlusioninc/crates/actions/workflows/iqhttp.yml/badge.svg 84 | [secrecy-build]: https://github.com/iqlusioninc/crates/actions/workflows/secrecy.yml/badge.svg 85 | [signatory-build]: https://github.com/iqlusioninc/crates/actions/workflows/signatory.yml/badge.svg 86 | [subtle-encoding-build]: https://github.com/iqlusioninc/crates/actions/workflows/subtle-encoding.yml/badge.svg 87 | [tai64-build]: https://github.com/iqlusioninc/crates/actions/workflows/tai64.yml/badge.svg 88 | -------------------------------------------------------------------------------- /bip32/src/mnemonic/phrase.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 mnemonic phrases 2 | 3 | use super::{ 4 | bits::{BitWriter, IterExt}, 5 | language::Language, 6 | }; 7 | use crate::{Error, KEY_SIZE}; 8 | use alloc::{format, string::String}; 9 | use rand_core::{CryptoRng, RngCore}; 10 | use sha2::{Digest, Sha256}; 11 | use zeroize::{Zeroize, Zeroizing}; 12 | 13 | #[cfg(feature = "bip39")] 14 | use {super::seed::Seed, sha2::Sha512}; 15 | 16 | /// Number of PBKDF2 rounds to perform when deriving the seed 17 | #[cfg(feature = "bip39")] 18 | const PBKDF2_ROUNDS: u32 = 2048; 19 | 20 | /// Source entropy for a BIP39 mnemonic phrase 21 | pub type Entropy = [u8; KEY_SIZE]; 22 | 23 | /// BIP39 mnemonic phrases: sequences of words representing cryptographic keys. 24 | #[derive(Clone)] 25 | pub struct Phrase { 26 | /// Language 27 | language: Language, 28 | 29 | /// Source entropy for this phrase 30 | entropy: Entropy, 31 | 32 | /// Mnemonic phrase 33 | phrase: String, 34 | } 35 | 36 | impl Phrase { 37 | /// Create a random BIP39 mnemonic phrase. 38 | pub fn random(mut rng: impl RngCore + CryptoRng, language: Language) -> Self { 39 | let mut entropy = Entropy::default(); 40 | rng.fill_bytes(&mut entropy); 41 | Self::from_entropy(entropy, language) 42 | } 43 | 44 | /// Create a new BIP39 mnemonic phrase from the given entropy 45 | pub fn from_entropy(entropy: Entropy, language: Language) -> Self { 46 | let wordlist = language.wordlist(); 47 | let checksum_byte = Sha256::digest(entropy.as_slice()).as_slice()[0]; 48 | 49 | // First, create a byte iterator for the given entropy and the first byte of the 50 | // hash of the entropy that will serve as the checksum (up to 8 bits for biggest 51 | // entropy source). 52 | // 53 | // Then we transform that into a bits iterator that returns 11 bits at a 54 | // time (as u16), which we can map to the words on the `wordlist`. 55 | // 56 | // Given the entropy is of correct size, this ought to give us the correct word 57 | // count. 58 | let phrase = entropy 59 | .iter() 60 | .chain(Some(&checksum_byte)) 61 | .bits() 62 | .map(|bits| wordlist.get_word(bits)) 63 | .join(" "); 64 | 65 | Phrase { 66 | language, 67 | entropy, 68 | phrase, 69 | } 70 | } 71 | 72 | /// Create a new BIP39 mnemonic phrase from the given string. 73 | /// 74 | /// The phrase supplied will be checked for word length and validated 75 | /// according to the checksum specified in BIP0039. 76 | /// 77 | /// To use the default language, English, (the only one supported by this 78 | /// library and also the only one standardized for BIP39) you can supply 79 | /// `Default::default()` as the language. 80 | pub fn new(phrase: S, language: Language) -> Result 81 | where 82 | S: AsRef, 83 | { 84 | let phrase = phrase.as_ref(); 85 | let wordmap = language.wordmap(); 86 | 87 | // Preallocate enough space for the longest possible word list 88 | let mut bits = BitWriter::with_capacity(264); 89 | 90 | for word in phrase.split(' ') { 91 | bits.push(wordmap.get_bits(word).ok_or(Error::Bip39)?); 92 | } 93 | 94 | let mut entropy = Zeroizing::new(bits.into_bytes()); 95 | 96 | if entropy.len() != KEY_SIZE + 1 { 97 | return Err(Error::Bip39); 98 | } 99 | 100 | let actual_checksum = entropy[KEY_SIZE]; 101 | 102 | // Truncate to get rid of the byte containing the checksum 103 | entropy.truncate(KEY_SIZE); 104 | 105 | let expected_checksum = Sha256::digest(&entropy).as_slice()[0]; 106 | 107 | if actual_checksum != expected_checksum { 108 | return Err(Error::Bip39); 109 | } 110 | 111 | Ok(Self::from_entropy( 112 | entropy.as_slice().try_into().map_err(|_| Error::Bip39)?, 113 | language, 114 | )) 115 | } 116 | 117 | /// Get source entropy for this phrase. 118 | pub fn entropy(&self) -> &Entropy { 119 | &self.entropy 120 | } 121 | 122 | /// Get the mnemonic phrase as a string reference. 123 | pub fn phrase(&self) -> &str { 124 | &self.phrase 125 | } 126 | 127 | /// Language this phrase's wordlist is for 128 | pub fn language(&self) -> Language { 129 | self.language 130 | } 131 | 132 | /// Convert this mnemonic phrase into the BIP39 seed value. 133 | #[cfg(feature = "bip39")] 134 | pub fn to_seed(&self, password: &str) -> Seed { 135 | let salt = Zeroizing::new(format!("mnemonic{}", password)); 136 | let mut seed = [0u8; Seed::SIZE]; 137 | pbkdf2::pbkdf2_hmac::( 138 | self.phrase.as_bytes(), 139 | salt.as_bytes(), 140 | PBKDF2_ROUNDS, 141 | &mut seed, 142 | ); 143 | Seed(seed) 144 | } 145 | } 146 | 147 | impl Drop for Phrase { 148 | fn drop(&mut self) { 149 | self.phrase.zeroize(); 150 | self.entropy.zeroize(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /bip32/src/derivation_path.rs: -------------------------------------------------------------------------------- 1 | //! Derivation paths 2 | 3 | use crate::{ChildNumber, Error, Result}; 4 | use alloc::vec::{self, Vec}; 5 | use core::{ 6 | fmt::{self, Display}, 7 | str::FromStr, 8 | }; 9 | 10 | /// Prefix for all derivation paths. 11 | const PREFIX: &str = "m"; 12 | 13 | /// Derivation paths within a hierarchical keyspace. 14 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 15 | pub struct DerivationPath { 16 | path: Vec, 17 | } 18 | 19 | impl DerivationPath { 20 | /// Iterate over the [`ChildNumber`] values in this derivation path. 21 | pub fn iter(&self) -> impl Iterator + '_ { 22 | self.path.iter().cloned() 23 | } 24 | 25 | /// Is this derivation path empty? (i.e. the root) 26 | pub fn is_empty(&self) -> bool { 27 | self.path.is_empty() 28 | } 29 | 30 | /// Get the count of [`ChildNumber`] values in this derivation path. 31 | pub fn len(&self) -> usize { 32 | self.path.len() 33 | } 34 | 35 | /// Get the parent [`DerivationPath`] for the current one. 36 | /// 37 | /// Returns `None` if this is already the root path. 38 | pub fn parent(&self) -> Option { 39 | self.path.len().checked_sub(1).map(|n| { 40 | let mut parent = self.clone(); 41 | parent.path.truncate(n); 42 | parent 43 | }) 44 | } 45 | 46 | /// Push a [`ChildNumber`] onto an existing derivation path. 47 | pub fn push(&mut self, child_number: ChildNumber) { 48 | self.path.push(child_number) 49 | } 50 | } 51 | 52 | impl AsRef<[ChildNumber]> for DerivationPath { 53 | fn as_ref(&self) -> &[ChildNumber] { 54 | &self.path 55 | } 56 | } 57 | 58 | impl Display for DerivationPath { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | f.write_str(PREFIX)?; 61 | 62 | for child_number in self.iter() { 63 | write!(f, "/{}", child_number)?; 64 | } 65 | 66 | Ok(()) 67 | } 68 | } 69 | 70 | impl Extend for DerivationPath { 71 | fn extend(&mut self, iter: T) 72 | where 73 | T: IntoIterator, 74 | { 75 | self.path.extend(iter); 76 | } 77 | } 78 | 79 | impl FromStr for DerivationPath { 80 | type Err = Error; 81 | 82 | fn from_str(path: &str) -> Result { 83 | let mut path = path.split('/'); 84 | 85 | if path.next() != Some(PREFIX) { 86 | return Err(Error::Decode); 87 | } 88 | 89 | Ok(DerivationPath { 90 | path: path.map(str::parse).collect::>()?, 91 | }) 92 | } 93 | } 94 | 95 | impl IntoIterator for DerivationPath { 96 | type Item = ChildNumber; 97 | type IntoIter = vec::IntoIter; 98 | 99 | fn into_iter(self) -> vec::IntoIter { 100 | self.path.into_iter() 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::DerivationPath; 107 | use alloc::string::ToString; 108 | 109 | /// BIP32 test vectors 110 | // TODO(tarcieri): consolidate test vectors 111 | #[test] 112 | fn round_trip() { 113 | let path_m = "m"; 114 | assert_eq!( 115 | path_m.parse::().unwrap().to_string(), 116 | path_m 117 | ); 118 | 119 | let path_m_0 = "m/0"; 120 | assert_eq!( 121 | path_m_0.parse::().unwrap().to_string(), 122 | path_m_0 123 | ); 124 | 125 | let path_m_0_2147483647h = "m/0/2147483647'"; 126 | assert_eq!( 127 | path_m_0_2147483647h 128 | .parse::() 129 | .unwrap() 130 | .to_string(), 131 | path_m_0_2147483647h 132 | ); 133 | 134 | let path_m_0_2147483647h_1 = "m/0/2147483647'/1"; 135 | assert_eq!( 136 | path_m_0_2147483647h_1 137 | .parse::() 138 | .unwrap() 139 | .to_string(), 140 | path_m_0_2147483647h_1 141 | ); 142 | 143 | let path_m_0_2147483647h_1_2147483646h = "m/0/2147483647'/1/2147483646'"; 144 | assert_eq!( 145 | path_m_0_2147483647h_1_2147483646h 146 | .parse::() 147 | .unwrap() 148 | .to_string(), 149 | path_m_0_2147483647h_1_2147483646h 150 | ); 151 | 152 | let path_m_0_2147483647h_1_2147483646h_2 = "m/0/2147483647'/1/2147483646'/2"; 153 | assert_eq!( 154 | path_m_0_2147483647h_1_2147483646h_2 155 | .parse::() 156 | .unwrap() 157 | .to_string(), 158 | path_m_0_2147483647h_1_2147483646h_2 159 | ); 160 | } 161 | 162 | #[test] 163 | fn parent() { 164 | let path_m_0_2147483647h = "m/0/2147483647'".parse::().unwrap(); 165 | let path_m_0 = path_m_0_2147483647h.parent().unwrap(); 166 | assert_eq!("m/0", path_m_0.to_string()); 167 | 168 | let path_m = path_m_0.parent().unwrap(); 169 | assert_eq!("m", path_m.to_string()); 170 | assert_eq!(path_m.parent(), None); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /hkd32/src/key_material.rs: -------------------------------------------------------------------------------- 1 | //! Cryptographic key material. 2 | //! 3 | //! Unlike traditional KDFs and XOFs, HKD32 acts on fixed-sized 4 | //! 256-bit (32-byte) keys. 5 | //! 6 | //! The `KeyMaterial` type is used to represent both input and output key 7 | //! material, and is the primary type useful for deriving other keys. 8 | 9 | use crate::{path::Path, Error, KEY_SIZE}; 10 | use hmac::{Hmac, Mac}; 11 | use rand_core::{CryptoRng, RngCore}; 12 | use sha2::Sha512; 13 | use zeroize::Zeroize; 14 | 15 | #[cfg(feature = "bech32")] 16 | use {alloc::string::String, subtle_encoding::bech32::Bech32, zeroize::Zeroizing}; 17 | 18 | #[cfg(feature = "mnemonic")] 19 | use crate::mnemonic; 20 | 21 | /// Cryptographic key material: 256-bit (32-byte) uniformly random bytestring 22 | /// generated either via a CSRNG or via hierarchical derivation. 23 | /// 24 | /// This type provides the main key derivation functionality and is used to 25 | /// represent both input and output key material. 26 | #[derive(Clone)] 27 | pub struct KeyMaterial([u8; KEY_SIZE]); 28 | 29 | impl KeyMaterial { 30 | /// Create random key material using the operating system CSRNG 31 | pub fn random(mut rng: impl RngCore + CryptoRng) -> Self { 32 | let mut bytes = [0u8; KEY_SIZE]; 33 | rng.fill_bytes(&mut bytes); 34 | Self::new(bytes) 35 | } 36 | 37 | /// Decode key material from a Bech32 representation 38 | #[cfg(feature = "bech32")] 39 | pub fn from_bech32(encoded: S) -> Result<(String, Self), Error> 40 | where 41 | S: AsRef, 42 | { 43 | let (hrp, mut key_bytes) = Bech32::default().decode(encoded).map_err(|_| Error)?; 44 | let key_result = Self::from_bytes(&key_bytes); 45 | key_bytes.zeroize(); 46 | key_result.map(|key| (hrp, key)) 47 | } 48 | 49 | /// Create key material from a 24-word BIP39 mnemonic phrase 50 | #[cfg(feature = "mnemonic")] 51 | pub fn from_mnemonic(phrase: S, language: mnemonic::Language) -> Result 52 | where 53 | S: AsRef, 54 | { 55 | Ok(mnemonic::Phrase::new(phrase, language)?.into()) 56 | } 57 | 58 | /// Create new key material from a byte slice. 59 | /// 60 | /// Byte slice is expected to have been generated by a cryptographically 61 | /// secure random number generator. 62 | pub fn from_bytes(slice: &[u8]) -> Result { 63 | if slice.len() == KEY_SIZE { 64 | let mut bytes = [0u8; KEY_SIZE]; 65 | bytes.copy_from_slice(slice); 66 | Ok(Self::new(bytes)) 67 | } else { 68 | Err(Error) 69 | } 70 | } 71 | 72 | /// Import existing key material - must be uniformly random! 73 | pub fn new(bytes: [u8; KEY_SIZE]) -> KeyMaterial { 74 | KeyMaterial(bytes) 75 | } 76 | 77 | /// Borrow the key material as a byte slice 78 | pub fn as_bytes(&self) -> &[u8] { 79 | &self.0 80 | } 81 | 82 | /// Derive an output key from the given input key material 83 | pub fn derive_subkey

(self, path: P) -> Self 84 | where 85 | P: AsRef, 86 | { 87 | let component_count = path.as_ref().components().count(); 88 | 89 | path.as_ref() 90 | .components() 91 | .enumerate() 92 | .fold(self, |parent_key, (i, component)| { 93 | let mut hmac = Hmac::::new_from_slice(parent_key.as_bytes()) 94 | .expect("HMAC key size incorrect"); 95 | hmac.update(component.as_bytes()); 96 | 97 | let mut hmac_result = hmac.finalize().into_bytes(); 98 | let (secret_key, chain_code) = hmac_result.split_at_mut(KEY_SIZE); 99 | let mut child_key = [0u8; KEY_SIZE]; 100 | 101 | if i < component_count - 1 { 102 | // Use chain code for all but the last element 103 | child_key.copy_from_slice(chain_code); 104 | } else { 105 | // Use secret key for the last element 106 | child_key.copy_from_slice(secret_key); 107 | } 108 | 109 | secret_key.zeroize(); 110 | chain_code.zeroize(); 111 | 112 | KeyMaterial(child_key) 113 | }) 114 | } 115 | 116 | /// Serialize this `KeyMaterial` as a self-zeroizing Bech32 string 117 | #[cfg(feature = "bech32")] 118 | pub fn to_bech32(&self, hrp: S) -> Zeroizing 119 | where 120 | S: AsRef, 121 | { 122 | let b32 = Bech32::default().encode(hrp, self.as_bytes()); 123 | Zeroizing::new(b32) 124 | } 125 | } 126 | 127 | impl Drop for KeyMaterial { 128 | fn drop(&mut self) { 129 | self.zeroize(); 130 | } 131 | } 132 | 133 | impl From<[u8; KEY_SIZE]> for KeyMaterial { 134 | fn from(bytes: [u8; KEY_SIZE]) -> Self { 135 | Self::new(bytes) 136 | } 137 | } 138 | 139 | impl<'a> TryFrom<&'a [u8]> for KeyMaterial { 140 | type Error = Error; 141 | 142 | fn try_from(slice: &'a [u8]) -> Result { 143 | Self::from_bytes(slice) 144 | } 145 | } 146 | 147 | // TODO(tarcieri): remove this impl in favor of `ZeroizeOnDrop` in next breaking release 148 | impl Zeroize for KeyMaterial { 149 | fn zeroize(&mut self) { 150 | self.0.zeroize(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /bip32/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | #![forbid(unsafe_code)] 5 | #![warn( 6 | clippy::unwrap_used, 7 | missing_docs, 8 | rust_2018_idioms, 9 | unused_qualifications 10 | )] 11 | 12 | //! ## Backends 13 | //! This crate provides a generic implementation of BIP32 which can be used 14 | //! with any backing provider which implements the [`PrivateKey`] and 15 | //! [`PublicKey`] traits. The following providers are built into this crate, 16 | //! under the following crate features: 17 | //! 18 | //! - `secp256k1` (enabled by default): support for the pure Rust [`k256`] 19 | //! crate, with [`XPrv`] and [`XPub`] type aliases. 20 | //! - `secp256k1-ffi`: support for Bitcoin Core's [libsecp256k1 C library], 21 | //! as wrapped by the [`secp256k1` Rust crate]. 22 | //! 23 | //! ## Limitations and further work 24 | //! - Only 24-word BIP39 mnemonics are supported 25 | //! - BIP43, BIP44, BIP49, BIP84 not yet properly supported 26 | //! 27 | //! # Usage 28 | //! The following is an end-to-end example of how to generate a random BIP39 29 | //! mnemonic and use it to derive child keys according to a provided BIP32 30 | //! derivation path. 31 | //! 32 | //! ## Accessing `OsRng` 33 | //! The following example uses `OsRng` for cryptographically secure random 34 | //! number generation. To use it, you need to include `rand_core` with the 35 | //! `std` feature by adding the following to `Cargo.toml`: 36 | //! 37 | //! ```toml 38 | //! rand_core = { version = "0.6", features = ["std"] } 39 | //! ``` 40 | //! 41 | //! (on embedded platforms, you will need to supply our own RNG) 42 | //! 43 | //! ## Rust code example 44 | //! ``` 45 | //! # fn main() -> Result<(), Box> { 46 | //! # #[cfg(all(feature = "bip39", feature = "secp256k1"))] 47 | //! # { 48 | //! use bip32::{Mnemonic, Prefix, XPrv}; 49 | //! use rand_core::OsRng; 50 | //! 51 | //! // Generate random Mnemonic using the default language (English) 52 | //! let mnemonic = Mnemonic::random(&mut OsRng, Default::default()); 53 | //! 54 | //! // Derive a BIP39 seed value using the given password 55 | //! let seed = mnemonic.to_seed("password"); 56 | //! 57 | //! // Derive the root `XPrv` from the `seed` value 58 | //! let root_xprv = XPrv::new(&seed)?; 59 | //! assert_eq!(root_xprv, XPrv::derive_from_path(&seed, &"m".parse()?)?); 60 | //! 61 | //! // Derive a child `XPrv` using the provided BIP32 derivation path 62 | //! let child_path = "m/0/2147483647'/1/2147483646'"; 63 | //! let child_xprv = XPrv::derive_from_path(&seed, &child_path.parse()?)?; 64 | //! 65 | //! // Get the `XPub` associated with `child_xprv`. 66 | //! let child_xpub = child_xprv.public_key(); 67 | //! 68 | //! // Serialize `child_xprv` as a string with the `xprv` prefix. 69 | //! let child_xprv_str = child_xprv.to_string(Prefix::XPRV); 70 | //! assert!(child_xprv_str.starts_with("xprv")); 71 | //! 72 | //! // Serialize `child_xpub` as a string with the `xpub` prefix. 73 | //! let child_xpub_str = child_xpub.to_string(Prefix::XPUB); 74 | //! assert!(child_xpub_str.starts_with("xpub")); 75 | //! 76 | //! // Get the ECDSA/secp256k1 signing and verification keys for the xprv and xpub 77 | //! let signing_key = child_xprv.private_key(); 78 | //! let verification_key = child_xpub.public_key(); 79 | //! 80 | //! // Sign and verify an example message using the derived keys. 81 | //! use bip32::secp256k1::ecdsa::{ 82 | //! signature::{Signer, Verifier}, 83 | //! Signature 84 | //! }; 85 | //! 86 | //! let example_msg = b"Hello, world!"; 87 | //! let signature: Signature = signing_key.sign(example_msg); 88 | //! assert!(verification_key.verify(example_msg, &signature).is_ok()); 89 | //! # } 90 | //! # Ok(()) 91 | //! # } 92 | //! ``` 93 | //! 94 | //! [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 95 | //! [libsecp256k1 C library]: https://github.com/bitcoin-core/secp256k1 96 | //! [`secp256k1` Rust crate]: https://github.com/rust-bitcoin/rust-secp256k1/ 97 | 98 | #[cfg(feature = "alloc")] 99 | extern crate alloc; 100 | 101 | #[cfg(feature = "std")] 102 | extern crate std; 103 | 104 | mod child_number; 105 | mod error; 106 | mod extended_key; 107 | mod prefix; 108 | mod private_key; 109 | mod public_key; 110 | 111 | #[cfg(feature = "alloc")] 112 | mod derivation_path; 113 | 114 | #[cfg(feature = "mnemonic")] 115 | mod mnemonic; 116 | 117 | pub use crate::{ 118 | child_number::ChildNumber, 119 | error::{Error, Result}, 120 | extended_key::{ 121 | attrs::ExtendedKeyAttrs, private_key::ExtendedPrivateKey, public_key::ExtendedPublicKey, 122 | ExtendedKey, 123 | }, 124 | prefix::Prefix, 125 | private_key::{PrivateKey, PrivateKeyBytes}, 126 | public_key::{PublicKey, PublicKeyBytes}, 127 | }; 128 | 129 | #[cfg(feature = "alloc")] 130 | pub use crate::derivation_path::DerivationPath; 131 | 132 | #[cfg(feature = "bip39")] 133 | pub use crate::mnemonic::{Language, Phrase as Mnemonic, Seed}; 134 | 135 | #[cfg(feature = "secp256k1")] 136 | pub use { 137 | crate::extended_key::{private_key::XPrv, public_key::XPub}, 138 | k256 as secp256k1, 139 | }; 140 | 141 | /// Chain code: extension for both private and public keys which provides an 142 | /// additional 256-bits of entropy. 143 | pub type ChainCode = [u8; KEY_SIZE]; 144 | 145 | /// Derivation depth. 146 | pub type Depth = u8; 147 | 148 | /// BIP32 key fingerprints. 149 | pub type KeyFingerprint = [u8; 4]; 150 | 151 | /// BIP32 "versions": integer representation of the key prefix. 152 | pub type Version = u32; 153 | 154 | /// HMAC with SHA-512 155 | type HmacSha512 = hmac::Hmac; 156 | 157 | /// Size of input key material and derived keys. 158 | pub const KEY_SIZE: usize = 32; 159 | -------------------------------------------------------------------------------- /iqhttp/src/https_client.rs: -------------------------------------------------------------------------------- 1 | //! HTTPS client 2 | 3 | use crate::{Path, Query, Result, USER_AGENT}; 4 | use hyper::{ 5 | body::Buf, 6 | client::{Client, HttpConnector}, 7 | header::{self, HeaderMap, HeaderName, HeaderValue}, 8 | Body, Request, Response, 9 | }; 10 | use hyper_rustls::HttpsConnector; 11 | 12 | #[cfg(feature = "json")] 13 | use serde::de::DeserializeOwned; 14 | 15 | #[cfg(feature = "proxy")] 16 | use { 17 | crate::{Error, Uri}, 18 | hyper_proxy::{Intercept, Proxy, ProxyConnector}, 19 | }; 20 | 21 | /// HTTPS client. 22 | /// 23 | /// This type provides a persistent connection to a particular hostname and 24 | /// allows requests by path and query string. 25 | pub struct HttpsClient { 26 | /// Enum over possible `hyper` clients. 27 | inner: InnerClient, 28 | 29 | /// Hostname this client is making requests to. 30 | hostname: String, 31 | 32 | /// Headers to send in the request. 33 | headers: HeaderMap, 34 | } 35 | 36 | impl HttpsClient { 37 | /// Create a new HTTPS client which makes requests to the given hostname. 38 | pub fn new(hostname: impl Into) -> Self { 39 | let client = Client::builder().build(Self::https_connector()); 40 | 41 | Self { 42 | inner: InnerClient::Https(client), 43 | hostname: hostname.into(), 44 | headers: default_headers(), 45 | } 46 | } 47 | 48 | /// Create a new HTTPS client which makes requests to the given hostname 49 | /// via the provided HTTP CONNECT proxy. 50 | // TODO(tarcieri): proxy auth 51 | #[cfg(feature = "proxy")] 52 | pub fn new_with_proxy(hostname: impl Into, proxy_uri: Uri) -> Result { 53 | let connector = Self::https_connector(); 54 | let proxy = Proxy::new(Intercept::All, proxy_uri); 55 | let proxy_connector = ProxyConnector::from_proxy(connector, proxy).map_err(Error::Proxy)?; 56 | let client = Client::builder().build(proxy_connector); 57 | 58 | Ok(Self { 59 | inner: InnerClient::HttpsViaProxy(client), 60 | hostname: hostname.into(), 61 | headers: default_headers(), 62 | }) 63 | } 64 | 65 | /// Borrow the request headers mutably. 66 | pub fn headers_mut(&mut self) -> &mut HeaderMap { 67 | &mut self.headers 68 | } 69 | 70 | /// Add a header to this request context. 71 | pub fn add_header(&mut self, name: HeaderName, value: &str) -> Result> { 72 | Ok(self.headers.insert(name, value.parse()?)) 73 | } 74 | 75 | /// Perform a low-level request using hyper's types directly. 76 | pub async fn request(&self, mut request: Request) -> Result> { 77 | if request.headers().is_empty() { 78 | *request.headers_mut() = self.headers.clone(); 79 | } else { 80 | for (name, value) in &self.headers { 81 | request.headers_mut().append(name, value.clone()); 82 | } 83 | } 84 | 85 | Ok(match &self.inner { 86 | InnerClient::Https(client) => client.request(request), 87 | #[cfg(feature = "proxy")] 88 | InnerClient::HttpsViaProxy(client) => client.request(request), 89 | } 90 | .await?) 91 | } 92 | 93 | /// Perform HTTP GET request for the given [`Path`] and [`Query`]. 94 | pub async fn get(&self, path: &Path, query: &Query) -> Result> { 95 | let uri = query.to_request_uri(&self.hostname, path); 96 | 97 | // TODO(tarcieri): better errors 98 | let request = Request::builder() 99 | .method("GET") 100 | .uri(&uri) 101 | .body(Body::empty())?; 102 | 103 | self.request(request).await 104 | } 105 | 106 | /// Perform HTTP GET request and return the response body. 107 | pub async fn get_body(&self, path: &Path, query: &Query) -> Result { 108 | // TODO(tarcieri): timeouts 109 | let response = self.get(path, query).await?; 110 | Ok(hyper::body::aggregate(response.into_body()).await?) 111 | } 112 | 113 | /// Perform HTTP GET request and parse the response as JSON. 114 | #[cfg(feature = "json")] 115 | pub async fn get_json(&self, path: &Path, query: &Query) -> Result 116 | where 117 | T: DeserializeOwned, 118 | { 119 | let uri = query.to_request_uri(&self.hostname, path); 120 | 121 | let mut request = Request::builder() 122 | .method("GET") 123 | .uri(&uri) 124 | .body(Body::empty())?; 125 | 126 | request 127 | .headers_mut() 128 | .append(header::CONTENT_TYPE, "application/json".parse()?); 129 | 130 | let response = self.request(request).await?; 131 | let body = hyper::body::aggregate(response.into_body()).await?; 132 | Ok(serde_json::from_reader(body.reader())?) 133 | } 134 | 135 | /// Get the `HttpsConnector` to use. 136 | fn https_connector() -> HttpsConnector { 137 | HttpsConnector::with_native_roots() 138 | } 139 | } 140 | 141 | /// Inner client type which abstracts over the presence or absence of a proxy 142 | enum InnerClient { 143 | /// HTTPS client (no proxy) 144 | Https(Client, Body>), 145 | 146 | /// HTTPS client with proxy 147 | #[cfg(feature = "proxy")] 148 | HttpsViaProxy(Client>, Body>), 149 | } 150 | 151 | /// Default headers to send with requests. 152 | fn default_headers() -> HeaderMap { 153 | let mut headers = HeaderMap::new(); 154 | headers.insert( 155 | header::USER_AGENT, 156 | USER_AGENT.parse().expect("USER_AGENT invalid"), 157 | ); 158 | headers 159 | } 160 | -------------------------------------------------------------------------------- /hkd32/tests/bip39_vectors.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 test vectors 2 | 3 | #![cfg(feature = "bip39")] 4 | 5 | use hkd32::mnemonic; 6 | 7 | fn test_mnemonic(entropy_bytes: &[u8], expected_phrase: &str) { 8 | let mnemonic = mnemonic::Phrase::from_entropy( 9 | entropy_bytes.try_into().unwrap(), 10 | mnemonic::Language::English, 11 | ); 12 | assert_eq!(mnemonic.phrase(), expected_phrase); 13 | } 14 | 15 | fn test_seed(phrase: &str, password: &str, expected_seed_bytes: &[u8]) { 16 | let mnemonic = mnemonic::Phrase::new(phrase, mnemonic::Language::English).unwrap(); 17 | let seed = mnemonic.to_seed(password); 18 | let actual_seed_bytes: &[u8] = seed.as_bytes(); 19 | 20 | assert!( 21 | actual_seed_bytes.eq(expected_seed_bytes), 22 | "Wrong seed for '{}'\nexp: {:?}\nact: {:?}\n", 23 | phrase, 24 | expected_seed_bytes, 25 | actual_seed_bytes 26 | ); 27 | } 28 | 29 | macro_rules! tests { 30 | ($([$entropy_hex:expr, $phrase:expr, $seed_hex:expr, $xprv:expr]),*) => { 31 | mod mnemonic_tests { 32 | #[test] 33 | fn test_all() { 34 | $( 35 | super::test_mnemonic(&hex_literal::hex!($entropy_hex), $phrase); 36 | )* 37 | } 38 | } 39 | 40 | mod seed_tests { 41 | #[test] 42 | fn test_all() { 43 | $( 44 | super::test_seed($phrase, "TREZOR", &hex_literal::hex!($seed_hex)); 45 | )* 46 | } 47 | } 48 | }; 49 | } 50 | 51 | tests! { 52 | // https://github.com/trezor/python-mnemonic/blob/master/vectors.json 53 | [ 54 | "0000000000000000000000000000000000000000000000000000000000000000", 55 | "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", 56 | "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", 57 | "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM" 58 | ], 59 | [ 60 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 61 | "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", 62 | "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", 63 | "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU" 64 | ], 65 | [ 66 | "8080808080808080808080808080808080808080808080808080808080808080", 67 | "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", 68 | "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", 69 | "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo" 70 | ], 71 | [ 72 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 73 | "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", 74 | "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", 75 | "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB" 76 | ], 77 | 78 | [ 79 | "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", 80 | "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", 81 | "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", 82 | "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk" 83 | ], 84 | [ 85 | "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", 86 | "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", 87 | "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", 88 | "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems" 89 | ], 90 | [ 91 | "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", 92 | "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", 93 | "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", 94 | "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm" 95 | ], 96 | [ 97 | "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", 98 | "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", 99 | "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", 100 | "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS" 101 | ] 102 | } 103 | -------------------------------------------------------------------------------- /hkd32/src/mnemonic/phrase.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 mnemonic phrases 2 | 3 | use super::{ 4 | bits::{BitWriter, IterExt}, 5 | language::Language, 6 | }; 7 | use crate::{Error, KeyMaterial, Path, KEY_SIZE}; 8 | use alloc::string::String; 9 | use rand_core::{CryptoRng, RngCore}; 10 | use sha2::{Digest, Sha256}; 11 | use zeroize::{Zeroize, Zeroizing}; 12 | 13 | #[cfg(feature = "bip39")] 14 | use {super::seed::Seed, sha2::Sha512}; 15 | 16 | /// Number of PBKDF2 rounds to perform when deriving the seed 17 | #[cfg(feature = "bip39")] 18 | const PBKDF2_ROUNDS: u32 = 2048; 19 | 20 | /// Source entropy for a BIP39 mnemonic phrase 21 | pub type Entropy = [u8; KEY_SIZE]; 22 | 23 | /// BIP39 mnemonic phrases: sequences of words representing cryptographic keys. 24 | #[derive(Clone)] 25 | pub struct Phrase { 26 | /// Language 27 | language: Language, 28 | 29 | /// Source entropy for this phrase 30 | entropy: Entropy, 31 | 32 | /// Mnemonic phrase 33 | phrase: String, 34 | } 35 | 36 | impl Phrase { 37 | /// Create a random BIP39 mnemonic phrase. 38 | pub fn random(mut rng: impl RngCore + CryptoRng, language: Language) -> Self { 39 | let mut entropy = Entropy::default(); 40 | rng.fill_bytes(&mut entropy); 41 | Self::from_entropy(entropy, language) 42 | } 43 | 44 | /// Create a new BIP39 mnemonic phrase from the given entropy 45 | pub fn from_entropy(entropy: Entropy, language: Language) -> Self { 46 | let wordlist = language.wordlist(); 47 | let checksum_byte = Sha256::digest(entropy.as_ref()).as_slice()[0]; 48 | 49 | // First, create a byte iterator for the given entropy and the first byte of the 50 | // hash of the entropy that will serve as the checksum (up to 8 bits for biggest 51 | // entropy source). 52 | // 53 | // Then we transform that into a bits iterator that returns 11 bits at a 54 | // time (as u16), which we can map to the words on the `wordlist`. 55 | // 56 | // Given the entropy is of correct size, this ought to give us the correct word 57 | // count. 58 | let phrase = entropy 59 | .iter() 60 | .chain(Some(&checksum_byte)) 61 | .bits() 62 | .map(|bits| wordlist.get_word(bits)) 63 | .join(" "); 64 | 65 | Phrase { 66 | language, 67 | entropy, 68 | phrase, 69 | } 70 | } 71 | 72 | /// Create a new BIP39 mnemonic phrase from the given string. 73 | /// 74 | /// The phrase supplied will be checked for word length and validated 75 | /// according to the checksum specified in BIP0039. 76 | /// 77 | /// To use the default language, English, (the only one supported by this 78 | /// library and also the only one standardized for BIP39) you can supply 79 | /// `Default::default()` as the language. 80 | pub fn new(phrase: S, language: Language) -> Result 81 | where 82 | S: AsRef, 83 | { 84 | let phrase = phrase.as_ref(); 85 | let wordmap = language.wordmap(); 86 | 87 | // Preallocate enough space for the longest possible word list 88 | let mut bits = BitWriter::with_capacity(264); 89 | 90 | for word in phrase.split(' ') { 91 | bits.push(wordmap.get_bits(word)?); 92 | } 93 | 94 | let mut entropy = Zeroizing::new(bits.into_bytes()); 95 | 96 | if entropy.len() != KEY_SIZE + 1 { 97 | return Err(Error); 98 | } 99 | 100 | let actual_checksum = entropy[KEY_SIZE]; 101 | 102 | // Truncate to get rid of the byte containing the checksum 103 | entropy.truncate(KEY_SIZE); 104 | 105 | let expected_checksum = Sha256::digest(&entropy).as_slice()[0]; 106 | 107 | if actual_checksum != expected_checksum { 108 | return Err(Error); 109 | } 110 | 111 | Ok(Self::from_entropy( 112 | entropy.as_slice().try_into().unwrap(), 113 | language, 114 | )) 115 | } 116 | 117 | /// Get source entropy for this phrase. 118 | pub fn entropy(&self) -> &Entropy { 119 | &self.entropy 120 | } 121 | 122 | /// Get the mnemonic phrase as a string reference. 123 | pub fn phrase(&self) -> &str { 124 | &self.phrase 125 | } 126 | 127 | /// Language this phrase's wordlist is for 128 | pub fn language(&self) -> Language { 129 | self.language 130 | } 131 | 132 | /// Convert this mnemonic phrase's entropy directly into key material. 133 | /// If you are looking for the shortest path between a mnemonic phrase 134 | /// and a key derivation hierarchy, this is it. 135 | /// 136 | /// Note: that this does not follow the normal BIP39 derivation, which 137 | /// first applies PBKDF2 along with a secondary password. Use `to_seed` 138 | /// if you are interested in BIP39 compatibility. 139 | pub fn derive_subkey(self, path: impl AsRef) -> KeyMaterial { 140 | KeyMaterial::from(self).derive_subkey(path) 141 | } 142 | 143 | /// Convert this mnemonic phrase into the BIP39 seed value. 144 | #[cfg(feature = "bip39")] 145 | pub fn to_seed(&self, password: &str) -> Seed { 146 | let salt = Zeroizing::new(format!("mnemonic{}", password)); 147 | let mut seed = [0u8; Seed::SIZE]; 148 | pbkdf2::pbkdf2_hmac::( 149 | self.phrase.as_bytes(), 150 | salt.as_bytes(), 151 | PBKDF2_ROUNDS, 152 | &mut seed, 153 | ); 154 | Seed(seed) 155 | } 156 | } 157 | 158 | impl From for KeyMaterial { 159 | /// Convert to `KeyMaterial` using an empty password 160 | fn from(phrase: Phrase) -> KeyMaterial { 161 | KeyMaterial::from_bytes(&phrase.entropy).unwrap() 162 | } 163 | } 164 | 165 | impl Drop for Phrase { 166 | fn drop(&mut self) { 167 | self.phrase.zeroize(); 168 | self.entropy.zeroize(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /bip32/tests/bip39_vectors.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 test vectors 2 | 3 | #![cfg(all(feature = "bip39", feature = "secp256k1"))] 4 | 5 | use bip32::{Mnemonic, Seed, XPrv}; 6 | use hex_literal::hex; 7 | 8 | /// BIP39 test vector 9 | struct TestVector { 10 | entropy: [u8; 32], 11 | phrase: &'static str, 12 | seed: [u8; 64], 13 | xprv: &'static str, 14 | } 15 | 16 | /// Password used on all test vectors 17 | const TEST_VECTOR_PASSWORD: &str = "TREZOR"; 18 | 19 | /// From: https://github.com/trezor/python-mnemonic/blob/master/vectors.json 20 | const TEST_VECTORS: &[TestVector] = &[ 21 | TestVector { 22 | entropy: hex!("0000000000000000000000000000000000000000000000000000000000000000"), 23 | phrase: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", 24 | seed: hex!("bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"), 25 | xprv: "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM" 26 | }, 27 | TestVector { 28 | entropy: hex!("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"), 29 | phrase: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", 30 | seed: hex!("bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87"), 31 | xprv: "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU" 32 | }, 33 | TestVector { 34 | entropy: hex!("8080808080808080808080808080808080808080808080808080808080808080"), 35 | phrase: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", 36 | seed: hex!("c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f"), 37 | xprv: "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo" 38 | }, 39 | TestVector { 40 | entropy: hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 41 | phrase: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", 42 | seed: hex!("dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad"), 43 | xprv: "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB" 44 | }, 45 | TestVector { 46 | entropy: hex!("68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c"), 47 | phrase: "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", 48 | seed: hex!("64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440"), 49 | xprv: "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk" 50 | }, 51 | TestVector { 52 | entropy: hex!("9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863"), 53 | phrase: "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", 54 | seed: hex!("72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d"), 55 | xprv: "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems" 56 | }, 57 | TestVector { 58 | entropy: hex!("066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad"), 59 | phrase: "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", 60 | seed: hex!("26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d"), 61 | xprv: "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm" 62 | }, 63 | TestVector { 64 | entropy: hex!("f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f"), 65 | phrase: "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", 66 | seed: hex!("01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998"), 67 | xprv: "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS" 68 | } 69 | ]; 70 | 71 | #[test] 72 | fn test_mnemonic() { 73 | for vector in TEST_VECTORS { 74 | let mnemonic = Mnemonic::from_entropy(vector.entropy, Default::default()); 75 | assert_eq!(mnemonic.phrase(), vector.phrase); 76 | } 77 | } 78 | 79 | #[test] 80 | fn test_seed() { 81 | for vector in TEST_VECTORS { 82 | let mnemonic = Mnemonic::new(vector.phrase, Default::default()).unwrap(); 83 | assert_eq!( 84 | &vector.seed, 85 | mnemonic.to_seed(TEST_VECTOR_PASSWORD).as_bytes() 86 | ); 87 | } 88 | } 89 | 90 | #[test] 91 | fn test_xprv() { 92 | for vector in TEST_VECTORS { 93 | let seed = Seed::new(vector.seed); 94 | let expected_xprv = vector.xprv.parse::().unwrap(); 95 | let derived_xprv = XPrv::new(&seed).unwrap(); 96 | assert_eq!(expected_xprv, derived_xprv); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /bip32/src/extended_key.rs: -------------------------------------------------------------------------------- 1 | //! Parser for extended key types (i.e. `xprv` and `xpub`) 2 | 3 | pub(crate) mod attrs; 4 | pub(crate) mod private_key; 5 | pub(crate) mod public_key; 6 | 7 | use crate::{ChildNumber, Error, ExtendedKeyAttrs, Prefix, Result, Version, KEY_SIZE}; 8 | use core::{ 9 | fmt::{self, Display}, 10 | str::{self, FromStr}, 11 | }; 12 | use zeroize::Zeroize; 13 | 14 | /// Serialized extended key (e.g. `xprv` and `xpub`). 15 | #[derive(Clone)] 16 | pub struct ExtendedKey { 17 | /// [`Prefix`] (a.k.a. "version") of the key (e.g. `xprv`, `xpub`) 18 | pub prefix: Prefix, 19 | 20 | /// Extended key attributes. 21 | pub attrs: ExtendedKeyAttrs, 22 | 23 | /// Key material (may be public or private). 24 | /// 25 | /// Includes an extra byte for a public key's SEC1 tag. 26 | pub key_bytes: [u8; KEY_SIZE + 1], 27 | } 28 | 29 | impl ExtendedKey { 30 | /// Size of an extended key when deserialized into bytes from Base58. 31 | pub const BYTE_SIZE: usize = 78; 32 | 33 | /// Maximum size of a Base58Check-encoded extended key in bytes. 34 | /// 35 | /// Note that extended keys can also be 111-bytes. 36 | pub const MAX_BASE58_SIZE: usize = 112; 37 | 38 | /// Write a Base58-encoded key to the provided buffer, returning a `&str` 39 | /// containing the serialized data. 40 | /// 41 | /// Note that this type also impls [`Display`] and therefore you can 42 | /// obtain an owned string by calling `to_string()`. 43 | pub fn write_base58<'a>(&self, buffer: &'a mut [u8; Self::MAX_BASE58_SIZE]) -> Result<&'a str> { 44 | let mut bytes = [0u8; Self::BYTE_SIZE]; // with 4-byte checksum 45 | bytes[..4].copy_from_slice(&self.prefix.to_bytes()); 46 | bytes[4] = self.attrs.depth; 47 | bytes[5..9].copy_from_slice(&self.attrs.parent_fingerprint); 48 | bytes[9..13].copy_from_slice(&self.attrs.child_number.to_bytes()); 49 | bytes[13..45].copy_from_slice(&self.attrs.chain_code); 50 | bytes[45..78].copy_from_slice(&self.key_bytes); 51 | 52 | let base58_len = bs58::encode(&bytes).with_check().onto(buffer.as_mut_slice())?; 53 | bytes.zeroize(); 54 | 55 | str::from_utf8(&buffer[..base58_len]).map_err(|_| Error::Base58) 56 | } 57 | } 58 | 59 | impl Display for ExtendedKey { 60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | let mut buf = [0u8; Self::MAX_BASE58_SIZE]; 62 | self.write_base58(&mut buf) 63 | .map_err(|_| fmt::Error) 64 | .and_then(|base58| f.write_str(base58)) 65 | } 66 | } 67 | 68 | impl FromStr for ExtendedKey { 69 | type Err = Error; 70 | 71 | fn from_str(base58: &str) -> Result { 72 | let mut bytes = [0u8; Self::BYTE_SIZE + 4]; // with 4-byte checksum 73 | let decoded_len = bs58::decode(base58).with_check(None).onto(&mut bytes)?; 74 | 75 | if decoded_len != Self::BYTE_SIZE { 76 | return Err(Error::Decode); 77 | } 78 | 79 | let prefix = base58.get(..4).ok_or(Error::Decode).and_then(|chars| { 80 | Prefix::validate_str(chars)?; 81 | let version = Version::from_be_bytes(bytes[..4].try_into()?); 82 | Ok(Prefix::from_parts_unchecked(chars, version)) 83 | })?; 84 | 85 | let depth = bytes[4]; 86 | let parent_fingerprint = bytes[5..9].try_into()?; 87 | let child_number = ChildNumber::from_bytes(bytes[9..13].try_into()?); 88 | let chain_code = bytes[13..45].try_into()?; 89 | let key_bytes = bytes[45..78].try_into()?; 90 | bytes.zeroize(); 91 | 92 | let attrs = ExtendedKeyAttrs { 93 | depth, 94 | parent_fingerprint, 95 | child_number, 96 | chain_code, 97 | }; 98 | 99 | Ok(ExtendedKey { 100 | prefix, 101 | attrs, 102 | key_bytes, 103 | }) 104 | } 105 | } 106 | 107 | impl Drop for ExtendedKey { 108 | fn drop(&mut self) { 109 | self.key_bytes.zeroize(); 110 | } 111 | } 112 | 113 | // TODO(tarcieri): consolidate test vectors 114 | #[cfg(all(test, feature = "alloc"))] 115 | mod tests { 116 | use super::ExtendedKey; 117 | use alloc::string::ToString; 118 | use hex_literal::hex; 119 | 120 | #[test] 121 | fn bip32_test_vector_1_xprv() { 122 | let xprv_base58 = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPP\ 123 | qjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"; 124 | 125 | let xprv = xprv_base58.parse::().unwrap(); 126 | assert_eq!(xprv.prefix.as_str(), "xprv"); 127 | assert_eq!(xprv.attrs.depth, 0); 128 | assert_eq!(xprv.attrs.parent_fingerprint, [0u8; 4]); 129 | assert_eq!(xprv.attrs.child_number.0, 0); 130 | assert_eq!( 131 | xprv.attrs.chain_code, 132 | hex!("873DFF81C02F525623FD1FE5167EAC3A55A049DE3D314BB42EE227FFED37D508") 133 | ); 134 | assert_eq!( 135 | xprv.key_bytes, 136 | hex!("00E8F32E723DECF4051AEFAC8E2C93C9C5B214313817CDB01A1494B917C8436B35") 137 | ); 138 | assert_eq!(&xprv.to_string(), xprv_base58); 139 | } 140 | 141 | #[test] 142 | fn bip32_test_vector_1_xpub() { 143 | let xpub_base58 = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhe\ 144 | PY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; 145 | 146 | let xpub = xpub_base58.parse::().unwrap(); 147 | assert_eq!(xpub.prefix.as_str(), "xpub"); 148 | assert_eq!(xpub.attrs.depth, 0); 149 | assert_eq!(xpub.attrs.parent_fingerprint, [0u8; 4]); 150 | assert_eq!(xpub.attrs.child_number.0, 0); 151 | assert_eq!( 152 | xpub.attrs.chain_code, 153 | hex!("873DFF81C02F525623FD1FE5167EAC3A55A049DE3D314BB42EE227FFED37D508") 154 | ); 155 | assert_eq!( 156 | xpub.key_bytes, 157 | hex!("0339A36013301597DAEF41FBE593A02CC513D0B55527EC2DF1050E2E8FF49C85C2") 158 | ); 159 | assert_eq!(&xpub.to_string(), xpub_base58); 160 | } 161 | } 162 | --------------------------------------------------------------------------------