diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b2d69f4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [ ubideco, strict-types, dr-orlovsky ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4424fe7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,86 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + default: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Default build + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace + features: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + feature: + - cli + steps: + - uses: actions/checkout@v2 + - name: Install rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Feature ${{ matrix.feature }} + uses: actions-rs/cargo@v1 + with: + command: check + args: --no-default-features --features=${{ matrix.feature }} + - name: Defaults + ${{ matrix.feature }} + uses: actions-rs/cargo@v1 + with: + command: check + args: --features=${{ matrix.feature }} + platforms: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-20.04, ubuntu-22.04, macos-12, macos-13, windows-2019, windows-2022 ] + steps: + - uses: actions/checkout@v2 + - name: Install rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Build with all features + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace --all-targets --all-features + toolchains: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: [ nightly, beta, stable, 1.75.0 ] + steps: + - uses: actions/checkout@v2 + - name: Install rust ${{ matrix.toolchain }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + override: true + - name: All features + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace --all-targets --all-features diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000..2077319 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,51 @@ +name: Codecov + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + codecov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, llvm-tools-preview + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release + env: + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Cinstrument-coverage" + RUSTDOCFLAGS: "-Cinstrument-coverage" + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --no-fail-fast + env: + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Cinstrument-coverage" + RUSTDOCFLAGS: "-Cinstrument-coverage" + - name: Install grcov + run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo install grcov; fi + - name: Generate coverage + run: grcov . --binary-path target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../**' --ignore '/*' -o coverage.lcov + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage.lcov + flags: rust + fail_ci_if_error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..3f51bde --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,57 @@ +name: Lints + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rustc nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + name: Formatting + with: + command: fmt + args: --all -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rustc stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: clippy + - uses: actions-rs/cargo@v1 + name: Clippy + with: + command: clippy + args: --workspace --all-features --all-targets -- -D warnings + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rustc nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rust-docs + - uses: actions-rs/cargo@v1 + name: Doc + with: + command: doc + args: --workspace --all-features diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f40b2cd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + testing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install latest stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Build & test + uses: actions-rs/cargo@v1 + with: + command: test + args: --workspace --all-features --no-fail-fast + wasm-testing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rust nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - uses: Swatinem/rust-cache@v2 + - uses: jetli/wasm-pack-action@v0.3.0 + - name: Add wasm32 target + run: rustup target add wasm32-unknown-unknown + - name: Test in headless Chrome + run: wasm-pack test --headless --chrome diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5e194c --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Generated by Cargo +# will have compiled files and executables +/target + +# These are backup files generated by rustfmt +**/*.rs.bk + +.idea +.vscode + +*.swp + +/dep_test + + +# Added by cargo +# +# already existing elements were commented out + +#/target diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..de9c4d6 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,27 @@ +edition = "2021" +version = "Two" + +max_width = 100 +array_width = 100 +attr_fn_like_width = 100 +fn_call_width = 100 + +format_code_in_doc_comments = true +fn_single_line = true +format_macro_matchers = true +format_macro_bodies = true +format_strings = true +merge_derives = false +overflow_delimited_expr = true +reorder_modules = false +use_field_init_shorthand = true +use_try_shorthand = true +wrap_comments = true +where_single_line = true +unstable_features = true +empty_item_single_line = true + +binop_separator = "Back" + +imports_granularity = "Module" +group_imports = "StdExternalCrate" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..784426e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,19 @@ +# Code of Conduct + +We do not apply any code of conduct expectations for contributors and +maintainers in their live and behaviour outside the scope of this project. +We believe that a restriction is the word of sin: free people write code, take +their decisions and act in a way they will, taking responsibility for the +consequences. + +Moreover, we will try to protect the freedom of speech of contributors, and +explicit distance from personal or public life of contributors, as long as +they behave in a civil and productive way when contributing and interacting +within the project, and will go to great lengths to not deny anyone +participation. + +Actions within the technical scope of the project (code quality, spamming etc), +as well as interaction with other maintainers and contributors of course is +a factor of the access to the project development and lifecycle. The decision in +these cases will be made by the project maintainers, with the right of veto or +overriding vote reserved for the original project author, Maxim Orlovsky. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..66acff9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,196 @@ +Contributing guidelines +======================= + +Contributions are very welcome. When contributing code, please follow these +simple guidelines. + +#### Table Of Contents +- [Contribution workflow](#contribution-workflow) + * [Proposing changes](#proposing-changes) + * [Preparing PRs](#preparing-prs) + * [Peer review](#peer-review) +- [Coding conventions](#coding-conventions) +- [Security](#security) +- [Testing](#testing) +- [Going further](#going-further) + +Overview +-------- + +* Before adding any code dependencies, check with the maintainers if this is okay. +* Write properly formatted comments: they should be English sentences, eg: + + // Return the current UNIX time. + +* Read the DCO and make sure all commits are signed off, using `git commit -s`. +* Follow the guidelines when proposing code changes (see below). +* Write properly formatted git commits (see below). +* Run the tests with `cargo test --workspace --all-features`. +* Make sure you run `rustfmt` on your code (see below details). +* Please don't file an issue to ask a question. Each repository - or + GitHub organization has a "Discussions" with Q&A section; please post your + questions there. You'll get faster results by using this channel. + +Contribution Workflow +--------------------- +The codebase is maintained using the "contributor workflow" where everyone +without exception contributes patch proposals using "pull requests". This +facilitates social contribution, easy testing and peer review. + +To contribute a patch, the workflow is a as follows: + +1. Fork Repository +2. Create topic branch +3. Commit patches + +In general commits should be atomic and diffs should be easy to read. For this +reason do not mix any formatting fixes or code moves with actual code changes. +Further, each commit, individually, should compile and pass tests, in order to +ensure git bisect and other automated tools function properly. + +When adding a new feature thought must be given to the long term technical debt. +Every new features should be covered by unit tests. + +When refactoring, structure your PR to make it easy to review and don't hesitate +to split it into multiple small, focused PRs. + +Commits should cover both the issue fixed and the solution's rationale. +These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in +mind. + +To facilitate communication with other contributors, the project is making use +of GitHub's "assignee" field. First check that no one is assigned and then +comment suggesting that you're working on it. If someone is already assigned, +don't hesitate to ask if the assigned party or previous commenters are still +working on it if it has been awhile. + +### Proposing changes + +When proposing changes via a pull-request or patch: + +* Isolate changes in separate commits to make the review process easier. +* Don't make unrelated changes, unless it happens to be an obvious improvement to + code you are touching anyway ("boyscout rule"). +* Rebase on `master` when needed. +* Keep your changesets small, specific and uncontroversial, so that they can be + merged more quickly. +* If the change is substantial or requires re-architecting certain parts of the + codebase, write a proposal in English first, and get consensus on that before + proposing the code changes. + +### Preparing PRs + +The main library development happens in the `master` branch. This branch must +always compile without errors (using Travis CI). All external contributions are +made within PRs into this branch. + +Prerequisites that a PR must satisfy for merging into the `master` branch: +* the tip of any PR branch must compile and pass unit tests with no errors, with + every feature combination (including compiling the fuzztests) on MSRV, stable + and nightly compilers (this is partially automated with CI, so the rule + is that we will not accept commits which do not pass GitHub CI); +* contain all necessary tests for the introduced functional (either as a part of + commits, or, more preferably, as separate commits, so that it's easy to + reorder them during review and check that the new tests fail without the new + code); +* contain all inline docs for newly introduced API and pass doc tests; +* be based on the recent `master` tip from the original repository at. + +NB: reviewers may run more complex test/CI scripts, thus, satisfying all the +requirements above is just a preliminary, but not necessary sufficient step for +getting the PR accepted as a valid candidate PR for the `master` branch. + +Additionally, to the `master` branch some repositories may have `develop` branch +for any experimental developments. This branch may not compile and should not be +used by any projects depending on the library. + +### Writing Git commit messages + +A properly formed git commit subject line should always be able to complete the +following sentence: + + If applied, this commit will _____ + +In addition, it should be capitalized and *must not* include a period. + +For example, the following message is well formed: + + Add support for .gif files + +While these ones are **not**: `Adding support for .gif files`, +`Added support for .gif files`. + +When it comes to formatting, here's a model git commit message[1]: + + Capitalized, short (50 chars or less) summary + + More detailed explanatory text, if necessary. Wrap it to about 72 + characters or so. In some contexts, the first line is treated as the + subject of an email and the rest of the text as the body. The blank + line separating the summary from the body is critical (unless you omit + the body entirely); tools like rebase can get confused if you run the + two together. + + Write your commit message in the imperative: "Fix bug" and not "Fixed bug" + or "Fixes bug." This convention matches up with commit messages generated + by commands like git merge and git revert. + + Further paragraphs come after blank lines. + + - Bullet points are okay, too. + + - Typically a hyphen or asterisk is used for the bullet, followed by a + single space, with blank lines in between, but conventions vary here. + + - Use a hanging indent. + +### Peer review + +Anyone may participate in peer review which is expressed by comments in the pull +request. Typically reviewers will review the code for obvious errors, as well as +test out the patch set and opine on the technical merits of the patch. PR should +be reviewed first on the conceptual level before focusing on code style or +grammar fixes. + +Coding Conventions +------------------ +Our CI enforces [clippy's](https://github.com/rust-lang/rust-clippy) +[default linting](https://rust-lang.github.io/rust-clippy/rust-1.52.0/index.html) +and [rustfmt](https://github.com/rust-lang/rustfmt) formatting defined by rules +in [.rustfmt.toml](./.rustfmt.toml). The linter should be run with current +stable rust compiler, while formatter requires nightly version due to the use of +unstable formatting parameters. + +If you use rustup, to lint locally you may run the following instructions: + +```console +rustup component add clippy +rustup component add fmt +cargo +stable clippy --workspace --all-features +cargo +nightly fmt --all +``` + +Security +-------- +Responsible disclosure of security vulnerabilities helps prevent user loss of +privacy. If you believe a vulnerability may affect other implementations, please +inform them. Guidelines for a responsible disclosure can be found in +[SECURITY.md](./SECURITY.md) file in the project root. + +Note that some of our projects are currently considered "pre-production". +Such projects can be distinguished by the absence of `SECURITY.md`. In such +cases there are no special handling of security issues; please simply open +an issue on GitHub. + +Going further +------------- +You may be interested in Jon Atack guide on +[How to review Bitcoin Core PRs][Review] and [How to make Bitcoin Core PRs][PR]. +While there are differences between the projects in terms of context and +maturity, many of the suggestions offered apply to this project. + +Overall, have fun :) + +[1]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[Review]: https://github.com/jonatack/bitcoin-development/blob/master/how-to-review-bitcoin-core-prs.md +[PR]: https://github.com/jonatack/bitcoin-development/blob/master/how-to-make-bitcoin-core-prs.md diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..43b1d68 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,276 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "amplify" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e711289a6cb28171b4f0e6c8019c69ff9476050508dc082167575d458ff74d0" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "wasm-bindgen", +] + +[[package]] +name = "amplify_derive" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759dcbfaf94d838367a86d493ec34ccc8aa6fe365cb7880d6bf89006de24d9c1" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify_num" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c009c5c4de814911b177e2ea59e4930bb918978ed3cce4900d846a6ceb0838" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "baid64" +version = "0.1.0" +dependencies = [ + "amplify", + "base64", + "mnemonic", + "sha2", +] + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "mnemonic" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b8f3a258db515d5e91a904ce4ae3f73e091149b90cadbdb93d210bee07f63b" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5147009 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "baid64" +version = "0.1.0" +description = "Easy-to-check Base64 encoding for identities" +keywords = ["base64", "encoding", "mnemonic"] +categories = ["encoding", "value-formatting", "command-line-utilities"] +readme = "README.md" +authors = ["Dr. Maxim Orlovsky "] +homepage = "https://github.com/UBIDECO/rust-baid64" +repository = "https://github.com/UBIDECO/rust-baid64" +rust-version = "1.66.0" +edition = "2021" +license = "Apache-2.0" + +[lib] + +[dependencies] +amplify = "4.6.0" +base64 = "0.22.0" +sha2 = "0.10.8" +mnemonic = "1.0.1" diff --git a/DCO b/DCO new file mode 100644 index 0000000..69175c9 --- /dev/null +++ b/DCO @@ -0,0 +1,28 @@ +Developer's Certificate of Origin 1.1 +Copyright © 2004, 2006 The Linux Foundation and its contributors. + +--- + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dea5499 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 LNP/BP Standards Association + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..a3af1e0 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,6 @@ +Maxim Orlovsky +--------------- +- GitHub: [@dr-orlovsky](https://github.com/dr-orlovsky) +- GPG: `EAE730CEC0C663763F028A5860094BAF18A26EC9` +- SSH: `BoSGFzbyOKC7Jm28MJElFboGepihCpHop60nS8OoG/A` +- EMail: [dr@orlovsky.ch](mailto:dr@orlovsky.ch) diff --git a/MANIFEST.yml b/MANIFEST.yml new file mode 100644 index 0000000..aa92b3a --- /dev/null +++ b/MANIFEST.yml @@ -0,0 +1,14 @@ +Name: baid64 +Type: Library +Kind: Free software +License: Apache-2.0 +Language: Rust +Compiler: 1.75 +Author: Maxim Orlovsky +Maintained: UBIDECO Institute, Switzerland +Maintainers: + Maxim Orlovsky: + GitHub: @dr-orlovsky + GPG: EAE730CEC0C663763F028A5860094BAF18A26EC9 + SSH: BoSGFzbyOKC7Jm28MJElFboGepihCpHop60nS8OoG/A + EMail: dr@orlovsky.ch diff --git a/README.md b/README.md new file mode 100644 index 0000000..e37798f --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Easy-to-check Base64 encoding for identities + +![Build](https://github.com/UBIDECO/rust-baid64/workflows/Build/badge.svg) +![Tests](https://github.com/UBIDECO/rust-baid64/workflows/Tests/badge.svg) +![Lints](https://github.com/UBIDECO/rust-baid64/workflows/Lints/badge.svg) + +[![crates.io](https://img.shields.io/crates/v/baid64)](https://crates.io/crates/baid64) +[![Docs](https://docs.rs/baid64/badge.svg)](https://docs.rs/baid64) +[![Apache-2 licensed](https://img.shields.io/crates/l/baid64)](./LICENSE) + +Base64 encoding extended with HRP and mnemonic checksum information. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..22101c1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,59 @@ +# Security + +We take the security of our software products and services seriously, which +includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any of our repository +that meets [definition of a security vulnerability][definition], please report +it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the repository maintainers by sending a **GPG +encrypted e-mail** to _all maintainers of a specific repo_ using their GPG keys. + +A list of repository maintainers and their keys and e-mail addresses are +provided inside MAINTANERS.md file and MANIFEST.yml, with the latter also +included in the README.md as a manifest block, which looks in the following way: + +```yaml +Name: +... +Maintained: +Maintainers: + : + GPG: + EMail: + : + ... +``` + +You should receive a response within 72 hours. If for some reason you do not, +please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can +provide) to help us better understand the nature and scope of the possible +issue: + +* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +We follow the principle of [Coordinated Vulnerability Disclosure][disclosure]. + +[definition]: https://aka.ms/opensource/security/definition +[disclosure]: https://aka.ms/opensource/security/cvd diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..240d731 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,19 @@ +codecov: + require_ci_to_pass: no + +coverage: + precision: 1 + round: nearest + range: "0...95" + status: + project: + default: + target: 75% + threshold: 1% + branches: + - master + patch: + default: + target: 60% + threshold: 1% + only_pulls: true diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..51a392a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,252 @@ +// Base64 encoding extended with HRP and mnemonic checksum information +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2024 UBIDECO Institute, Switzerland. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate amplify; + +use std::fmt::{self, Display, Formatter}; + +use base64::Engine; +use sha2::Digest; + +pub const HRI_MAX_LEN: usize = 16; + +pub const BAID64_ALPHABET: &str = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$"; + +fn check(hri: &'static str, payload: [u8; LEN]) -> [u8; 4] { + let key = sha2::Sha256::digest(hri.as_bytes()); + let mut sha = sha2::Sha256::new_with_prefix(key); + sha.update(&payload); + let sha = sha.finalize(); + [sha[0], sha[1], sha[1], sha[2]] +} + +pub trait DisplayBaid64 { + const HRI: &'static str; + const CHUNKING: bool; + const PREFIX: bool; + const EMBED_CHECKSUM: bool; + const MNEMONIC: bool; + + fn to_baid64_payload(&self) -> [u8; LEN]; + fn to_baid64_string(&self) -> String { self.display_baid64().to_string() } + fn to_baid64_mnemonic(&self) -> String { self.display_baid64().mnemonic } + fn display_baid64(&self) -> Baid64Display { + Baid64Display::with( + Self::HRI, + self.to_baid64_payload(), + Self::CHUNKING, + Self::PREFIX, + Self::MNEMONIC, + Self::EMBED_CHECKSUM, + ) + } + fn fmt_baid64(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(&self.display_baid64(), f) + } +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum Baid64ParseError { + /// invalid human-readable prefix in {0} ({1} is expected). + InvalidHri(String, &'static str), + + /// invalid length of identifier {0}. + InvalidLen(String), + + /// invalid checksum value in {0} - expected {1:#x} while found + /// {2:#x}. + InvalidChecksum(String, u32, u32), + + /// invalid length of mnemonic in {0}. + InvalidMnemonicLen(String), + + #[from] + #[display(inner)] + InvalidMnemonic(mnemonic::Error), + + #[from] + #[display(inner)] + Base64(base64::DecodeError), +} + +pub trait FromBaid64Str: DisplayBaid64 + From<[u8; LEN]> { + fn from_baid64_str(mut s: &str) -> Result { + let orig = s; + + use base64::alphabet::Alphabet; + use base64::engine::general_purpose::NO_PAD; + use base64::engine::GeneralPurpose; + + let mut checksum = None; + + if let Some((hri, rest)) = s.rsplit_once(':') { + if hri != Self::HRI { + return Err(Baid64ParseError::InvalidHri(orig.to_owned(), Self::HRI)); + } + s = rest; + } + + if let Some((rest, sfx)) = s.split_once('#') { + let mut mnemo = Vec::::with_capacity(4); + mnemonic::decode(sfx, &mut mnemo)?; + if mnemo.len() != 4 { + return Err(Baid64ParseError::InvalidMnemonicLen(orig.to_string())); + } + checksum = Some([mnemo[0], mnemo[1], mnemo[2], mnemo[3]]); + s = rest; + } + + let s = if s.contains('-') { + s.replace('-', "") + } else { + s.to_owned() + }; + + let alphabet = Alphabet::new(BAID64_ALPHABET).expect("invalid Baid64 alphabet"); + let engine = GeneralPurpose::new(&alphabet, NO_PAD); + let data = engine.decode(s)?; + + if data.len() != LEN && data.len() != LEN + 4 { + return Err(Baid64ParseError::InvalidLen(orig.to_owned())); + } + let mut payload = [0u8; LEN]; + payload.copy_from_slice(&data[..LEN]); + if data.len() == LEN + 4 { + checksum = Some([data[LEN], data[LEN + 1], data[LEN + 2], data[LEN + 3]]); + } + + let ck = check(Self::HRI, payload); + if matches!(checksum, Some(c) if c != ck) { + return Err(Baid64ParseError::InvalidChecksum( + orig.to_owned(), + u32::from_le_bytes(ck), + u32::from_le_bytes(checksum.unwrap()), + )); + } + + Ok(Self::from(payload)) + } +} + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +pub struct Baid64Display { + hri: &'static str, + chunking: bool, + mnemonic: String, + prefix: bool, + suffix: bool, + embed_checksum: bool, + checksum: [u8; 4], + payload: [u8; LEN], +} + +impl Baid64Display { + pub fn with( + hri: &'static str, + payload: [u8; LEN], + chunking: bool, + prefix: bool, + suffix: bool, + embed_checksum: bool, + ) -> Self { + debug_assert!(hri.len() <= HRI_MAX_LEN, "HRI is too long"); + debug_assert!(LEN > HRI_MAX_LEN, "Baid64 id must be at least 9 bytes"); + + let checksum = check(hri, payload); + let mnemonic = mnemonic::to_string(checksum); + + Self { + hri, + chunking, + mnemonic, + prefix, + suffix, + embed_checksum, + checksum, + payload, + } + } + + pub fn new(hri: &'static str, payload: [u8; LEN]) -> Self { + Self::with(hri, payload, false, false, false, false) + } + pub const fn use_hri(mut self) -> Self { + self.prefix = true; + self + } + pub const fn use_chunking(mut self) -> Self { + self.chunking = true; + self + } + pub const fn use_mnemonic(mut self) -> Self { + self.suffix = true; + self + } + pub const fn embed_checksum(mut self) -> Self { + self.embed_checksum = true; + self + } + + pub const fn human_identifier(&self) -> &'static str { self.hri } + + pub fn mnemonic(&self) -> &str { self.mnemonic.as_str() } + pub const fn checksum(&self) -> [u8; 4] { self.checksum } +} + +impl Display for Baid64Display { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use base64::alphabet::Alphabet; + use base64::engine::general_purpose::NO_PAD; + use base64::engine::GeneralPurpose; + + if (self.prefix && !f.sign_minus()) || (!self.prefix && f.sign_minus()) { + write!(f, "{}:", self.hri)?; + } + + let alphabet = Alphabet::new(BAID64_ALPHABET).expect("invalid Baid64 alphabet"); + let engine = GeneralPurpose::new(&alphabet, NO_PAD); + + let mut payload = self.payload.to_vec(); + if self.embed_checksum { + payload.extend(self.checksum); + } + let s = engine.encode(payload); + + if self.chunking { + let bytes = s.as_bytes(); + f.write_str(&String::from_utf8_lossy(&bytes[..8]))?; + for chunk in bytes[8..].chunks(7) { + write!(f, "-{}", &String::from_utf8_lossy(chunk))?; + } + } else { + f.write_str(&s)?; + } + + if (self.suffix && !f.alternate()) || (!self.suffix && f.alternate()) { + write!(f, "#{}", self.mnemonic)?; + } + + Ok(()) + } +}