├── .github └── workflows │ ├── ci_checks.yaml │ ├── ci_integration.yaml │ ├── python-scripts.yml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bindings ├── wallet-c │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── cbindgen.toml │ ├── check_header.sh │ ├── regen_header.sh │ ├── src │ │ ├── lib.rs │ │ ├── settings.rs │ │ └── time.rs │ └── wallet.h ├── wallet-cordova │ ├── .eslintrc.yml │ ├── .gitattributes │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── package.json │ ├── plugin.xml │ ├── scripts │ │ ├── build_ios.py │ │ ├── build_jni.py │ │ ├── copy_jni_definitions.py │ │ ├── directories.py │ │ └── test.py │ ├── src │ │ ├── android │ │ │ ├── WalletPlugin.kt │ │ │ └── jna-compile.gradle │ │ ├── ios │ │ │ ├── .clang-format │ │ │ ├── WalletPlugin.h │ │ │ └── WalletPlugin.m │ │ └── windows │ │ │ └── Wallet.js │ ├── tests │ │ ├── .eslintrc.yml │ │ ├── .gitignore │ │ ├── babel.config.json │ │ ├── package.json │ │ ├── plugin.xml │ │ └── src │ │ │ ├── main.js │ │ │ ├── manual_tests.js │ │ │ └── utils.js │ └── www │ │ └── wallet.js ├── wallet-core │ ├── Cargo.toml │ └── src │ │ ├── c │ │ ├── fragment.rs │ │ ├── macros.rs │ │ ├── mod.rs │ │ ├── settings.rs │ │ ├── time.rs │ │ └── vote.rs │ │ ├── conversion.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── vote.rs │ │ └── wallet.rs ├── wallet-js │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs └── wallet-uniffi │ ├── Cargo.toml │ ├── build.rs │ ├── codegen │ └── kotlin │ │ └── com │ │ └── iohk │ │ └── jormungandr_wallet │ │ └── jormungandr_wallet.kt │ ├── gen_bindings.sh │ ├── src │ ├── lib.rs │ └── lib.udl │ └── uniffi.toml ├── bip39 ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── bip39_chinese_simplified.txt │ ├── bip39_chinese_traditional.txt │ ├── bip39_english.txt │ ├── bip39_french.txt │ ├── bip39_italian.txt │ ├── bip39_japanese.txt │ ├── bip39_korean.txt │ ├── bip39_spanish.txt │ ├── bits.rs │ ├── dictionary.rs │ ├── entropy.rs │ ├── error.rs │ ├── lib.rs │ ├── mnemonic.rs │ ├── seed.rs │ ├── test_vectors │ ├── bip39_english.txt │ └── bip39_japanese.txt │ └── types.rs ├── chain-path-derivation ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── bip44.rs │ ├── derivation.rs │ ├── derivation_path.rs │ ├── lib.rs │ └── rindex.rs ├── doc ├── CRYPTO.md └── EME.md ├── hdkeygen ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── account.rs │ ├── bip44.rs │ ├── key.rs │ ├── lib.rs │ └── rindex │ ├── hdpayload.rs │ └── mod.rs ├── jsdoc.json ├── symmetric-cipher ├── Cargo.toml └── src │ └── lib.rs ├── test-vectors ├── block0 ├── block0.json ├── free_keys │ ├── address1.pub │ ├── key1.prv │ ├── key1.pub │ └── keys.json ├── genesis.yaml ├── leader.prv ├── leader.pub ├── leaders │ └── leader1.prv ├── rebuild_genesis_data.py └── vote │ ├── input.yaml │ └── vote_plan.cert └── wallet ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── src ├── account.rs ├── blockchain.rs ├── lib.rs ├── password.rs ├── scheme │ └── mod.rs ├── states.rs ├── time.rs └── transaction │ ├── builder.rs │ ├── mod.rs │ ├── strategy.rs │ └── witness_builder.rs └── tests ├── account.rs └── utils └── mod.rs /.github/workflows/ci_checks.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | 7 | name: C.I. Checks 8 | 9 | jobs: 10 | update-deps: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - id: cargo-deps 16 | name: Cache cargo dependencies 17 | uses: actions/cache@v2 18 | with: 19 | path: | 20 | ~/.cargo/registry/index 21 | ~/.cargo/registry/cache 22 | ~/.cargo/git/db 23 | key: cargo-deps-${{ hashFiles('**/Cargo.lock') }} 24 | 25 | - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }} 26 | id: ls-crates-io-index 27 | name: Get head commit hash of crates.io registry index 28 | shell: bash 29 | run: | 30 | commit=$( 31 | git ls-remote --heads https://github.com/rust-lang/crates.io-index.git master | 32 | cut -f 1 33 | ) 34 | echo "::set-output name=head::$commit" 35 | - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }} 36 | name: Cache cargo registry index 37 | uses: actions/cache@v2 38 | with: 39 | path: ~/.cargo/registry/index 40 | key: cargo-index-${{ steps.ls-crates-io-index.outputs.head }} 41 | restore-keys: cargo-index- 42 | 43 | - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }} 44 | name: Fetch dependencies and update registry index 45 | run: cargo fetch --locked 46 | 47 | check: 48 | name: Check 49 | needs: update-deps 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Checkout sources 53 | uses: actions/checkout@v1 54 | with: 55 | submodules: true 56 | 57 | - name: Cache cargo dependencies 58 | uses: actions/cache@v2 59 | with: 60 | path: | 61 | ~/.cargo/registry/index 62 | ~/.cargo/registry/cache 63 | ~/.cargo/git/db 64 | key: cargo-deps-${{ hashFiles('**/Cargo.lock') }} 65 | 66 | - name: Install uniffi 67 | uses: actions-rs/install@v0.1 68 | with: 69 | crate: uniffi_bindgen 70 | version: 0.16.0 71 | use-tool-cache: true 72 | 73 | - name: Install stable toolchain 74 | uses: actions-rs/toolchain@v1 75 | with: 76 | profile: minimal 77 | toolchain: stable 78 | override: true 79 | 80 | - name: Run cargo check 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: check 84 | 85 | test: 86 | name: Test Suite 87 | needs: update-deps 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Checkout sources 91 | uses: actions/checkout@v1 92 | with: 93 | submodules: true 94 | 95 | - name: Restore cargo dependencies 96 | uses: actions/cache@v2 97 | with: 98 | path: | 99 | ~/.cargo/registry/index 100 | ~/.cargo/registry/cache 101 | ~/.cargo/git/db 102 | key: cargo-deps-${{ hashFiles('**/Cargo.lock') }} 103 | 104 | - name: Install uniffi 105 | uses: actions-rs/install@v0.1 106 | with: 107 | crate: uniffi_bindgen 108 | version: 0.16.0 109 | use-tool-cache: true 110 | 111 | - name: Install stable toolchain 112 | uses: actions-rs/toolchain@v1 113 | with: 114 | profile: minimal 115 | toolchain: stable 116 | override: true 117 | 118 | - name: Run cargo test 119 | uses: actions-rs/cargo@v1 120 | continue-on-error: false 121 | with: 122 | command: test 123 | 124 | lints: 125 | name: Lints 126 | needs: update-deps 127 | runs-on: ubuntu-latest 128 | steps: 129 | - name: Checkout sources 130 | uses: actions/checkout@v1 131 | with: 132 | submodules: true 133 | 134 | - name: Install stable toolchain 135 | uses: actions-rs/toolchain@v1 136 | with: 137 | profile: minimal 138 | toolchain: stable 139 | override: true 140 | components: rustfmt, clippy 141 | 142 | - name: Run cargo fmt 143 | uses: actions-rs/cargo@v1 144 | continue-on-error: false 145 | with: 146 | command: fmt 147 | args: --all -- --check 148 | 149 | - name: Restore cargo dependencies 150 | uses: actions/cache@v2 151 | with: 152 | path: | 153 | ~/.cargo/registry/index 154 | ~/.cargo/registry/cache 155 | ~/.cargo/git/db 156 | key: cargo-deps-${{ hashFiles('**/Cargo.lock') }} 157 | 158 | - name: Run cargo clippy 159 | uses: actions-rs/cargo@v1 160 | with: 161 | command: clippy 162 | args: --all-features --all-targets -- -D warnings 163 | 164 | test-wasm: 165 | name: Wasm-pack test 166 | needs: update-deps 167 | runs-on: ubuntu-latest 168 | steps: 169 | - name: Checkout sources 170 | uses: actions/checkout@v2 171 | with: 172 | submodules: true 173 | 174 | - name: Restore cargo dependencies 175 | uses: actions/cache@v2 176 | with: 177 | path: | 178 | ~/.cargo/registry/index 179 | ~/.cargo/registry/cache 180 | ~/.cargo/git/db 181 | key: cargo-deps-${{ hashFiles('**/Cargo.lock') }} 182 | 183 | - name: Install wasm pack 184 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 185 | 186 | - name: Test in chrome 187 | run: wasm-pack test --headless --chrome ./bindings/wallet-js 188 | 189 | - name: Test in firefox 190 | run: wasm-pack test --headless --firefox ./bindings/wallet-js 191 | 192 | eslint-cordova-plugin: 193 | name: check eslint for cordova plugin 194 | runs-on: ubuntu-latest 195 | steps: 196 | - name: Checkout sources 197 | uses: actions/checkout@v2 198 | with: 199 | submodules: true 200 | 201 | - name: setup node 202 | uses: actions/setup-node@v1 203 | with: 204 | node-version: 12.x 205 | 206 | - name: install dependencies 207 | working-directory: ./bindings/wallet-cordova 208 | run: npm install 209 | 210 | - name: eslint 211 | working-directory: ./bindings/wallet-cordova 212 | run: npm run eslint 213 | 214 | check-c-header: 215 | name: check c-header is up-to-date 216 | runs-on: ubuntu-latest 217 | steps: 218 | - name: Checkout sources 219 | uses: actions/checkout@v1 220 | with: 221 | submodules: true 222 | 223 | - name: Install stable toolchain 224 | uses: actions-rs/toolchain@v1 225 | with: 226 | profile: minimal 227 | toolchain: stable 228 | override: true 229 | components: rustfmt, clippy 230 | 231 | - name: Install cbindgen 232 | uses: actions-rs/install@v0.1 233 | with: 234 | crate: cbindgen 235 | use-tool-cache: true 236 | 237 | - name: run check script 238 | run: bash bindings/wallet-c/check_header.sh 239 | -------------------------------------------------------------------------------- /.github/workflows/python-scripts.yml: -------------------------------------------------------------------------------- 1 | name: Python scripts linters 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install linters 11 | run: pip3 install black 12 | 13 | - uses: actions/checkout@v2 14 | 15 | - name: Check formatting (black) 16 | run: black . --check 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | 3 | /target 4 | **/*.rs.bk 5 | **/__pycache__ 6 | bindings/wallet-uniffi/codegen 7 | **/*node_modules 8 | **/*package-lock.json 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/chain-wallet-libs/b46e4740823719c83af688e6e7c031a792978c03/.gitmodules -------------------------------------------------------------------------------- /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 | ## Unreleased 8 | 9 | - *breaking change*: wallet_spending_counter replaced by wallet_spending_counters 10 | - *breaking change*: wallet_set_state now takes an array of spending counters 11 | - *breaking change*: wallet_vote now takes a lane as an argument 12 | - *breaking change*: wallet_import_keys now takes a single argument with the 13 | account key. 14 | 15 | ## [0.8.0-alpha1] 16 | 17 | - Remove the keygen crate due to lack of need for it. 18 | - Remove wallet-jni and use wallet-uniffi instead. 19 | 20 | ## [0.7.0-pre4] 2021-09-02 21 | 22 | - Bech32 HRP for encryption key now is set by chain-libs 23 | 24 | ## [0.7.0-pre3] 2021-08-27 25 | 26 | - Fix compilation issue in iOS for cordova 27 | 28 | ## [0.7.0-pre2] 2021-08-26 29 | 30 | ### Changed 31 | 32 | - Improved documentation 33 | - Recover from mnemonics can now take a password 34 | 35 | #### Wallet-js 36 | 37 | - *deprecated*: VotePlanId::new_from_bytes 38 | - *deprecated*: Ed25519Signature::from_binary 39 | - *deprecated*: FragmentId::new_from_bytes 40 | 41 | ### Added 42 | 43 | #### Wallet-js 44 | 45 | - VotePlanId::from_bytes 46 | - Ed25519Signature::from_bytes 47 | - FragmentId::from_bytes 48 | 49 | ## [0.7.0-pre1] 2021-08-25 50 | 51 | ### Changed 52 | 53 | - *breaking change*: `wallet_vote_cast` and `wallet_convert` now require the TTL argument. 54 | - *breaking change*: `settings_new` takes new arguments needed for managing time. 55 | 56 | ### Added 57 | 58 | - max_expiration_date 59 | - block_date_from_system_time 60 | 61 | ## [0.6.0-pre2] 2021-05-14 62 | 63 | ### Added 64 | 65 | #### cordova - c - java 66 | 67 | - spending_counter 68 | - settings_new 69 | - settings_get 70 | - fragment_from_raw 71 | - fragment_id 72 | - fragment_delete 73 | 74 | ### Changed 75 | 76 | pending_transactions now returns transactions in order relative to the same 77 | wallet type instead of arbitrary order. First starting with deadalus/yoroi/free 78 | utxo keys (those are all exclusive) in order of creating, and then the account 79 | transactions, also in order of creation (and signing). 80 | 81 | ## [0.6.0-pre1] 82 | 83 | ### Changed 84 | 85 | #### cordova - c - java - wallet-js 86 | 87 | - *breaking change*: Take a bech32 string in proposal_new_private instead of raw bytes 88 | 89 | ## [0.5.0] - 2021-01-05 90 | 91 | ## [0.5.0-pre9] - 2020-12-17 92 | 93 | - Add explicit link to libdl.so in release process 94 | 95 | ## [0.5.0-pre8] - 2020-12-04 96 | 97 | #### wallet-js 98 | 99 | - Remove `symmetric_encrypt` (moved to *keygen*) 100 | - Remove `symmetric_decrypt` (moved to *keygen*) 101 | - Remove `bech32_decode_to_bytes` (moved to *keygen*) 102 | 103 | #### keygen 104 | 105 | Add specific package used for daedalus catalyst 106 | 107 | *Features* 108 | 109 | - Generate Ed25519Extended private/public keys. 110 | - Encrypt keys with pin. 111 | - Decode bech32 strings. 112 | 113 | ## [0.5.0-pre7] - 2020-11-16 114 | 115 | #### wallet-js 116 | 117 | - Expose function to decode bech32 to byte array 118 | 119 | ## [0.5.0-pre6] - 2020-11-06 120 | 121 | ### Changed 122 | 123 | #### wallet-js 124 | 125 | - Put the package into scope as @iohk-jormungandr/wallet-js 126 | - Change the output file prefix to generate files named `wallet.js` etc. 127 | 128 | ## [0.5.0-pre5] - 2020-10-30 129 | 130 | ### Added 131 | 132 | #### wallet-js 133 | 134 | - Add Ed25519Extended generation from seed 135 | - Key signing and verification. 136 | - Add the other kinds of private keys: 137 | - Ed25519 138 | 139 | #### Cordova-android | Java | C | Electron/Browser 140 | - vote_proposal_new_public 141 | - vote_proposal_new_private 142 | 143 | #### Cordova-electron/browser 144 | - add confirm/get pending transactions 145 | - add import keys (free keys) 146 | - pin decryption (symmetric cipher decrypt) 147 | 148 | ### Deprecated 149 | 150 | - proposal_new: In favour of the specific functions for each case. This 151 | function takes an enum, which currently only can be used to cast public 152 | votes (the internal function still uses rust enums, this is only for non-rust 153 | apis). 154 | 155 | ## [0.5.0-pre4] - 2020-10-13 156 | 157 | ### Added 158 | 159 | #### wallet-js 160 | 161 | - Key pair generation support. 162 | - Symmetric encryption and decryption support. 163 | 164 | ## [0.5.0-pre3] - 2020-09-04 165 | 166 | ### Fixed 167 | 168 | - Decryption function now returns an error if the authentication fails. 169 | 170 | ### Added 171 | 172 | #### wallet-cordova 173 | 174 | - iOS support for the import key and decryption functions. 175 | 176 | ## [0.5.0-pre2] - 2020-08-18 177 | 178 | ### Fixed 179 | - Wrong secret key type was used when recovering from mnemonics. 180 | 181 | ## [0.5.0-pre1] - 2020-08-18 182 | ### Added 183 | 184 | - New utxo store. 185 | - Allow recovering from single free utxo keys. 186 | - Custom symmetric encryption/decryption module. 187 | 188 | ## [0.4.0] - 2020-07-08 189 | 190 | ## [0.4.0-pre3] - 2020-06-22 191 | 192 | ## [0.4.0-pre2] - 2020-06-04 193 | 194 | ## [0.4.0-pre1] - 2020-06-03 195 | 196 | ## [0.3.1] - 2020-22-05 197 | 198 | ## [0.3.0] - 2020-05-01 199 | 200 | ## [0.2.0] - 2020-04-15 201 | 202 | ## [0.1.0] - 2020-04-10 203 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "bip39", 4 | "chain-path-derivation", 5 | "hdkeygen", 6 | "wallet", 7 | "symmetric-cipher", 8 | "bindings/wallet-c", 9 | "bindings/wallet-js", 10 | "bindings/wallet-uniffi", 11 | ] 12 | 13 | [profile.release] 14 | lto = "yes" 15 | strip = "symbols" 16 | 17 | [patch.crates-io] 18 | cryptoxide = { git = "https://github.com/typed-io/cryptoxide.git" } 19 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2019 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chain-wallet-libs 2 | 3 | [![MIT licensed][mit-badge]][mit-url] 4 | [![C.I. Integration][ci-integration-badge]][ci-integration-url] 5 | [![C.I. Checks][ci-check-badge]][ci-check-url] 6 | [![Release][release-badge]][release-url] 7 | 8 | [mit-badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue 9 | [mit-url]: LICENSE 10 | [ci-integration-badge]: https://github.com/input-output-hk/chain-wallet-libs/workflows/C.I.%20Integration/badge.svg 11 | [ci-integration-url]: https://github.com/input-output-hk/chain-wallet-libs/actions?query=workflow%3A%22C.I.+Integration%22 12 | [ci-check-badge]: https://github.com/input-output-hk/chain-wallet-libs/workflows/C.I.%20Checks/badge.svg 13 | [ci-check-url]: https://github.com/input-output-hk/chain-wallet-libs/actions?query=workflow%3A%22C.I.+Checks%22 14 | [release-badge]: https://github.com/input-output-hk/chain-wallet-libs/workflows/Release/badge.svg 15 | [release-url]: https://github.com/input-output-hk/chain-wallet-libs/actions?query=workflow%3ARelease 16 | 17 | Chain Wallet libs is a set of library, written in [rust-lang], to use to build application for [Jörmungandr]. 18 | 19 | ## released binaries 20 | 21 | Currently we provide support for many platforms of the high level wallet library. 22 | 23 | **Releases can be found there: [link][release-latest]** 24 | 25 | ### Android 26 | 27 | | Target | released binaries | 28 | | ------------------------- | :---------------: | 29 | | `aarch64-linux-android` | ✓ | 30 | | `arm-linux-androideabi` | ✓ | 31 | | `armv7-linux-androideabi` | ✓ | 32 | | `i686-linux-android` | ✓ | 33 | | `x86_64-linux-android` | ✓ | 34 | 35 | This includes bindings for Android Kotlin already packaged in a AAR package. 36 | 37 | ### Cordova plugin 38 | 39 | | Platform | supported | 40 | | -------- | :-------: | 41 | | android | ✓ | 42 | | ios | ✓ | 43 | 44 | ### iOS 45 | 46 | | Target | released binaries | 47 | | ------------------- | :---------------: | 48 | | `aarch64-apple-ios` | ✓ | 49 | | `x86_64-apple-ios` | ✓ | 50 | 51 | _Swift package in development..._ 52 | 53 | ### Linux 54 | 55 | | Target | released binaries | 56 | | ---------------------------------- | :---------------: | 57 | | `aarch64-unknown-linux-gnu` | ✓ | 58 | | `arm-unknown-linux-gnueabi` | ✓ | 59 | | `armv7-unknown-linux-gnueabihf` | ✓ | 60 | | `mips64el-unknown-linux-gnueabi64` | ✓ | 61 | | `powerpc64el-unknown-linux-gnu` | ✓ | 62 | | `x86_64-unknown-linux-gnu` | ✓ | 63 | | `x86_64-unknown-linux-musl` | ✓ | 64 | 65 | ### MacOS 66 | 67 | | Target | released binaries | 68 | | --------------------- | :---------------: | 69 | | `x86_64-apple-darwin` | ✓ | 70 | 71 | ### Wasm (and JavaScript) 72 | 73 | | Target | released binaries | 74 | | ------------------------ | :---------------: | 75 | | `wasm32-unknown-unknown` | ✓ | 76 | 77 | This include Javascript generated binaries (with typescript annotations) 78 | for webjs and nodejs. 79 | 80 | ### Windows 81 | 82 | | Target | released binaries | 83 | | ------------------------ | :---------------: | 84 | | `x86_64-pc-windows-gnu` | ✓ | 85 | | `x86_64-pc-windows-msvc` | ✓ | 86 | 87 | # Development 88 | 89 | You can find the main rust libraries at the top level of this repository. These 90 | are the core elements and offer prime support for all the different `bindings` 91 | implemented in the `bindings` directory. 92 | 93 | For the Cordova plugin, check out the [readme in the plugin's 94 | directory](bindings/wallet-cordova/README.md). 95 | 96 | ## Code formatting 97 | 98 | In order to avoid long lasting discussions and arguments about how code should 99 | be formatted for better readability all must be formatted with `rustfmt`. 100 | 101 | ## Clippy 102 | 103 | Cargo clippy is ran on this repository at every PRs. This will come in handy to 104 | prevent some readability issues but also potential mistakes in the C bindings 105 | when manipulating raw pointers. 106 | 107 | ## Documentation 108 | 109 | - [Wallet Cryptography and Encoding](doc/CRYPTO.md) 110 | - [Enhanced Mnemonic Encoding (EME)](doc/EME.md) 111 | - [Cordova plugin](bindings/wallet-cordova/README.md) 112 | 113 | [rust-lang]: https://www.rust-lang.org/ 114 | [Jörmungandr]: https://input-output-hk.github.io/jormungandr 115 | [release-latest]: https://github.com/input-output-hk/chain-wallet-libs/releases/latest 116 | -------------------------------------------------------------------------------- /bindings/wallet-c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jormungandrwallet" 3 | version = "0.7.0-pre4" 4 | authors = ["Nicolas Di Prima ", "Vincent Hanquez "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [lib] 9 | crate-type = ["staticlib", "cdylib"] 10 | 11 | [dependencies] 12 | bip39 = { path = "../../bip39" } 13 | wallet = { path = "../../wallet" } 14 | chain-ser = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 15 | chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 16 | chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 17 | wallet-core = { path = "../wallet-core"} 18 | -------------------------------------------------------------------------------- /bindings/wallet-c/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /bindings/wallet-c/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bindings/wallet-c/cbindgen.toml: -------------------------------------------------------------------------------- 1 | # This is a template cbindgen.toml file with all of the default values. 2 | # Some values are commented out because their absence is the real default. 3 | # 4 | # See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml 5 | # for detailed documentation of every option here. 6 | 7 | language = "C" 8 | 9 | ############## Options for Wrapping the Contents of the Header ################# 10 | 11 | header = """/** 12 | * Wallet for Jörmungandr blockchain 13 | * 14 | * Provide support for recovering funds from both Yoroi and Daedalus wallets. 15 | * 16 | * Copyright 2020, Input Output HK Ltd 17 | * Licensed with: MIT OR Apache-2.0 18 | */""" 19 | include_guard = "IOHK_CHAIN_WALLET_LIBC_" 20 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 21 | include_version = true 22 | 23 | ############################ Code Style Options ################################ 24 | 25 | braces = "NextLine" 26 | line_length = 80 27 | tab_width = 2 28 | documentation_style = "doxy" 29 | 30 | ############################# Codegen Options ################################## 31 | 32 | style = "both" 33 | 34 | [export] 35 | item_types = [] 36 | renaming_overrides_prefixing = false 37 | 38 | [fn] 39 | rename_args = "None" 40 | args = "auto" 41 | sort_by = "Name" 42 | 43 | [struct] 44 | rename_fields = "None" 45 | 46 | [enum] 47 | rename_variants = "None" 48 | add_sentinel = false 49 | prefix_with_name = true 50 | derive_helper_methods = false 51 | derive_const_casts = false 52 | derive_mut_casts = false 53 | derive_tagged_enum_destructor = false 54 | derive_tagged_enum_copy_constructor = false 55 | enum_class = true 56 | private_default_tagged_enum_constructor = false 57 | 58 | [const] 59 | allow_static_const = true 60 | allow_constexpr = false 61 | 62 | [macro_expansion] 63 | bitflags = false 64 | 65 | ############## Options for How Your Rust library Should Be Parsed ############## 66 | 67 | [parse] 68 | parse_deps = true 69 | include = ["wallet-core"] 70 | exclude = [] 71 | clean = false 72 | extra_bindings = [] 73 | 74 | [parse.expand] 75 | crates = [] 76 | all_features = false 77 | default_features = true 78 | features = [] -------------------------------------------------------------------------------- /bindings/wallet-c/check_header.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | # the find command is used mostly so this work from the root directory 5 | # and from the wallet-c directory 6 | 7 | EXCLUDE='-not -path "*/target" -not -path "*/.git" -not -path "*/node_modules"' 8 | CONFIG=$(find . -name "cbindgen.toml" $EXCLUDE) 9 | HEADER_FILEPATH=$(find . -name "wallet.h" $EXCLUDE) 10 | 11 | # remove this line, as it contains the cbindgen version, causing PR's to fail 12 | # when cbindgen releases a new version. 13 | 14 | strip_line_with_version() { 15 | sed -i '/Generated with cbindgen/d' $1 16 | } 17 | 18 | ACTUAL_HEADER=$(mktemp) 19 | GENERATED_HEADER=$(mktemp) 20 | 21 | cat $HEADER_FILEPATH >$ACTUAL_HEADER 22 | cbindgen --config $CONFIG --crate jormungandrwallet >$GENERATED_HEADER 23 | 24 | strip_line_with_version $ACTUAL_HEADER 25 | strip_line_with_version $GENERATED_HEADER 26 | 27 | diff $GENERATED_HEADER $ACTUAL_HEADER 28 | 29 | rm "$ACTUAL_HEADER" 30 | rm "$GENERATED_HEADER" 31 | -------------------------------------------------------------------------------- /bindings/wallet-c/regen_header.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cbindgen --config cbindgen.toml --crate jormungandrwallet --output wallet.h . 4 | -------------------------------------------------------------------------------- /bindings/wallet-c/src/settings.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorPtr, SettingsPtr}; 2 | use wallet_core::c::settings::{ 3 | settings_block0_hash, settings_discrimination, settings_fees, settings_new, Discrimination, 4 | LinearFee, SettingsInit, 5 | }; 6 | 7 | /// # Safety 8 | /// 9 | /// settings_out must point to valid writable memory 10 | /// block_0_hash is assumed to point to 32 bytes of readable memory 11 | #[no_mangle] 12 | pub unsafe extern "C" fn iohk_jormungandr_wallet_settings_new( 13 | settings_init: SettingsInit, 14 | settings_out: *mut SettingsPtr, 15 | ) -> ErrorPtr { 16 | settings_new( 17 | settings_init, 18 | settings_out as *mut *mut wallet_core::Settings, 19 | ) 20 | .into_c_api() as ErrorPtr 21 | } 22 | 23 | /// # Safety 24 | /// 25 | /// This function also assumes that settings is a valid pointer previously 26 | /// obtained with this library, a null check is performed, but is important that 27 | /// the data it points to is valid 28 | /// 29 | /// linear_fee_out must point to valid writable memory, a null check is 30 | /// performed 31 | #[no_mangle] 32 | pub unsafe extern "C" fn iohk_jormungandr_wallet_settings_fees( 33 | settings: SettingsPtr, 34 | linear_fee_out: *mut LinearFee, 35 | ) -> ErrorPtr { 36 | settings_fees(settings as *const wallet_core::Settings, linear_fee_out).into_c_api() as ErrorPtr 37 | } 38 | 39 | /// # Safety 40 | /// 41 | /// This function also assumes that settings is a valid pointer previously 42 | /// obtained with this library, a null check is performed, but is important that 43 | /// the data it points to is valid 44 | /// 45 | /// discrimination_out must point to valid writable memory, a null check is 46 | /// performed 47 | #[no_mangle] 48 | pub unsafe extern "C" fn iohk_jormungandr_wallet_settings_discrimination( 49 | settings: SettingsPtr, 50 | discrimination_out: *mut Discrimination, 51 | ) -> ErrorPtr { 52 | settings_discrimination(settings as *const wallet_core::Settings, discrimination_out) 53 | .into_c_api() as ErrorPtr 54 | } 55 | 56 | /// # Safety 57 | /// 58 | /// This function assumes block0_hash points to 32 bytes of valid memory 59 | /// This function also assumes that settings is a valid pointer previously 60 | /// obtained with this library, a null check is performed, but is important that 61 | /// the data it points to is valid 62 | #[no_mangle] 63 | pub unsafe extern "C" fn iohk_jormungandr_wallet_settings_block0_hash( 64 | settings: SettingsPtr, 65 | block0_hash: *mut u8, 66 | ) -> ErrorPtr { 67 | settings_block0_hash(settings as *const wallet_core::Settings, block0_hash).into_c_api() 68 | as ErrorPtr 69 | } 70 | -------------------------------------------------------------------------------- /bindings/wallet-c/src/time.rs: -------------------------------------------------------------------------------- 1 | use wallet_core::c::time::{block_date_from_system_time, max_epiration_date, BlockDate}; 2 | 3 | use crate::{ErrorPtr, Settings}; 4 | 5 | /// This function dereference raw pointers. Even though the function checks if 6 | /// the pointers are null. Mind not to put random values in or you may see 7 | /// unexpected behaviors. 8 | /// 9 | /// # Arguments 10 | /// 11 | /// *settings*: the blockchain settings previously allocated with this library. 12 | /// *date*: desired date of expiration for a fragment. It must be expressed in seconds since the 13 | /// unix epoch. 14 | /// *block_date_out*: pointer to an allocated BlockDate structure, the memory should be writable. 15 | /// 16 | /// # Safety 17 | /// 18 | /// pointers should be allocated by this library and be valid. 19 | /// null pointers are checked and will result in an error. 20 | /// 21 | #[no_mangle] 22 | pub unsafe extern "C" fn iohk_jormungandr_block_date_from_system_time( 23 | settings: *const Settings, 24 | date: u64, 25 | block_date_out: *mut BlockDate, 26 | ) -> ErrorPtr { 27 | let r = block_date_from_system_time(settings.cast::(), date, block_date_out); 28 | 29 | r.into_c_api() as ErrorPtr 30 | } 31 | 32 | /// This function dereference raw pointers. Even though the function checks if 33 | /// the pointers are null. Mind not to put random values in or you may see 34 | /// unexpected behaviors. 35 | /// 36 | /// # Arguments 37 | /// 38 | /// *settings*: the blockchain settings previously allocated with this library. 39 | /// *current_time*: Current real time. It must be expressed in seconds since the unix epoch. 40 | /// *block_date_out*: pointer to an allocated BlockDate structure, the memory should be writable. 41 | /// 42 | /// # Safety 43 | /// 44 | /// pointers should be allocated by this library and be valid. 45 | /// null pointers are checked and will result in an error. 46 | /// 47 | #[no_mangle] 48 | pub unsafe extern "C" fn iohk_jormungandr_max_expiration_date( 49 | settings: *const Settings, 50 | current_time: u64, 51 | block_date_out: *mut BlockDate, 52 | ) -> ErrorPtr { 53 | let r = max_epiration_date( 54 | settings.cast::(), 55 | current_time, 56 | block_date_out, 57 | ); 58 | 59 | r.into_c_api() as ErrorPtr 60 | } 61 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: '@cordova/eslint-config/browser' 3 | ignorePatterns: ['**/pkg/*', '**/docs/*', '**/tests'] 4 | 5 | # overrides: 6 | # - files: [tests/**/*.js] 7 | # extends: '@cordova/eslint-config/node-tests' 8 | 9 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf -------------------------------------------------------------------------------- /bindings/wallet-cordova/.gitignore: -------------------------------------------------------------------------------- 1 | /package-lock.json 2 | /src/electron/pkg 3 | /src/android/libs 4 | /src/android/jormungandrwallet 5 | /src/android/jormungandr_wallet.kt 6 | /docs 7 | /node_modules -------------------------------------------------------------------------------- /bindings/wallet-cordova/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | tests 3 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/README.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | The javascript documentation for this module can be generated with jsdoc by 4 | running: 5 | 6 | ```bash 7 | npm install 8 | npm run doc 9 | ``` 10 | 11 | The generated files can be found at the `doc` directory, and can be read by 12 | opening `index.html` with a web browser. 13 | 14 | At the moment the best source for examples are the javascript 15 | [tests](tests/src/main.js). 16 | 17 | # Development 18 | 19 | ## Getting started 20 | 21 | The [official cordova 22 | documentation](https://cordova.apache.org/docs/en/11.x/guide/hybrid/plugins/index.html) 23 | is the best place to start. 24 | 25 | ## Requirements 26 | 27 | ### General 28 | 29 | As a baseline, Node.js and the cordova cli are required. Since the process of 30 | running the tests involves creating an application. The documentation at 31 | [installing-the-cordova-cli](https://cordova.apache.org/docs/en/11.x/guide/cli/index.html#installing-the-cordova-cli) 32 | can be used as a guide. Check out also the [Android 33 | documentation](https://cordova.apache.org/docs/en/11.x/guide/platforms/android/index.html) 34 | and the [iOS 35 | documentation](https://cordova.apache.org/docs/en/11.x/guide/platforms/ios/plugin.html) 36 | for requirements specific to the platform you are going to be developing for. 37 | 38 | Additionally, python3 is required to run the helper scripts. 39 | 40 | 41 | `jcli` is required to generate the genesis file that it is used in 42 | the test-vectors, installation instructions can be found in the [jormungandr's 43 | repository](https://github.com/input-output-hk/jormungandr). It's recommended 44 | that the `jcli` version is built with the same version of `chain-libs` that is 45 | used to build the plugin (which can be found in the Cargo.lock file), although 46 | it's not strictly necessary as long as the genesis binary encoding is 47 | compatible. 48 | 49 | ### Android 50 | 51 | - [cross](https://github.com/cross-rs/cross) is currently used for building the 52 | native libraries for Android. 53 | - [uniffi-bindgen](https://github.com/mozilla/uniffi-rs). The version must be the same one that is used in the `wallet-uniffi` crate. This can be found [here](../wallet-uniffi/Cargo.toml). 54 | 55 | 56 | ### iOS 57 | 58 | The ios rust platforms: 59 | 60 | - `rustup target add x86_64-apple-ios` 61 | - `rustup target add aarch64-apple-ios` 62 | 63 | [cbindgen](https://github.com/eqrion/cbindgen) is necessary for regenerating the 64 | C header, which is then used from the [Objetive C code](src/ios/WalletPlugin.m) in this package. Since the 65 | latest version is in source control, this is only needed if the core API 66 | changes. The [regen_header.sh](../bindings/wallet-c/regen_header.sh) script can 67 | be used to do this. 68 | 69 | ## Overview 70 | 71 | The core of the plugin is written in rust, and ffi is used to bridge that to 72 | either Objective-C or Kotlin, depending on the platform. 73 | 74 | The [wallet.js](www/wallet.js) file has the top level Javascript api for the 75 | plugin users, which is mostly a one-to-one mapping to the API of the 76 | wallet-core rust crate. 77 | 78 | The iOS part of the plugin is backed by the [wallet-c](../wallet-c/wallet.h) 79 | package, while the Android support is provided via the 80 | [wallet-uniffi](../wallet-uniffi/src/lib.udl) package. Both are also thin 81 | wrappers over **wallet-core**. 82 | 83 | ## Build 84 | 85 | [build_jni.py](scripts/build_jni.py) in the `scripts` directory will compile the 86 | Android native libraries, generate the Kotlin bindings, and copy those to this 87 | package in the `src/android` directory. 88 | 89 | [build_ios.py](scripts/build_ios.py) in the `scripts` directory will compile the 90 | iOS native libraries, and copy those along the C header to this package. 91 | 92 | `npm pack` can be used to make a distributable version of the plugin as an npm 93 | package. 94 | 95 | ## Running the tests 96 | 97 | The *tests* directory contains a Cordova plugin with [js 98 | tests](tests/src/main.js), we use 99 | [cordova-plugin-test-framework](https://github.com/apache/cordova-plugin-test-framework) 100 | as a test harness. 101 | 102 | The [test.py](scripts/test.py) script can be used to build 103 | the plugin and setup the test harness. For example, the following command will 104 | 105 | - create a cordova application at the `~/cdvtest/hello` directory. The cdvtest directory must not exist, as the script will not overwrite it. 106 | - install the cordova-plugin-test-framework. 107 | - build the native libraries for the android platform, and copy those to 108 | src/android/libs. 109 | - build the wallet-uniffi kotlin bindings for the native library. 110 | - install the plugin at this directory. 111 | - install the plugin in the tests directory. 112 | - run the test application if there is an emulator or device available. 113 | 114 | ```bash 115 | python3 test.py --platform android -d ~/cdvtest --cargo-build --run android full 116 | ``` 117 | 118 | The `reload-plugin` and `reload-tests` commands can be used if only one of 119 | those was modified, to avoid having to redo the whole process. -------------------------------------------------------------------------------- /bindings/wallet-cordova/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wallet-cordova-plugin", 3 | "version": "0.8.0-alpha1", 4 | "description": "Jormungandr wallet Cordova Plugin", 5 | "cordova": { 6 | "id": "wallet-cordova-plugin", 7 | "platforms": [ 8 | "android", 9 | "ios", 10 | "windows", 11 | "browser", 12 | "osx" 13 | ] 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/input-output-hk/chain-wallet-libs.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/input-output-hk/chain-wallet-libs/issues" 21 | }, 22 | "keywords": [ 23 | "cordova", 24 | "device", 25 | "ecosystem:cordova", 26 | "cordova-android", 27 | "cordova-ios", 28 | "cordova-windows", 29 | "cordova-browser", 30 | "cordova-osx" 31 | ], 32 | "scripts": { 33 | "eslint": "npm run lint", 34 | "eslint:fix": "npm run fix", 35 | "lint": "eslint .", 36 | "fix": "eslint --fix .", 37 | "doc": "jsdoc www/wallet.js --readme ./README.md -d docs" 38 | }, 39 | "author": "ecioppettini@atixlabs.com", 40 | "license": "Apache-2.0 OR MIT", 41 | "engines": { 42 | "cordovaDependencies": { 43 | "3.0.0": { 44 | "cordova": ">100" 45 | } 46 | } 47 | }, 48 | "devDependencies": { 49 | "@cordova/eslint-config": "^3.0.0", 50 | "jsdoc": "^3.6.4" 51 | }, 52 | "homepage": "https://github.com/input-output-hk/chain-wallet-libs#readme", 53 | "main": "index.js", 54 | "directories": { 55 | "test": "tests" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wallet Cordova Plugin 5 | Wallet Cordova Plugin 6 | MIT OR Apache-2.0 7 | cordova,wallet 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/scripts/build_ios.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import subprocess 5 | import sys 6 | import shutil 7 | from directories import ( 8 | repository_directory, 9 | script_directory, 10 | rust_build_directory, 11 | plugin_directory, 12 | ) 13 | 14 | libname = "libjormungandrwallet.a" 15 | 16 | library_header_src = repository_directory / Path("bindings/wallet-c/wallet.h") 17 | library_header_dst = plugin_directory / Path("src/ios/LibWallet.h") 18 | 19 | targets = { 20 | "x86_64-apple-ios": "x86_64", 21 | "aarch64-apple-ios": "arm64", 22 | } 23 | 24 | 25 | def run(release=True): 26 | lipo_args = [ 27 | "lipo", 28 | "-create", 29 | "-output", 30 | str(plugin_directory / "src/ios/" / libname), 31 | ] 32 | 33 | for rust_target, apple_target in targets.items(): 34 | arguments = [ 35 | "cargo", 36 | "rustc", 37 | "--target", 38 | rust_target, 39 | "-p", 40 | "jormungandrwallet", 41 | ] 42 | 43 | if release: 44 | arguments = arguments + ["--release", "--", "-C", "lto"] 45 | 46 | out = subprocess.run(arguments) 47 | if out.returncode != 0: 48 | print("couldn't build for target: ", rust_target) 49 | sys.exit(1) 50 | 51 | debug_or_release = "release" if release else "debug" 52 | 53 | lipo_args += [ 54 | "-arch", 55 | apple_target, 56 | str(rust_build_directory / rust_target / debug_or_release / libname), 57 | ] 58 | 59 | out = subprocess.run(lipo_args) 60 | if out.returncode != 0: 61 | print("couldn't build universal lib") 62 | sys.exit(1) 63 | 64 | shutil.copy(library_header_src, library_header_dst) 65 | 66 | 67 | if __name__ == "__main__": 68 | run() 69 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/scripts/build_jni.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import subprocess 5 | import sys 6 | import shutil 7 | from copy_jni_definitions import run as copy_definitions 8 | from directories import rust_build_directory, plugin_directory 9 | 10 | libname = "libuniffi_jormungandr_wallet.so" 11 | android_libs_directory = Path("src/android/libs") 12 | 13 | targets = { 14 | "aarch64-linux-android": "arm64-v8a", 15 | "armv7-linux-androideabi": "armeabi-v7a", 16 | "i686-linux-android": "x86", 17 | "x86_64-linux-android": "x86_64", 18 | } 19 | 20 | 21 | def copy_libs(release=True): 22 | for rust_target, android_target in targets.items(): 23 | dst = plugin_directory / android_libs_directory / android_target 24 | dst.mkdir(parents=True, exist_ok=True) 25 | 26 | debug_or_release = "release" if release else "debug" 27 | 28 | src = rust_build_directory / rust_target / debug_or_release / libname 29 | shutil.copy(src, dst) 30 | 31 | 32 | def run(release=True): 33 | for rust_target, android_target in targets.items(): 34 | arguments = [ 35 | "cross", 36 | "rustc", 37 | "--target", 38 | rust_target, 39 | "-p", 40 | "wallet-uniffi", 41 | "--features", 42 | "builtin-bindgen", 43 | ] 44 | 45 | if release: 46 | arguments = arguments + ["--release", "--", "-C", "lto"] 47 | 48 | out = subprocess.run(arguments) 49 | 50 | if out.returncode != 0: 51 | print("couldn't build for target: ", rust_target) 52 | sys.exit(1) 53 | 54 | copy_libs(release) 55 | copy_definitions() 56 | 57 | 58 | if __name__ == "__main__": 59 | run() 60 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/scripts/copy_jni_definitions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import subprocess 5 | import sys 6 | import shutil 7 | from directories import repository_directory, plugin_directory 8 | 9 | 10 | def run(): 11 | if ( 12 | subprocess.run( 13 | [ 14 | "uniffi-bindgen", 15 | "generate", 16 | "-l", 17 | "kotlin", 18 | "src/lib.udl", 19 | "--config-path", 20 | "uniffi.toml", 21 | "-o", 22 | "codegen/kotlin", 23 | ], 24 | cwd=repository_directory / "bindings" / "wallet-uniffi", 25 | ).returncode 26 | != 0 27 | ): 28 | print("couldn't build kotlin bindings") 29 | sys.exit(1) 30 | 31 | src_files = ( 32 | repository_directory 33 | / "bindings" 34 | / "wallet-uniffi" 35 | / "codegen" 36 | / "kotlin" 37 | / "com" 38 | / "iohk" 39 | / "jormungandr_wallet" 40 | ).glob("*kt") 41 | 42 | dst = plugin_directory / Path("src/android/") 43 | dst.mkdir(parents=True, exist_ok=True) 44 | 45 | print("Copy kotlin definitions from uniffi") 46 | print(f"destination: {dst}") 47 | 48 | for file in src_files: 49 | print(f"copy file: {file}") 50 | shutil.copy(file, dst) 51 | 52 | 53 | if __name__ == "__main__": 54 | run() 55 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/scripts/directories.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | script_directory = Path(__file__).parent 4 | plugin_directory = script_directory.parent 5 | tests_directory = plugin_directory / "tests" 6 | repository_directory = script_directory.parent.parent.parent 7 | rust_build_directory = repository_directory / "target" 8 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/scripts/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | 5 | from pathlib import Path 6 | import subprocess 7 | import sys 8 | import shutil 9 | import argparse 10 | import os 11 | import re 12 | 13 | from build_jni import run as build_jni 14 | from build_jni import copy_libs as copy_jni_libs 15 | from copy_jni_definitions import run as copy_jni_definitions 16 | from build_ios import run as build_ios 17 | from directories import repository_directory, plugin_directory, tests_directory 18 | 19 | sys.path.append(str(repository_directory / "test-vectors")) 20 | from rebuild_genesis_data import run as rebuild_genesis_data 21 | 22 | 23 | def sed(original: str, replacement: str, file: Path): 24 | # TODO: this may have some problems, but I'm also not sure if I want to use 25 | # `sed`, mostly for Windows compatibility 26 | with open(file, "r") as config: 27 | lines = config.readlines() 28 | 29 | with open(file, "w") as config: 30 | for line in lines: 31 | config.write(re.sub(original, replacement, line)) 32 | 33 | 34 | def create_hello_world(build_dir: Path): 35 | os.makedirs(build_dir, exist_ok=True) 36 | 37 | subprocess.check_call( 38 | ["cordova", "create", "hello", "com.example.hello", "HelloWorld"], 39 | cwd=build_dir, 40 | ) 41 | 42 | 43 | def enable_kotlin(app_dir: Path): 44 | file = app_dir / "config.xml" 45 | 46 | with open(file, "r") as config: 47 | lines = config.readlines() 48 | 49 | with open(file, "w") as config: 50 | for line in lines[:-1]: 51 | config.write(line) 52 | 53 | config.write(' ') 54 | config.write( 55 | ' ' 56 | ) 57 | config.write( 58 | ' ' 59 | ) 60 | 61 | config.write(lines[-1]) 62 | 63 | 64 | def install_test_framework(app_dir: Path): 65 | subprocess.check_call( 66 | ["cordova", "plugin", "add", "cordova-plugin-test-framework"], cwd=app_dir 67 | ) 68 | 69 | sed( 70 | '', 71 | '', 72 | app_dir / "config.xml", 73 | ) 74 | 75 | 76 | def install_platforms(app_dir: Path, android=True, ios=True): 77 | if android: 78 | subprocess.check_call( 79 | ["cordova", "platform", "add", "android@10.1.1"], cwd=app_dir 80 | ) 81 | subprocess.check_call(["cordova", "requirements", "android"], cwd=app_dir) 82 | 83 | if ios: 84 | subprocess.check_call(["cordova", "platform", "add", "ios"], cwd=app_dir) 85 | subprocess.check_call(["cordova", "requirements", "ios"], cwd=app_dir) 86 | 87 | 88 | def install_main_plugin( 89 | app_dir: Path, reinstall=True, android=False, ios=False, cargo_build=True 90 | ): 91 | if reinstall: 92 | subprocess.call( 93 | ["cordova", "plugin", "rm", "wallet-cordova-plugin"], cwd=app_dir 94 | ) 95 | 96 | if android: 97 | if cargo_build: 98 | build_jni(release=False) 99 | else: 100 | copy_jni_libs(release=False) 101 | copy_jni_definitions() 102 | 103 | if ios: 104 | build_ios(release=False) 105 | 106 | subprocess.check_call( 107 | ["cordova", "plugin", "add", str(plugin_directory)], cwd=app_dir 108 | ) 109 | 110 | 111 | def install_test_plugin(app_dir: Path, reinstall=True, regen_vectors=True): 112 | subprocess.check_call(["npm", "install"], cwd=tests_directory) 113 | 114 | if regen_vectors: 115 | # make sure the genesis is up-to-date 116 | rebuild_genesis_data() 117 | 118 | subprocess.check_call(["npm", "run", "build"], cwd=tests_directory) 119 | 120 | if reinstall: 121 | subprocess.call( 122 | ["cordova", "plugin", "rm", "wallet-cordova-plugin-tests"], cwd=app_dir 123 | ) 124 | 125 | subprocess.check_call( 126 | ["cordova", "plugin", "add", str(tests_directory)], cwd=app_dir 127 | ) 128 | 129 | 130 | if __name__ == "__main__": 131 | parser = argparse.ArgumentParser(description="Create test harness") 132 | 133 | platform_choices = ["android", "ios"] 134 | 135 | parser.add_argument( 136 | "--platform", required=True, nargs="+", choices=platform_choices 137 | ) 138 | parser.add_argument("command", choices=["full", "reload-plugin", "reload-tests"]) 139 | parser.add_argument("-d", "--directory", type=Path, required=True) 140 | parser.add_argument("-r", "--run", choices=platform_choices) 141 | 142 | parser.add_argument("--cargo-build", dest="cargo_build", action="store_true") 143 | parser.add_argument("--no-cargo-build", dest="cargo_build", action="store_false") 144 | 145 | parser.add_argument( 146 | "--regen-test-vectors", dest="regen_test_vectors", action="store_true" 147 | ) 148 | parser.add_argument( 149 | "--no-regen-test-vectors", dest="regen_test_vectors", action="store_false" 150 | ) 151 | 152 | parser.set_defaults(feature=True) 153 | 154 | args = parser.parse_args() 155 | 156 | android = "android" in args.platform 157 | ios = "ios" in args.platform 158 | 159 | build_dir = args.directory 160 | 161 | app_dir = build_dir / "hello" 162 | 163 | if args.command == "full": 164 | create_hello_world(build_dir) 165 | enable_kotlin(app_dir) 166 | install_platforms(app_dir, android=android, ios=ios) 167 | install_test_framework(app_dir) 168 | install_main_plugin( 169 | app_dir, 170 | reinstall=False, 171 | android=android, 172 | ios=ios, 173 | cargo_build=args.cargo_build, 174 | ) 175 | install_test_plugin( 176 | app_dir, reinstall=False, regen_vectors=args.regen_test_vectors 177 | ) 178 | 179 | if args.command == "reload-plugin": 180 | install_main_plugin(app_dir, reinstall=True, android=android, ios=ios) 181 | 182 | if args.command == "reload-tests": 183 | install_test_plugin( 184 | app_dir, reinstall=True, regen_vectors=args.regen_test_vectors 185 | ) 186 | 187 | if ios: 188 | subprocess.check_call( 189 | [ 190 | "cordova", 191 | "build", 192 | "ios", 193 | "--debug", 194 | ], 195 | cwd=app_dir, 196 | ) 197 | 198 | if android: 199 | subprocess.check_call(["cordova", "build", "android"], cwd=app_dir) 200 | 201 | if args.run: 202 | subprocess.check_call(["cordova", "run", args.run], cwd=app_dir) 203 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/src/android/jna-compile.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'net.java.dev.jna:jna:5.9.0@aar' 3 | } 4 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/src/ios/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | ColumnLimit: 100 3 | TabWidth: 4 4 | IndentWidth: 4 5 | ContinuationIndentWidth: 4 6 | ObjCBlockIndentWidth: 4 7 | AllowAllArgumentsOnNextLine: false 8 | AlignAfterOpenBracket: DontAlign 9 | IncludeBlocks: Preserve 10 | SortIncludes: false 11 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/src/ios/WalletPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #ifndef H_WALLET_PLUGIN 4 | #define H_WALLET_PLUGIN 5 | 6 | @interface WalletPlugin : CDVPlugin 7 | 8 | - (void)WALLET_IMPORT_KEYS:(CDVInvokedUrlCommand*)command; 9 | - (void)SYMMETRIC_CIPHER_DECRYPT:(CDVInvokedUrlCommand*)command; 10 | - (void)WALLET_SPENDING_COUNTER:(CDVInvokedUrlCommand*)command; 11 | - (void)WALLET_TOTAL_FUNDS:(CDVInvokedUrlCommand*)command; 12 | - (void)WALLET_ID:(CDVInvokedUrlCommand*)command; 13 | - (void)WALLET_SET_STATE:(CDVInvokedUrlCommand*)command; 14 | - (void)WALLET_VOTE:(CDVInvokedUrlCommand*)command; 15 | 16 | - (void)PROPOSAL_NEW_PUBLIC:(CDVInvokedUrlCommand*)command; 17 | - (void)PROPOSAL_NEW_PRIVATE:(CDVInvokedUrlCommand*)command; 18 | 19 | - (void)SETTINGS_NEW:(CDVInvokedUrlCommand*)command; 20 | - (void)SETTINGS_GET:(CDVInvokedUrlCommand*)command; 21 | 22 | - (void)FRAGMENT_ID:(CDVInvokedUrlCommand*)command; 23 | 24 | - (void)BLOCK_DATE_FROM_SYSTEM_TIME:(CDVInvokedUrlCommand*)command; 25 | - (void)MAX_EXPIRATION_DATE:(CDVInvokedUrlCommand*)command; 26 | 27 | - (void)WALLET_DELETE:(CDVInvokedUrlCommand*)command; 28 | - (void)SETTINGS_DELETE:(CDVInvokedUrlCommand*)command; 29 | - (void)PROPOSAL_DELETE:(CDVInvokedUrlCommand*)command; 30 | 31 | @end 32 | 33 | #endif /* H_WALLET_PLUGIN */ 34 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/src/windows/Wallet.js: -------------------------------------------------------------------------------- 1 | // TODO 2 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | root: true 3 | env: 4 | es6: true 5 | jasmine: true 6 | amd: true 7 | browser: true 8 | 9 | parserOptions: 10 | ecmaVersion: 2018 11 | sourceType: module 12 | 13 | extends: 'eslint:recommended' 14 | 15 | globals: 16 | exports: 'writable' 17 | module: 'writable' 18 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/.gitignore: -------------------------------------------------------------------------------- 1 | tests.js 2 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { 7 | "edge": "17", 8 | "firefox": "60", 9 | "chrome": "67", 10 | "safari": "11.1", 11 | }, 12 | "useBuiltIns": "usage", 13 | "corejs": "3", 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wallet-cordova-plugin-tests", 3 | "version": "2.0.0", 4 | "description": "", 5 | "cordova": { 6 | "id": "wallet-cordova-plugin-tests", 7 | "platforms": [] 8 | }, 9 | "keywords": [ 10 | "ecosystem:cordova" 11 | ], 12 | "author": "ecioppettini@atixlabs.com", 13 | "license": "MIT OR Apache-2.0", 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "@babel/cli": "^7.11.6", 17 | "@babel/core": "^7.12.3", 18 | "@babel/eslint-parser": "^7.12.1", 19 | "@babel/preset-env": "^7.11.5", 20 | "@babel/runtime-corejs3": "^7.11.2", 21 | "browserify": "^16.5.2", 22 | "core-js": "^3.6.5", 23 | "eslint": "^7.11.0", 24 | "eslintify": "^3.1.0" 25 | }, 26 | "scripts": { 27 | "build": "babel src/main.js --out-file tests.js && browserify tests.js -o tests.js --external wallet-cordova-plugin.wallet -s tests --debug -t eslintify", 28 | "lint": "eslint" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Wallet cordova plugin tests 7 | Apache 2.0 OR MIT 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/src/manual_tests.js: -------------------------------------------------------------------------------- 1 | const primitives = require('wallet-cordova-plugin.wallet'); 2 | 3 | const { hex, hexStringToBytes } = require('./utils.js'); 4 | 5 | // TODO: untangle this nesting hell. I still don't know if I can use promises/async here 6 | function restoreManualInputWallet(mnemonics, hexBlock, callBack) { 7 | window.wallet.walletRestore(mnemonics, wallet => { 8 | window.wallet.walletRetrieveFunds(wallet, hexStringToBytes(hexBlock), settings => { 9 | window.wallet.walletTotalFunds(wallet, retrievedFunds => { 10 | window.wallet.settingsDelete(settings, () => { 11 | window.wallet.walletDelete(wallet, () => { 12 | callBack(undefined, retrievedFunds); 13 | }, err => { callBack(new Error(`couldn't delete wallet ${err}`)); }); 14 | }, err => { callBack(new Error(`couldn't delete settings ${err}`)); }); 15 | }, err => { callBack(new Error(`couldn't get total funds ${err}`)); }); 16 | }, err => { 17 | callBack(new Error(`could not retrieve funds ${err}`)); 18 | }); 19 | }, err => { 20 | callBack(new Error(`could not create wallet ${err}`)); 21 | }); 22 | } 23 | 24 | function getAccountId(mnemonics, callBack) { 25 | primitives.walletRestore(mnemonics, wallet => { 26 | primitives.walletId(wallet, function (id) { 27 | callBack(undefined, hex(id)); 28 | }, function (err) { 29 | callBack(new Error(`could not get account id ${err}`)); 30 | }); 31 | }, err => { 32 | callBack(new Error(`could not create wallet ${err}`)); 33 | }); 34 | } 35 | 36 | module.exports = function (contentEl, createActionButton) { 37 | var logMessage = function (message, color) { 38 | var log = document.getElementById('info'); 39 | var logLine = document.createElement('div'); 40 | if (color) { 41 | logLine.style.color = color; 42 | } 43 | logLine.innerHTML = message; 44 | log.appendChild(logLine); 45 | }; 46 | 47 | var clearLog = function () { 48 | var log = document.getElementById('info'); 49 | log.innerHTML = ''; 50 | }; 51 | 52 | const form = 53 | '
' + 54 | '
' + 55 | '
' + 56 | '
'; 57 | 58 | contentEl.innerHTML = '
' + form; 59 | 60 | createActionButton( 61 | 'get funds', 62 | function () { 63 | clearLog(); 64 | const mnemonics = document.getElementById('mnemonics').value; 65 | const block = document.getElementById('block').value; 66 | restoreManualInputWallet(mnemonics, block, (error, value) => { 67 | if (error) { 68 | logMessage(`Error: ${error}`, null); 69 | } else { 70 | logMessage(`Funds: ${value}`, null); 71 | } 72 | }); 73 | }, 74 | 'get_funds' 75 | ); 76 | 77 | createActionButton( 78 | 'get account id', 79 | function () { 80 | clearLog(); 81 | const mnemonics = document.getElementById('mnemonics').value; 82 | getAccountId(mnemonics, (error, value) => { 83 | if (error) { 84 | logMessage(`Error: ${error}`, null); 85 | } else { 86 | logMessage(`account id: ${value}`, null); 87 | } 88 | }); 89 | }, 90 | 'account' 91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /bindings/wallet-cordova/tests/src/utils.js: -------------------------------------------------------------------------------- 1 | // copypasted ArrayBuffer to Hex string function 2 | const byteToHex = []; 3 | 4 | for (let n = 0; n <= 0xff; ++n) { 5 | const hexOctet = ('0' + n.toString(16)).slice(-2); 6 | byteToHex.push(hexOctet); 7 | } 8 | 9 | function hex(arrayBuffer) { 10 | const buff = new Uint8Array(arrayBuffer); 11 | const hexOctets = []; 12 | 13 | for (let i = 0; i < buff.length; ++i) { hexOctets.push(byteToHex[buff[i]]); } 14 | 15 | return hexOctets.join(''); 16 | } 17 | 18 | function hexStringToBytes(string) { 19 | const bytes = []; 20 | for (let c = 0; c < string.length; c += 2) { bytes.push(parseInt(string.substr(c, 2), 16)); } 21 | return Uint8Array.from(bytes); 22 | } 23 | 24 | /** 25 | * helper to convert the cordova-callback-style to promises, to make tests simpler 26 | * @param {function} f 27 | * @returns {function} 28 | */ 29 | function promisify(thisArg, f) { 30 | const newFunction = function () { 31 | const args = Array.prototype.slice.call(arguments); 32 | return new Promise(function (resolve, reject) { 33 | const success = function () { 34 | resolve(arguments[0]); 35 | }; 36 | 37 | const error = function () { 38 | reject(arguments[0]); 39 | }; 40 | 41 | args.push(success); 42 | args.push(error); 43 | 44 | f.apply(thisArg, args); 45 | }); 46 | }; 47 | return newFunction; 48 | } 49 | 50 | function uint8ArrayEquals(a, b) { 51 | if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) { 52 | throw Error('invalid arguments, expected a Uint8Array'); 53 | } 54 | 55 | return arrayEquals(a, b); 56 | } 57 | 58 | function uint32ArrayEquals(a, b) { 59 | if (!(a instanceof Uint32Array) || !(b instanceof Uint32Array)) { 60 | throw Error('invalid arguments, expected a Uint32Array'); 61 | } 62 | 63 | return arrayEquals(a, b); 64 | } 65 | 66 | function arrayEquals(a, b) { 67 | const length = a.length === b.length; 68 | 69 | let elements = true; 70 | 71 | for (let i = 0; i < a.length; i++) { 72 | elements = elements && a[i] === b[i]; 73 | } 74 | 75 | return length && elements; 76 | } 77 | 78 | module.exports = { 79 | hex, hexStringToBytes, promisify, uint8ArrayEquals, uint32ArrayEquals 80 | } -------------------------------------------------------------------------------- /bindings/wallet-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nicolas Di Prima ", "Vincent Hanquez "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "wallet-core" 6 | version = "0.7.0-pre4" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | crate-type = ["lib"] 12 | 13 | [dependencies] 14 | bip39 = {path = "../../bip39"} 15 | chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 16 | chain-core = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 17 | chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 18 | chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 19 | chain-path-derivation = {path = "../../chain-path-derivation"} 20 | chain-ser = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 21 | chain-vote = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 22 | chain-time = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 23 | hdkeygen = {path = "../../hdkeygen"} 24 | symmetric-cipher = {path = "../../symmetric-cipher"} 25 | thiserror = {version = "1.0.13", default-features = false} 26 | wallet = {path = "../../wallet"} 27 | bech32 = "0.7.2" 28 | 29 | rand = { version = "0.8.3", features = ["getrandom"] } 30 | 31 | [dev-dependencies] 32 | rand_chacha = "0.3.0" 33 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/c/fragment.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use chain_core::{packer::Codec, property::DeserializeFromSlice}; 3 | use chain_impl_mockchain::fragment::Fragment; 4 | use core::slice; 5 | 6 | use super::{FragmentPtr, NulPtr, FRAGMENT_ID_LENGTH}; 7 | 8 | /// # Safety 9 | /// 10 | /// buffer must be non null and point to buffer_length bytes of valid memory. 11 | /// 12 | pub unsafe fn fragment_from_raw( 13 | buffer: *const u8, 14 | buffer_length: usize, 15 | fragment_out: *mut FragmentPtr, 16 | ) -> Result { 17 | if buffer.is_null() { 18 | return Error::invalid_input("buffer").with(NulPtr).into(); 19 | } 20 | 21 | let fragment_out_ref = non_null_mut!(fragment_out); 22 | 23 | let bytes = slice::from_raw_parts(buffer, buffer_length); 24 | 25 | let fragment = match Fragment::deserialize_from_slice(&mut Codec::new(bytes)) { 26 | Ok(fragment) => fragment, 27 | Err(_e) => return Error::invalid_fragment().into(), 28 | }; 29 | 30 | let fragment = Box::new(fragment); 31 | 32 | *fragment_out_ref = Box::into_raw(fragment); 33 | 34 | Result::success() 35 | } 36 | 37 | /// # Safety 38 | /// 39 | /// fragment_ptr must be a pointer to memory allocated by this library, for 40 | /// example, with `fragment_from_raw` 41 | /// id_out must point to FRAGMENT_ID_LENGTH bytes of valid allocated writable 42 | /// memory 43 | /// This function checks for null pointers 44 | /// 45 | pub unsafe fn fragment_id(fragment_ptr: FragmentPtr, id_out: *mut u8) -> Result { 46 | let fragment = non_null!(fragment_ptr); 47 | 48 | let id = fragment.hash(); 49 | 50 | let bytes = id.as_bytes(); 51 | 52 | assert_eq!(bytes.len(), FRAGMENT_ID_LENGTH); 53 | 54 | std::ptr::copy(bytes.as_ptr(), id_out, bytes.len()); 55 | 56 | Result::success() 57 | } 58 | 59 | /// # Safety 60 | /// 61 | /// This function checks for null pointers, but take care that fragment_ptr was 62 | /// previously allocated by this library for example with fragment_from_raw 63 | /// 64 | pub unsafe fn fragment_delete(fragment_ptr: FragmentPtr) { 65 | if !fragment_ptr.is_null() { 66 | Box::from_raw(fragment_ptr as FragmentPtr); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/c/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! non_null { 2 | ( $obj:expr ) => { 3 | if let Some(obj) = $obj.as_ref() { 4 | obj 5 | } else { 6 | return Error::invalid_input(stringify!($expr)) 7 | .with(crate::c::NulPtr) 8 | .into(); 9 | } 10 | }; 11 | } 12 | 13 | macro_rules! non_null_mut { 14 | ( $obj:expr ) => { 15 | if let Some(obj) = $obj.as_mut() { 16 | obj 17 | } else { 18 | return Error::invalid_input(stringify!($expr)) 19 | .with(crate::c::NulPtr) 20 | .into(); 21 | } 22 | }; 23 | } 24 | 25 | macro_rules! non_null_array { 26 | ( $obj:expr, $len:expr) => { 27 | if $obj.is_null() { 28 | return Error::invalid_input(stringify!($expr)) 29 | .with(crate::c::NulPtr) 30 | .into(); 31 | } else { 32 | std::slice::from_raw_parts($obj, $len) 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/c/time.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use std::time::{Duration, SystemTime}; 3 | use wallet::Settings; 4 | 5 | #[repr(C)] 6 | pub struct BlockDate { 7 | pub epoch: u32, 8 | pub slot: u32, 9 | } 10 | 11 | impl From for BlockDate { 12 | fn from(date: chain_impl_mockchain::block::BlockDate) -> Self { 13 | BlockDate { 14 | epoch: date.epoch, 15 | slot: date.slot_id, 16 | } 17 | } 18 | } 19 | 20 | impl From for chain_impl_mockchain::block::BlockDate { 21 | fn from(date: BlockDate) -> Self { 22 | chain_impl_mockchain::block::BlockDate { 23 | epoch: date.epoch, 24 | slot_id: date.slot, 25 | } 26 | } 27 | } 28 | 29 | /// 30 | /// # Safety 31 | /// 32 | /// settings should be a pointer to a valid settings object allocated by this library with, for 33 | /// example, settings_build. 34 | pub unsafe fn block_date_from_system_time( 35 | settings: *const Settings, 36 | date: u64, 37 | block_date_out: *mut BlockDate, 38 | ) -> Result { 39 | let settings = non_null!(settings); 40 | match wallet::time::block_date_from_system_time( 41 | settings, 42 | SystemTime::UNIX_EPOCH + Duration::from_secs(date), 43 | ) { 44 | Ok(block_date) => { 45 | (*block_date_out).epoch = block_date.epoch; 46 | (*block_date_out).slot = block_date.slot_id; 47 | 48 | Result::success() 49 | } 50 | Err(_) => Error::invalid_transaction_validity_date().into(), 51 | } 52 | } 53 | 54 | /// 55 | /// # Safety 56 | /// 57 | /// settings should be a pointer to a valid settings object allocated by this library with, for 58 | /// example, settings_build. 59 | pub unsafe fn max_epiration_date( 60 | settings: *const Settings, 61 | current_time: u64, 62 | block_date_out: *mut BlockDate, 63 | ) -> Result { 64 | let settings = non_null!(settings); 65 | match wallet::time::max_expiration_date( 66 | settings, 67 | SystemTime::UNIX_EPOCH + Duration::from_secs(current_time), 68 | ) { 69 | Ok(block_date) => { 70 | (*block_date_out).epoch = block_date.epoch; 71 | (*block_date_out).slot = block_date.slot_id; 72 | 73 | Result::success() 74 | } 75 | Err(_) => Error::invalid_transaction_validity_date().into(), 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/c/vote.rs: -------------------------------------------------------------------------------- 1 | use super::ProposalPtr; 2 | use crate::{vote::PayloadTypeConfig, Error, Proposal, Result as AbiResult}; 3 | use chain_crypto::bech32::Bech32; 4 | use chain_impl_mockchain::{certificate::VotePlanId, vote::Options as VoteOptions}; 5 | use chain_vote::ElectionPublicKey; 6 | use std::convert::{TryFrom, TryInto}; 7 | use std::ffi::CStr; 8 | pub use wallet::Settings; 9 | 10 | // using generics in this module is questionable, but it's used just for code 11 | // re-use, the idea is to have two functions, and then it's exposed that way in 12 | // wallet-c/wallet-jni (with manual name mangling). 13 | // for the C interface, a tagged union could be used as input too, but I think 14 | // using the same approach for all the interfaces it's better. 15 | // something else that could work is a new opaque type. 16 | pub struct ProposalPublic; 17 | pub struct ProposalPrivate<'a>(pub &'a CStr); 18 | 19 | impl TryInto for ProposalPublic { 20 | type Error = Error; 21 | 22 | fn try_into(self) -> Result { 23 | Ok(PayloadTypeConfig::Public) 24 | } 25 | } 26 | 27 | impl<'a> TryInto for ProposalPrivate<'a> { 28 | type Error = Error; 29 | 30 | fn try_into(self) -> Result { 31 | const INPUT_NAME: &str = "election_public_key"; 32 | 33 | self.0 34 | .to_str() 35 | .map_err(|_| Error::invalid_input(INPUT_NAME)) 36 | .and_then(|s| { 37 | ElectionPublicKey::try_from_bech32_str(s) 38 | .map_err(|_| Error::invalid_vote_encryption_key()) 39 | }) 40 | .map(PayloadTypeConfig::Private) 41 | } 42 | } 43 | 44 | /// build the proposal object 45 | /// 46 | /// # Errors 47 | /// 48 | /// This function may fail if: 49 | /// 50 | /// * a null pointer was provided as an argument. 51 | /// * `num_choices` is out of the allowed range. 52 | /// 53 | /// # Safety 54 | /// 55 | /// This function dereference raw pointers. Even though the function checks if 56 | /// the pointers are null. Mind not to put random values in or you may see 57 | /// unexpected behaviors. 58 | pub unsafe fn proposal_new

( 59 | vote_plan_id: *const u8, 60 | index: u8, 61 | num_choices: u8, 62 | payload_type: P, 63 | proposal_out: *mut ProposalPtr, 64 | ) -> AbiResult 65 | where 66 | P: TryInto, 67 | P::Error: Into, 68 | { 69 | let options = match VoteOptions::new_length(num_choices) { 70 | Ok(options) => options, 71 | Err(err) => return Error::invalid_input("num_choices").with(err).into(), 72 | }; 73 | 74 | let vote_plan_id = non_null_array!(vote_plan_id, crate::vote::VOTE_PLAN_ID_LENGTH); 75 | let vote_plan_id = match VotePlanId::try_from(vote_plan_id) { 76 | Ok(id) => id, 77 | Err(err) => return Error::invalid_input("vote_plan_id").with(err).into(), 78 | }; 79 | 80 | let payload_type = match payload_type.try_into() { 81 | Ok(payload_type) => payload_type, 82 | Err(err) => return err.into(), 83 | }; 84 | 85 | let proposal = Proposal::new(vote_plan_id, index, options, payload_type); 86 | *non_null_mut!(proposal_out) = Box::into_raw(Box::new(proposal)); 87 | 88 | AbiResult::success() 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | 95 | #[test] 96 | fn cast_private_vote() { 97 | use chain_vote::{ 98 | committee::{MemberCommunicationKey, MemberState}, 99 | tally::Crs, 100 | }; 101 | let vote_plan_id = [0u8; crate::vote::VOTE_PLAN_ID_LENGTH]; 102 | 103 | let shared_string = 104 | b"Example of a shared string. This should be VotePlan.to_id()".to_owned(); 105 | let h = Crs::from_hash(&shared_string); 106 | 107 | let mut rng = rand::thread_rng(); 108 | 109 | let mc1 = MemberCommunicationKey::new(&mut rng); 110 | let mc = [mc1.to_public()]; 111 | 112 | let threshold = 1; 113 | 114 | let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); 115 | 116 | let pk = ElectionPublicKey::from_participants(&[m1.public_key()]); 117 | 118 | let election_public_key = pk.to_bech32_str(); 119 | let election_public_key = std::ffi::CString::new(election_public_key).unwrap(); 120 | 121 | let mut proposal: ProposalPtr = std::ptr::null_mut(); 122 | unsafe { 123 | let result = proposal_new( 124 | vote_plan_id.as_ptr(), 125 | 0, 126 | 2, 127 | ProposalPrivate(&election_public_key), 128 | (&mut proposal) as *mut ProposalPtr, 129 | ); 130 | assert!(result.is_ok()); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/conversion.rs: -------------------------------------------------------------------------------- 1 | use chain_impl_mockchain::transaction::Input; 2 | 3 | pub struct Conversion { 4 | pub(crate) ignored: Vec, 5 | pub(crate) transactions: Vec>, 6 | } 7 | 8 | impl Conversion { 9 | pub fn ignored(&self) -> &[Input] { 10 | &self.ignored 11 | } 12 | 13 | pub fn transactions(&self) -> &[Vec] { 14 | &self.transactions 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod c; 2 | mod error; 3 | mod vote; 4 | mod wallet; 5 | 6 | pub use self::{ 7 | error::{Error, ErrorCode, ErrorKind, Result}, 8 | vote::Proposal, 9 | wallet::Wallet, 10 | }; 11 | pub use ::wallet::Settings; 12 | pub use chain_impl_mockchain::{ 13 | fragment::{Fragment, FragmentId}, 14 | value::Value, 15 | vote::{Choice, Options, PayloadType}, 16 | }; 17 | pub use vote::{PayloadTypeConfig, VOTE_PLAN_ID_LENGTH}; 18 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/vote.rs: -------------------------------------------------------------------------------- 1 | use chain_impl_mockchain::{ 2 | certificate::{VoteCast, VotePlanId}, 3 | vote::{self, Choice, Options, Payload}, 4 | }; 5 | use chain_vote::{ElectionPublicKey, Vote}; 6 | 7 | pub const VOTE_PLAN_ID_LENGTH: usize = 32; 8 | 9 | pub struct Proposal { 10 | vote_plan_id: VotePlanId, 11 | index: u8, 12 | options: Options, 13 | payload_type: PayloadTypeConfig, 14 | } 15 | 16 | pub enum PayloadTypeConfig { 17 | Public, 18 | Private(ElectionPublicKey), 19 | } 20 | 21 | impl Proposal { 22 | pub fn new( 23 | vote_plan_id: VotePlanId, 24 | index: u8, 25 | options: Options, 26 | payload_type: PayloadTypeConfig, 27 | ) -> Self { 28 | Self { 29 | vote_plan_id, 30 | index, 31 | options, 32 | payload_type, 33 | } 34 | } 35 | 36 | pub fn new_public(vote_plan_id: VotePlanId, index: u8, options: Options) -> Self { 37 | Self::new(vote_plan_id, index, options, PayloadTypeConfig::Public) 38 | } 39 | 40 | pub fn new_private( 41 | vote_plan_id: VotePlanId, 42 | index: u8, 43 | options: Options, 44 | key: ElectionPublicKey, 45 | ) -> Self { 46 | Self::new( 47 | vote_plan_id, 48 | index, 49 | options, 50 | PayloadTypeConfig::Private(key), 51 | ) 52 | } 53 | 54 | pub fn vote(&self, choice: Choice) -> Option { 55 | if !self.options.validate(choice) { 56 | return None; 57 | } 58 | 59 | let payload = match self.payload_type { 60 | PayloadTypeConfig::Public => Payload::Public { choice }, 61 | PayloadTypeConfig::Private(ref key) => { 62 | let mut rng = rand::rngs::OsRng; 63 | 64 | // there is actually no way to build an Options object that 65 | // doesn't start from 0, but the fact that internally is a range 66 | // allows it, so I take the length of the interval just in case 67 | // for the size of the unit vector. There is no difference 68 | // anyway if the start is zero 69 | let length = self 70 | .options 71 | .choice_range() 72 | .end 73 | .checked_sub(self.options.choice_range().start)?; 74 | 75 | // the Choice was validated already, so this can't overflow 76 | let choice = choice.as_byte() - self.options.choice_range().start; 77 | 78 | let vote = Vote::new(length.into(), choice.into()); 79 | let (encrypted_vote, proof) = vote::encrypt_vote( 80 | &mut rng, 81 | &chain_vote::Crs::from_hash(self.vote_plan_id.as_ref()), 82 | key, 83 | vote, 84 | ); 85 | 86 | Payload::Private { 87 | encrypted_vote, 88 | proof, 89 | } 90 | } 91 | }; 92 | 93 | let cast = VoteCast::new(self.vote_plan_id.clone(), self.index, payload); 94 | 95 | Some(cast) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /bindings/wallet-core/src/wallet.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Proposal}; 2 | use chain_core::property::Serialize as _; 3 | use chain_crypto::SecretKey; 4 | use chain_impl_mockchain::{ 5 | account::SpendingCounter, 6 | block::BlockDate, 7 | fragment::{Fragment, FragmentId}, 8 | value::Value, 9 | vote::Choice, 10 | }; 11 | use wallet::{AccountId, Settings}; 12 | 13 | /// the wallet 14 | /// 15 | /// * use the `recover` function to recover the wallet from the mnemonics/password; 16 | /// * use the `retrieve_funds` to retrieve initial funds (if necessary) from the block0; 17 | /// then you can use `total_value` to see how much was recovered from the initial block0; 18 | /// 19 | pub struct Wallet { 20 | account: wallet::Wallet, 21 | } 22 | 23 | impl Wallet { 24 | /// Returns address of the account with the given chain discrimination. 25 | pub fn account(&self, discrimination: chain_addr::Discrimination) -> chain_addr::Address { 26 | self.account.account_id().address(discrimination) 27 | } 28 | 29 | pub fn id(&self) -> AccountId { 30 | self.account.account_id() 31 | } 32 | 33 | /// Retrieve a wallet from a list of free keys used as utxo's 34 | /// 35 | /// You can also use this function to recover a wallet even after you have 36 | /// transferred all the funds to the new format (see the [Self::convert] function). 37 | /// 38 | /// Parameters 39 | /// 40 | /// * `account_key`: the private key used for voting 41 | /// * `keys`: unused 42 | /// 43 | /// # Errors 44 | /// 45 | /// The function may fail if: 46 | /// 47 | /// TODO 48 | /// 49 | pub fn recover_free_keys(account_key: &[u8]) -> Result { 50 | let account = wallet::Wallet::new_from_key(SecretKey::from_binary(account_key).unwrap()); 51 | 52 | Ok(Wallet { account }) 53 | } 54 | 55 | /// use this function to confirm a transaction has been properly received 56 | /// 57 | /// This function will automatically update the state of the wallet 58 | pub fn confirm_transaction(&mut self, id: FragmentId) { 59 | self.account.confirm(&id); 60 | } 61 | 62 | /// get the current spending counter 63 | /// 64 | pub fn spending_counter(&self) -> Vec { 65 | self.account 66 | .spending_counter() 67 | .into_iter() 68 | .map(SpendingCounter::into) 69 | .collect() 70 | } 71 | 72 | /// get the total value in the wallet 73 | /// 74 | /// make sure to call `retrieve_funds` prior to calling this function 75 | /// otherwise you will always have `0` 76 | /// 77 | /// Once a conversion has been performed, this value can be use to display 78 | /// how much the wallet started with or retrieved from the chain. 79 | /// 80 | pub fn total_value(&self) -> Value { 81 | self.account.value() 82 | } 83 | 84 | /// Update the wallet's account state. 85 | /// 86 | /// The values to update the account state with can be retrieved from a 87 | /// Jormungandr API endpoint. It sets the balance value on the account 88 | /// as well as the current spending counter. 89 | /// 90 | /// It is important to be sure to have an up to date wallet state 91 | /// before doing any transactions, otherwise future transactions may fail 92 | /// to be accepted by the blockchain nodes because of an invalid witness 93 | /// signature. 94 | pub fn set_state(&mut self, value: Value, counters: Vec) -> Result<(), Error> { 95 | self.account 96 | .set_state( 97 | value, 98 | counters.into_iter().map(SpendingCounter::from).collect(), 99 | ) 100 | .map_err(|_| Error::invalid_spending_counters()) 101 | } 102 | 103 | /// Cast a vote 104 | /// 105 | /// This function outputs a fragment containing a voting transaction. 106 | /// 107 | /// # Parameters 108 | /// 109 | /// * `settings` - ledger settings. 110 | /// * `proposal` - proposal information including the range of values 111 | /// allowed in `choice`. 112 | /// * `choice` - the option to vote for. 113 | /// 114 | /// # Errors 115 | /// 116 | /// The error is returned when `choice` does not fall withing the range of 117 | /// available choices specified in `proposal`. 118 | pub fn vote( 119 | &mut self, 120 | settings: Settings, 121 | proposal: &Proposal, 122 | choice: Choice, 123 | valid_until: &BlockDate, 124 | lane: u8, 125 | ) -> Result, Error> { 126 | let payload = if let Some(payload) = proposal.vote(choice) { 127 | payload 128 | } else { 129 | return Err(Error::wallet_vote_range()); 130 | }; 131 | 132 | let mut builder = wallet::TransactionBuilder::new(&settings, payload, *valid_until); 133 | 134 | let value = builder.estimate_fee_with(1, 0); 135 | 136 | let account_tx_builder = self 137 | .account 138 | .new_transaction(value, lane) 139 | .map_err(|_| Error::not_enough_funds())?; 140 | 141 | let input = account_tx_builder.input(); 142 | let witness_builder = account_tx_builder.witness_builder(); 143 | 144 | builder.add_input(input, witness_builder); 145 | 146 | let tx = builder 147 | .finalize_tx(()) 148 | .map_err(|e| Error::wallet_transaction().with(e))?; 149 | 150 | let fragment = Fragment::VoteCast(tx); 151 | let id = fragment.hash(); 152 | 153 | account_tx_builder.add_fragment_id(id); 154 | 155 | Ok(fragment.serialize_as_vec().unwrap().into_boxed_slice()) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /bindings/wallet-js/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /bindings/wallet-js/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Enzo Cioppettini "] 3 | description = """Wallet functionalities to interact with Jörmungandr 4 | 5 | This package profiles all that is needed to have an healthy and secure 6 | interaction with Jörmungandr blockchain technology. 7 | """ 8 | edition = "2018" 9 | license = "MIT OR Apache-2.0" 10 | name = "wallet-js" 11 | repository = "https://github.com/input-output-hk/chain-wallet-libs" 12 | version = "0.7.0-pre4" 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [features] 18 | default = ["console_error_panic_hook"] 19 | 20 | [dependencies] 21 | chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 22 | chain-vote = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 23 | chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 24 | getrandom = { version = "0.2.2", features = ["js"] } 25 | rand = "0.8.3" 26 | rand_chacha = "0.3.0" 27 | symmetric-cipher = {path = "../../symmetric-cipher"} 28 | wallet-core = {path = "../wallet-core"} 29 | wasm-bindgen = "0.2" 30 | js-sys = "0.3.40" 31 | bech32 = "0.7.2" 32 | 33 | # The `console_error_panic_hook` crate provides better debugging of panics by 34 | # logging them with `console.error`. This is great for development, but requires 35 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 36 | # code size when deploying. 37 | console_error_panic_hook = {version = "0.1.1", optional = true} 38 | 39 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size 40 | # compared to the default allocator's ~10K. It is slower than the default 41 | # allocator, however. 42 | # 43 | # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. 44 | wee_alloc = {version = "0.4.2", optional = true} 45 | 46 | # clear_on_drop is a dependency of ed25519_dalek 47 | # The default can't be compiled to wasm, so it's necessary to enable either the 'nightly' 48 | # feature or this one. 49 | clear_on_drop = {version = "0.2", features = ["no_cc"]} 50 | 51 | [dev-dependencies] 52 | wasm-bindgen-test = "0.3" 53 | 54 | # See https://github.com/rustwasm/wasm-pack/issues/886 55 | [package.metadata.wasm-pack.profile.release] 56 | wasm-opt = ["-O4", "--enable-mutable-globals"] 57 | -------------------------------------------------------------------------------- /bindings/wallet-js/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /bindings/wallet-js/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bindings/wallet-js/README.md: -------------------------------------------------------------------------------- 1 | # Jormungandr wallet SDK for JavaScript 2 | 3 | This crate provides a wasm package with JavaScript and TypeScript bindings 4 | for the Jormungandr wallet client library. 5 | 6 | See the `Wallet` class for a starting point in the API documentation. 7 | 8 | ## Building the package 9 | 10 | [wasm-pack](https://github.com/rustwasm/wasm-pack) is required. 11 | 12 | ``` 13 | wasm-pack build -d pkg 14 | ``` 15 | 16 | Use the `--target` option to select the target environment for the package. 17 | 18 | For a quicker, but unoptimized build: 19 | 20 | ``` 21 | wasm-pack build --dev 22 | ``` 23 | 24 | ## Documentation 25 | 26 | The API documentation can be generated from the built JavaScript bindings 27 | with the following command: 28 | 29 | ``` 30 | jsdoc pkg -c ../../jsdoc.json -d pkg/doc -R README.md 31 | ``` 32 | -------------------------------------------------------------------------------- /bindings/wallet-js/src/utils.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | /// `set_panic_hook` function can be called at least once during initialization, 4 | /// to get better error messages if the code ever panics. 5 | /// The function has no parameters. 6 | #[wasm_bindgen] 7 | pub fn set_panic_hook() { 8 | // When the `console_error_panic_hook` feature is enabled, we can call the 9 | // `set_panic_hook` function at least once during initialization, and then 10 | // we will get better error messages if our code ever panics. 11 | // 12 | // For more details see 13 | // https://github.com/rustwasm/console_error_panic_hook#readme 14 | #[cfg(feature = "console_error_panic_hook")] 15 | console_error_panic_hook::set_once(); 16 | } 17 | 18 | // taken from: 19 | // https://github.com/input-output-hk/js-chain-libs/blob/cc463b59fdc64a4fff63f67901118f60b783520c/src/utils.rs#L12 20 | #[macro_export] 21 | macro_rules! impl_collection { 22 | ($collection:ident, $type:ty) => { 23 | #[wasm_bindgen] 24 | pub struct $collection(Vec<$type>); 25 | 26 | #[allow(clippy::new_without_default)] 27 | #[wasm_bindgen] 28 | impl $collection { 29 | pub fn new() -> $collection { 30 | Self(vec![]) 31 | } 32 | 33 | pub fn size(&self) -> usize { 34 | self.0.len() 35 | } 36 | 37 | pub fn get(&self, index: usize) -> $type { 38 | self.0[index].clone() 39 | } 40 | 41 | pub fn add(&mut self, item: $type) { 42 | self.0.push(item); 43 | } 44 | } 45 | 46 | impl From> for $collection { 47 | fn from(vec: Vec<$type>) -> $collection { 48 | $collection(vec) 49 | } 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /bindings/wallet-js/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wallet_js::*; 7 | use wasm_bindgen_test::*; 8 | 9 | wasm_bindgen_test_configure!(run_in_browser); 10 | 11 | const BLOCK0: &[u8] = include_bytes!("../../../test-vectors/block0"); 12 | 13 | #[wasm_bindgen_test] 14 | fn gen_key() { 15 | // just test that the random generator works 16 | let _key = Ed25519ExtendedPrivate::generate(); 17 | } 18 | 19 | #[wasm_bindgen_test] 20 | fn gen_key_from_seed() { 21 | let seed1 = [1u8; 32]; 22 | let key1 = Ed25519ExtendedPrivate::from_seed(seed1.as_ref()).unwrap(); 23 | let key2 = Ed25519ExtendedPrivate::from_seed(seed1.as_ref()).unwrap(); 24 | 25 | assert_eq!(key1.bytes(), key2.bytes()); 26 | 27 | let seed2 = [2u8; 32]; 28 | let key3 = Ed25519ExtendedPrivate::from_seed(seed2.as_ref()).unwrap(); 29 | 30 | assert_ne!(key3.bytes(), key1.bytes()); 31 | } 32 | 33 | #[wasm_bindgen_test] 34 | fn gen_key_from_invalid_seed_fails() { 35 | const INVALID_SEED_SIZE: usize = 32 + 1; 36 | let bad_seed = [2u8; INVALID_SEED_SIZE]; 37 | assert!(Ed25519ExtendedPrivate::from_seed(bad_seed.as_ref()).is_err()) 38 | } 39 | 40 | #[wasm_bindgen_test] 41 | fn sign_verify_extended() { 42 | let key = Ed25519ExtendedPrivate::generate(); 43 | let msg = [1, 2, 3, 4u8]; 44 | let signature = key.sign(&msg); 45 | 46 | assert!(key.public().verify(&signature, &msg)); 47 | } 48 | 49 | #[wasm_bindgen_test] 50 | fn sign_verify() { 51 | let key = Ed25519Private::generate(); 52 | let msg = [1, 2, 3, 4u8]; 53 | let signature = key.sign(&msg); 54 | 55 | assert!(key.public().verify(&signature, &msg)); 56 | } 57 | -------------------------------------------------------------------------------- /bindings/wallet-uniffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wallet-uniffi" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = [ "cdylib" ] 8 | name = "uniffi_jormungandr_wallet" 9 | 10 | [dependencies] 11 | uniffi = "0.16.0" 12 | uniffi_macros = "0.16.0" 13 | wallet-core = { path = "../wallet-core" } 14 | wallet = {path = "../../wallet"} 15 | symmetric-cipher = {path = "../../symmetric-cipher"} 16 | chain-vote = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 17 | chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 18 | chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 19 | chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 20 | chain-time = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 21 | chain-ser = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 22 | thiserror = {version = "1.0", default-features = false} 23 | 24 | [build-dependencies] 25 | uniffi_build = "0.16.0" 26 | 27 | [features] 28 | builtin-bindgen = ["uniffi_build/builtin-bindgen"] 29 | -------------------------------------------------------------------------------- /bindings/wallet-uniffi/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | uniffi_build::generate_scaffolding("./src/lib.udl").unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /bindings/wallet-uniffi/gen_bindings.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | uniffi-bindgen generate -l kotlin src/lib.udl --config-path uniffi.toml -o codegen/kotlin 4 | -------------------------------------------------------------------------------- /bindings/wallet-uniffi/src/lib.udl: -------------------------------------------------------------------------------- 1 | namespace jormungandr_wallet { 2 | [Throws=WalletError] 3 | BlockDate block_date_from_system_time(Settings settings, u64 unix_epoch); 4 | [Throws=WalletError] 5 | BlockDate max_expiration_date(Settings settings, u64 current_time); 6 | 7 | [Throws=WalletError] 8 | sequence symmetric_cipher_decrypt(sequence password, sequence ciphertext); 9 | }; 10 | 11 | [Error] 12 | enum WalletError { 13 | "InvalidEncryptionKey", 14 | "MalformedVotePlanId", 15 | "CoreError", 16 | "MalformedBlock0Hash", 17 | "MalformedSecretKey", 18 | "TimeError", 19 | "CipherError", 20 | "InvalidFragment", 21 | "InvalidSpendingCounters", 22 | }; 23 | 24 | interface Wallet { 25 | [Throws=WalletError] 26 | constructor( 27 | SecretKeyEd25519Extended account_key 28 | ); 29 | 30 | [Throws=WalletError] 31 | void set_state(u64 value, sequence counter); 32 | [Throws=WalletError] 33 | sequence vote(Settings settings, Proposal proposal, u8 choice, BlockDate valid_until, u8 lane); 34 | sequence account_id(); 35 | sequence spending_counters(); 36 | u64 total_value(); 37 | }; 38 | 39 | interface SecretKeyEd25519Extended { 40 | [Throws=WalletError] 41 | constructor(sequence raw); 42 | }; 43 | 44 | interface Fragment { 45 | [Throws=WalletError] 46 | constructor(sequence raw); 47 | sequence id(); 48 | sequence serialize(); 49 | }; 50 | 51 | dictionary LinearFee { 52 | u64 constant; 53 | u64 coefficient; 54 | u64 certificate; 55 | PerCertificateFee per_certificate_fees; 56 | PerVoteCertificateFee per_vote_certificate_fees; 57 | }; 58 | 59 | dictionary PerCertificateFee { 60 | u64 certificate_pool_registration; 61 | u64 certificate_stake_delegation; 62 | u64 certificate_owner_stake_delegation; 63 | }; 64 | 65 | dictionary PerVoteCertificateFee { 66 | u64 certificate_vote_plan; 67 | u64 certificate_vote_cast; 68 | }; 69 | 70 | enum Discrimination { 71 | "Production", 72 | "Test", 73 | }; 74 | 75 | dictionary TimeEra { 76 | u32 epoch_start; 77 | u64 slot_start; 78 | u32 slots_per_epoch; 79 | }; 80 | 81 | dictionary SettingsRaw { 82 | LinearFee fees; 83 | Discrimination discrimination; 84 | sequence block0_hash; 85 | u64 block0_date; 86 | u8 slot_duration; 87 | TimeEra time_era; 88 | u8 transaction_max_expiry_epochs; 89 | }; 90 | 91 | interface Settings { 92 | [Throws=WalletError] 93 | constructor(SettingsRaw settings); 94 | SettingsRaw settings_raw(); 95 | }; 96 | 97 | dictionary Proposal { 98 | sequence vote_plan_id; 99 | u8 index; 100 | u8 options; 101 | PayloadTypeConfig payload_type; 102 | }; 103 | 104 | dictionary BlockDate { 105 | u32 epoch; 106 | u32 slot; 107 | }; 108 | 109 | [Enum] 110 | interface PayloadTypeConfig { 111 | Public(); 112 | Private(string encryption_key); 113 | }; 114 | -------------------------------------------------------------------------------- /bindings/wallet-uniffi/uniffi.toml: -------------------------------------------------------------------------------- 1 | [bindings.kotlin] 2 | package_name = "com.iohk.jormungandr_wallet" 3 | -------------------------------------------------------------------------------- /bip39/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bip39" 3 | version = "0.1.0" 4 | authors = ["Nicolas Di Prima ", "Vincent Hanquez "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | cryptoxide = "0.4.2" 10 | thiserror = { version = "1.0.13", default-features = false } 11 | zeroize = "1.5.3" 12 | 13 | [dev-dependencies] 14 | quickcheck = "0.9" 15 | quickcheck_macros = "0.9" 16 | unicode-normalization = "*" 17 | rand = "*" 18 | hex = "*" 19 | -------------------------------------------------------------------------------- /bip39/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /bip39/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /bip39/src/dictionary.rs: -------------------------------------------------------------------------------- 1 | //! Language support for BIP39 implementations. 2 | //! 3 | //! We provide default dictionaries for the some common languages. 4 | //! This interface is exposed to allow users to implement custom 5 | //! dictionaries. 6 | //! 7 | //! Because this module is part of the `chain_wallet` crate and that we 8 | //! need to keep the dependencies as small as possible we do not support 9 | //! UTF8 NFKD by default. Users must be sure to compose (or decompose) 10 | //! our output (or input) UTF8 strings. 11 | //! 12 | 13 | use thiserror::Error; 14 | 15 | use crate::MnemonicIndex; 16 | 17 | /// Errors associated to a given language/dictionary 18 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Error)] 19 | pub enum Error { 20 | /// this means the given word is not in the Dictionary of the Language. 21 | #[error("Mnemonic word not found in dictionary \"{0}\"")] 22 | MnemonicWordNotFoundInDictionary(String), 23 | } 24 | 25 | /// trait to represent the the properties that needs to be associated to 26 | /// a given language and its dictionary of known mnemonic words. 27 | /// 28 | pub trait Language { 29 | fn name(&self) -> &'static str; 30 | fn separator(&self) -> &'static str; 31 | fn lookup_mnemonic(&self, word: &str) -> Result; 32 | fn lookup_word(&self, mnemonic: MnemonicIndex) -> Result; 33 | } 34 | 35 | /// Default Dictionary basic support for the different main languages. 36 | /// This dictionary expect the inputs to have been normalized (UTF-8 NFKD). 37 | /// 38 | /// If you wish to implement support for non pre-normalized form you can 39 | /// create reuse this dictionary in a custom struct and implement support 40 | /// for [`Language`](./trait.Language.html) accordingly (_hint_: use 41 | /// [`unicode-normalization`](https://crates.io/crates/unicode-normalization)). 42 | /// 43 | pub struct DefaultDictionary { 44 | pub words: [&'static str; 2048], 45 | pub name: &'static str, 46 | } 47 | 48 | impl Language for DefaultDictionary { 49 | fn name(&self) -> &'static str { 50 | self.name 51 | } 52 | fn separator(&self) -> &'static str { 53 | " " 54 | } 55 | fn lookup_mnemonic(&self, word: &str) -> Result { 56 | match self.words.iter().position(|x| x == &word) { 57 | None => Err(Error::MnemonicWordNotFoundInDictionary(word.to_string())), 58 | Some(v) => { 59 | Ok( 60 | // it is safe to call unwrap as we guarantee that the 61 | // returned index `v` won't be out of bound for a 62 | // `MnemonicIndex` (DefaultDictionary.words is an array of 2048 elements) 63 | MnemonicIndex::new(v as u16).unwrap(), 64 | ) 65 | } 66 | } 67 | } 68 | fn lookup_word(&self, mnemonic: MnemonicIndex) -> Result { 69 | Ok(unsafe { self.words.get_unchecked(mnemonic.0 as usize) }).map(|s| String::from(*s)) 70 | } 71 | } 72 | 73 | /// default English dictionary as provided by the 74 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#wordlists) 75 | /// 76 | pub const ENGLISH: DefaultDictionary = DefaultDictionary { 77 | words: include!("bip39_english.txt"), 78 | name: "english", 79 | }; 80 | 81 | /// default French dictionary as provided by the 82 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#french) 83 | /// 84 | pub const FRENCH: DefaultDictionary = DefaultDictionary { 85 | words: include!("bip39_french.txt"), 86 | name: "french", 87 | }; 88 | 89 | /// default Japanese dictionary as provided by the 90 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese) 91 | /// 92 | pub const JAPANESE: DefaultDictionary = DefaultDictionary { 93 | words: include!("bip39_japanese.txt"), 94 | name: "japanese", 95 | }; 96 | 97 | /// default Korean dictionary as provided by the 98 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese) 99 | /// 100 | pub const KOREAN: DefaultDictionary = DefaultDictionary { 101 | words: include!("bip39_korean.txt"), 102 | name: "korean", 103 | }; 104 | 105 | /// default chinese simplified dictionary as provided by the 106 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#chinese) 107 | /// 108 | pub const CHINESE_SIMPLIFIED: DefaultDictionary = DefaultDictionary { 109 | words: include!("bip39_chinese_simplified.txt"), 110 | name: "chinese-simplified", 111 | }; 112 | /// default chinese traditional dictionary as provided by the 113 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#chinese) 114 | /// 115 | pub const CHINESE_TRADITIONAL: DefaultDictionary = DefaultDictionary { 116 | words: include!("bip39_chinese_traditional.txt"), 117 | name: "chinese-traditional", 118 | }; 119 | 120 | /// default italian dictionary as provided by the 121 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#italian) 122 | /// 123 | pub const ITALIAN: DefaultDictionary = DefaultDictionary { 124 | words: include!("bip39_italian.txt"), 125 | name: "italian", 126 | }; 127 | 128 | /// default spanish dictionary as provided by the 129 | /// [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#spanish) 130 | /// 131 | pub const SPANISH: DefaultDictionary = DefaultDictionary { 132 | words: include!("bip39_spanish.txt"), 133 | name: "spanish", 134 | }; 135 | -------------------------------------------------------------------------------- /bip39/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::result; 2 | use thiserror::Error; 3 | 4 | /// Error regarding BIP39 operations 5 | #[derive(Debug, Error, PartialEq, Eq)] 6 | pub enum Error { 7 | /// Received an unsupported number of mnemonic words. The parameter 8 | /// contains the unsupported number. Supported values are 9 | /// described as part of the [`Type`](./enum.Type.html). 10 | #[error("Unsupported number of mnemonic words: {0}")] 11 | WrongNumberOfWords(usize), 12 | 13 | /// The entropy is of invalid size. The parameter contains the invalid size, 14 | /// the list of supported entropy size are described as part of the 15 | /// [`Type`](./enum.Type.html). 16 | #[error("Unsupported mnemonic entropy size: {0}")] 17 | WrongKeySize(usize), 18 | 19 | /// The given mnemonic is out of bound, i.e. its index is above 2048 and 20 | /// is invalid within BIP39 specifications. 21 | #[error("The given mnemonic is out of bound, {0}")] 22 | MnemonicOutOfBound(u16), 23 | 24 | /// Forward error regarding dictionary operations. 25 | #[error("Unknown mnemonic word")] 26 | LanguageError( 27 | #[source] 28 | #[from] 29 | crate::dictionary::Error, 30 | ), 31 | 32 | /// the Seed is of invalid size. The parameter is the given seed size, 33 | /// the expected seed size is [`SEED_SIZE`](./constant.SEED_SIZE.html). 34 | #[error("Invalid Seed Size, expected 64 bytes, but received {0} bytes.")] 35 | InvalidSeedSize(usize), 36 | 37 | /// checksum is invalid. The first parameter is the expected checksum, 38 | /// the second id the computed checksum. This error means that the given 39 | /// mnemonics are invalid to retrieve the original entropy. The user might 40 | /// have given an invalid mnemonic phrase. 41 | #[error("Invalid Entropy's Checksum, expected {0:08b} but found {1:08b}")] 42 | InvalidChecksum(u8, u8), 43 | } 44 | 45 | /// convenient Alias to wrap up BIP39 operations that may return 46 | /// an [`Error`](./enum.Error.html). 47 | pub type Result = result::Result; 48 | -------------------------------------------------------------------------------- /bip39/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! BIP39 mnemonics 2 | //! 3 | //! Can be used to generate the root key of a given HDWallet, 4 | //! an address or simply convert bits to mnemonic for human friendly 5 | //! value. 6 | //! 7 | //! For more details about the protocol, see 8 | //! [Bitcoin Improvement Proposal 39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) 9 | //! 10 | //! # Example 11 | //! 12 | //! ## To create a new HDWallet 13 | //! 14 | //! ``` 15 | //! # extern crate rand; 16 | //! # 17 | //! # use bip39::*; 18 | //! 19 | //! // first, you need to generate the original entropy 20 | //! let entropy = Entropy::generate(Type::Type18Words, rand::random); 21 | //! 22 | //! // human readable mnemonics (in English) to retrieve the original entropy 23 | //! // and eventually recover a HDWallet. 24 | //! let mnemonic_phrase = entropy.to_mnemonics().to_string(&dictionary::ENGLISH); 25 | //! 26 | //! // The seed of the HDWallet is generated from the mnemonic string 27 | //! // in the associated language. 28 | //! let seed = Seed::from_mnemonic_string(&mnemonic_phrase, b"some password"); 29 | //! ``` 30 | //! 31 | //! ## To recover a HDWallet 32 | //! 33 | //! ``` 34 | //! # use bip39::*; 35 | //! 36 | //! let mnemonics = "mimic left ask vacant toast follow bitter join diamond gate attend obey"; 37 | //! 38 | //! // to retrieve the seed, you only need the mnemonic string, 39 | //! // here we construct the `MnemonicString` by verifying the 40 | //! // mnemonics are valid against the given dictionary (English here). 41 | //! let mnemonic_phrase = MnemonicString::new(&dictionary::ENGLISH, mnemonics.to_owned()) 42 | //! .expect("the given mnemonics are valid English words"); 43 | //! 44 | //! // The seed of the HDWallet is generated from the mnemonic string 45 | //! // in the associated language. 46 | //! let seed = Seed::from_mnemonic_string(&mnemonic_phrase, b"some password"); 47 | //! ``` 48 | //! 49 | 50 | #[cfg(test)] 51 | extern crate quickcheck; 52 | #[cfg(test)] 53 | #[macro_use(quickcheck)] 54 | extern crate quickcheck_macros; 55 | 56 | mod bits; 57 | mod entropy; 58 | mod error; 59 | mod mnemonic; 60 | mod seed; 61 | mod types; 62 | 63 | pub mod dictionary; 64 | 65 | pub use self::{ 66 | entropy::Entropy, 67 | error::{Error, Result}, 68 | mnemonic::{MnemonicIndex, MnemonicString, Mnemonics, MAX_MNEMONIC_VALUE}, 69 | seed::{Seed, SEED_SIZE}, 70 | types::Type, 71 | }; 72 | 73 | #[cfg(test)] 74 | mod test { 75 | use super::*; 76 | use rand::random; 77 | 78 | use unicode_normalization::UnicodeNormalization; 79 | 80 | use crate::{dictionary::Language, Entropy, Seed}; 81 | 82 | #[test] 83 | fn english_dic() { 84 | let dic = &dictionary::ENGLISH; 85 | 86 | assert_eq!(dic.lookup_mnemonic("abandon"), Ok(MnemonicIndex(0))); 87 | assert_eq!(dic.lookup_mnemonic("crack"), Ok(MnemonicIndex(398))); 88 | assert_eq!(dic.lookup_mnemonic("shell"), Ok(MnemonicIndex(1579))); 89 | assert_eq!(dic.lookup_mnemonic("zoo"), Ok(MnemonicIndex(2047))); 90 | 91 | assert_eq!(dic.lookup_word(MnemonicIndex(0)), Ok("abandon".to_string())); 92 | assert_eq!(dic.lookup_word(MnemonicIndex(398)), Ok("crack".to_string())); 93 | assert_eq!( 94 | dic.lookup_word(MnemonicIndex(1579)), 95 | Ok("shell".to_string()) 96 | ); 97 | assert_eq!(dic.lookup_word(MnemonicIndex(2047)), Ok("zoo".to_string())); 98 | } 99 | 100 | #[test] 101 | fn mnemonic_zero() { 102 | let entropy = Entropy::Entropy12([0; 16]); 103 | let mnemonics = entropy.to_mnemonics(); 104 | let entropy2 = Entropy::from_mnemonics(&mnemonics).unwrap(); 105 | assert_eq!(entropy.as_ref(), entropy2.as_ref()); 106 | } 107 | 108 | #[test] 109 | fn mnemonic_7f() { 110 | let entropy = Entropy::Entropy12([0x7f; 16]); 111 | let mnemonics = entropy.to_mnemonics(); 112 | let entropy2 = Entropy::from_mnemonics(&mnemonics).unwrap(); 113 | assert_eq!(entropy.as_ref(), entropy2.as_ref()); 114 | } 115 | 116 | #[test] 117 | fn from_mnemonic_to_mnemonic() { 118 | let entropy = Entropy::generate(Type::Type12Words, random); 119 | let mnemonics = entropy.to_mnemonics(); 120 | let entropy2 = Entropy::from_mnemonics(&mnemonics).unwrap(); 121 | assert_eq!(entropy.as_ref(), entropy2.as_ref()); 122 | } 123 | 124 | #[derive(Debug)] 125 | struct TestVector { 126 | entropy: &'static str, 127 | mnemonics: &'static str, 128 | seed: &'static str, 129 | passphrase: &'static str, 130 | } 131 | 132 | fn mk_test(test: &TestVector, dic: &D) { 133 | // decompose the UTF8 inputs before processing: 134 | let mnemonics: String = test.mnemonics.nfkd().collect(); 135 | let passphrase: String = test.passphrase.nfkd().collect(); 136 | 137 | let mnemonics_ref = Mnemonics::from_string(dic, &mnemonics).expect("valid mnemonics"); 138 | let mnemonics_str = MnemonicString::new(dic, mnemonics).expect("valid mnemonics string"); 139 | let entropy_ref = Entropy::from_slice(&hex::decode(test.entropy).unwrap()) 140 | .expect("decode entropy from hex"); 141 | let seed_ref = 142 | Seed::from_slice(&hex::decode(test.seed).unwrap()).expect("decode seed from hex"); 143 | 144 | assert!(mnemonics_ref.get_type() == entropy_ref.get_type()); 145 | 146 | assert!(entropy_ref.to_mnemonics() == mnemonics_ref); 147 | assert!( 148 | entropy_ref 149 | == Entropy::from_mnemonics(&mnemonics_ref) 150 | .expect("retrieve entropy from mnemonics") 151 | ); 152 | 153 | assert_eq!( 154 | seed_ref.as_ref(), 155 | Seed::from_mnemonic_string(&mnemonics_str, passphrase.as_bytes()).as_ref() 156 | ); 157 | } 158 | 159 | fn mk_tests(tests: &[TestVector], dic: &D) { 160 | for test in tests { 161 | mk_test(test, dic); 162 | } 163 | } 164 | 165 | #[test] 166 | fn test_vectors_english() { 167 | mk_tests(TEST_VECTORS_ENGLISH, &dictionary::ENGLISH) 168 | } 169 | #[test] 170 | fn test_vectors_japanese() { 171 | mk_tests(TEST_VECTORS_JAPANESE, &dictionary::JAPANESE) 172 | } 173 | 174 | const TEST_VECTORS_ENGLISH: &[TestVector] = &include!("test_vectors/bip39_english.txt"); 175 | const TEST_VECTORS_JAPANESE: &[TestVector] = &include!("test_vectors/bip39_japanese.txt"); 176 | } 177 | -------------------------------------------------------------------------------- /bip39/src/mnemonic.rs: -------------------------------------------------------------------------------- 1 | use zeroize::ZeroizeOnDrop; 2 | 3 | use crate::{dictionary, Error, Result, Type}; 4 | use std::{fmt, ops::Deref, str}; 5 | 6 | /// the maximum authorized value for a mnemonic. i.e. 2047 7 | pub const MAX_MNEMONIC_VALUE: u16 = 2047; 8 | 9 | /// Safe representation of a valid mnemonic index (see 10 | /// [`MAX_MNEMONIC_VALUE`](./constant.MAX_MNEMONIC_VALUE.html)). 11 | /// 12 | /// See [`dictionary module documentation`](./dictionary/index.html) for 13 | /// more details about how to use this. 14 | /// 15 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] 16 | pub struct MnemonicIndex(pub u16); 17 | 18 | /// Language agnostic mnemonic phrase representation. 19 | /// 20 | /// This is an handy intermediate representation of a given mnemonic 21 | /// phrase. One can use this intermediate representation to translate 22 | /// mnemonic from one [`Language`](./dictionary/trait.Language.html) 23 | /// to another. **However** keep in mind that the [`Seed`](./struct.Seed.html) 24 | /// is linked to the mnemonic string in a specific language, in a specific 25 | /// dictionary. The [`Entropy`](./struct.Entropy.html) will be the same 26 | /// but the resulted [`Seed`](./struct.Seed.html) will differ and all 27 | /// the derived key of a HDWallet using the [`Seed`](./struct.Seed.html) 28 | /// as a source to generate the root key. 29 | /// 30 | #[derive(Debug, PartialEq, Eq, Clone)] 31 | pub struct Mnemonics(Vec); 32 | 33 | /// Validated mnemonic words. This guarantee a given mnemonic phrase 34 | /// has been safely validated against a dictionary. 35 | /// 36 | /// See the module documentation for more details about how to use it 37 | /// within the `chain_wallet` library. 38 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, ZeroizeOnDrop)] 39 | pub struct MnemonicString(String); 40 | 41 | impl MnemonicString { 42 | /// create a `MnemonicString` from the given `String`. This function 43 | /// will validate the mnemonic phrase against the given [`Language`] 44 | /// 45 | /// [`Language`]: ./dictionary/trait.Language.html 46 | /// 47 | /// # Example 48 | /// 49 | /// ``` 50 | /// # use bip39::*; 51 | /// 52 | /// const MNEMONICS : &'static str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; 53 | /// let mnemonics = MnemonicString::new(&dictionary::ENGLISH, MNEMONICS.to_owned()) 54 | /// .expect("valid Mnemonic phrase"); 55 | /// ``` 56 | /// 57 | /// # Error 58 | /// 59 | /// This function may fail if one or all words are not recognized 60 | /// in the given [`Language`]. 61 | /// 62 | pub fn new(dic: &D, s: String) -> Result 63 | where 64 | D: dictionary::Language, 65 | { 66 | let _ = Mnemonics::from_string(dic, &s)?; 67 | 68 | Ok(MnemonicString(s)) 69 | } 70 | } 71 | 72 | impl MnemonicIndex { 73 | /// smart constructor, validate the given value fits the mnemonic index 74 | /// boundaries (see [`MAX_MNEMONIC_VALUE`](./constant.MAX_MNEMONIC_VALUE.html)). 75 | /// 76 | /// # Example 77 | /// 78 | /// ``` 79 | /// # use bip39::*; 80 | /// # 81 | /// let index = MnemonicIndex::new(1029); 82 | /// assert!(index.is_ok()); 83 | /// // this line will fail 84 | /// let index = MnemonicIndex::new(4029); 85 | /// assert_eq!(index, Err(Error::MnemonicOutOfBound(4029))); 86 | /// ``` 87 | /// 88 | /// # Error 89 | /// 90 | /// returns an [`Error::MnemonicOutOfBound`](enum.Error.html#variant.MnemonicOutOfBound) 91 | /// if the given value does not fit the valid values. 92 | /// 93 | pub fn new(m: u16) -> Result { 94 | if m <= MAX_MNEMONIC_VALUE { 95 | Ok(MnemonicIndex(m)) 96 | } else { 97 | Err(Error::MnemonicOutOfBound(m)) 98 | } 99 | } 100 | 101 | /// lookup in the given dictionary to retrieve the mnemonic word. 102 | /// 103 | /// # panic 104 | /// 105 | /// this function may panic if the 106 | /// [`Language::lookup_word`](./dictionary/trait.Language.html#method.lookup_word) 107 | /// returns an error. Which should not happen. 108 | /// 109 | pub fn to_word(self, dic: &D) -> String 110 | where 111 | D: dictionary::Language, 112 | { 113 | dic.lookup_word(self).unwrap() 114 | } 115 | 116 | /// retrieve the Mnemonic index from the given word in the 117 | /// given dictionary. 118 | /// 119 | /// # Error 120 | /// 121 | /// May fail with a [`LanguageError`](enum.Error.html#variant.LanguageError) 122 | /// if the given [`Language`](./dictionary/trait.Language.html) returns the 123 | /// given word is not within its dictionary. 124 | /// 125 | pub fn from_word(dic: &D, word: &str) -> Result 126 | where 127 | D: dictionary::Language, 128 | { 129 | let v = dic.lookup_mnemonic(word)?; 130 | Ok(v) 131 | } 132 | } 133 | 134 | impl Mnemonics { 135 | /// get the [`Type`](./enum.Type.html) of this given `Mnemonics`. 136 | /// 137 | /// # panic 138 | /// 139 | /// the only case this function may panic is if the `Mnemonics` has 140 | /// been badly constructed (i.e. not from one of the given smart 141 | /// constructor). 142 | /// 143 | pub fn get_type(&self) -> Type { 144 | Type::from_word_count(self.0.len()).unwrap() 145 | } 146 | 147 | /// get the mnemonic string representation in the given 148 | /// [`Language`](./dictionary/trait.Language.html). 149 | /// 150 | pub fn to_string(&self, dic: &D) -> MnemonicString 151 | where 152 | D: dictionary::Language, 153 | { 154 | let mut vec = String::new(); 155 | let mut first = true; 156 | for m in self.0.iter() { 157 | if first { 158 | first = false; 159 | } else { 160 | vec.push_str(dic.separator()); 161 | } 162 | vec.push_str(&m.to_word(dic)) 163 | } 164 | MnemonicString(vec) 165 | } 166 | 167 | /// Construct the `Mnemonics` from its string representation in the given 168 | /// [`Language`](./dictionary/trait.Language.html). 169 | /// 170 | /// # Error 171 | /// 172 | /// May fail with a [`LanguageError`](enum.Error.html#variant.LanguageError) 173 | /// if the given [`Language`](./dictionary/trait.Language.html) returns the 174 | /// given word is not within its dictionary. 175 | /// 176 | pub fn from_string(dic: &D, mnemonics: &str) -> Result 177 | where 178 | D: dictionary::Language, 179 | { 180 | let mut vec = vec![]; 181 | for word in mnemonics.split(dic.separator()) { 182 | vec.push(MnemonicIndex::from_word(dic, word)?); 183 | } 184 | Mnemonics::from_mnemonics(vec) 185 | } 186 | 187 | /// Construct the `Mnemonics` from the given array of `MnemonicIndex`. 188 | /// 189 | /// # Error 190 | /// 191 | /// May fail if this is an invalid number of `MnemonicIndex`. 192 | /// 193 | pub fn from_mnemonics(mnemonics: Vec) -> Result { 194 | let _ = Type::from_word_count(mnemonics.len())?; 195 | Ok(Mnemonics(mnemonics)) 196 | } 197 | 198 | pub(crate) fn iter(&self) -> impl Iterator { 199 | self.0.iter() 200 | } 201 | } 202 | 203 | impl AsRef<[MnemonicIndex]> for Mnemonics { 204 | fn as_ref(&self) -> &[MnemonicIndex] { 205 | &self.0[..] 206 | } 207 | } 208 | 209 | impl Deref for MnemonicString { 210 | type Target = str; 211 | fn deref(&self) -> &Self::Target { 212 | self.0.deref() 213 | } 214 | } 215 | 216 | impl fmt::Display for MnemonicString { 217 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 218 | write!(f, "{}", self.0) 219 | } 220 | } 221 | 222 | impl Drop for Mnemonics { 223 | fn drop(&mut self) { 224 | for byte in self.0.iter_mut() { 225 | *byte = MnemonicIndex(0); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /bip39/src/seed.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, MnemonicString, Result}; 2 | use cryptoxide::hmac::Hmac; 3 | use cryptoxide::pbkdf2::pbkdf2; 4 | use cryptoxide::sha2::Sha512; 5 | use std::ops::Deref; 6 | 7 | /// the expected size of a seed, in bytes. 8 | pub const SEED_SIZE: usize = 64; 9 | 10 | /// A BIP39 `Seed` object, will be used to generate a given HDWallet 11 | /// root key. 12 | /// 13 | /// See the module documentation for more details about how to use it 14 | /// within the `chain_wallet` library. 15 | #[derive(zeroize::ZeroizeOnDrop)] 16 | pub struct Seed([u8; SEED_SIZE]); 17 | 18 | impl Seed { 19 | /// create a Seed by taking ownership of the given array 20 | /// 21 | /// # Example 22 | /// 23 | /// ``` 24 | /// use bip39::*; 25 | /// 26 | /// let bytes = [0u8;SEED_SIZE]; 27 | /// let seed = Seed::from_bytes(bytes); 28 | /// 29 | /// assert!(seed.as_ref().len() == SEED_SIZE); 30 | /// ``` 31 | pub fn from_bytes(buf: [u8; SEED_SIZE]) -> Self { 32 | Seed(buf) 33 | } 34 | 35 | /// create a Seed by copying the given slice into a new array 36 | /// 37 | /// # Example 38 | /// 39 | /// ``` 40 | /// use bip39::*; 41 | /// 42 | /// let bytes = [0u8;SEED_SIZE]; 43 | /// let wrong = [0u8;31]; 44 | /// 45 | /// assert!(Seed::from_slice(&wrong[..]).is_err()); 46 | /// assert!(Seed::from_slice(&bytes[..]).is_ok()); 47 | /// ``` 48 | /// 49 | /// # Error 50 | /// 51 | /// This constructor may fail if the given slice's length is not 52 | /// compatible to define a `Seed` (see [`SEED_SIZE`](./constant.SEED_SIZE.html)). 53 | /// 54 | pub fn from_slice(buf: &[u8]) -> Result { 55 | if buf.len() != SEED_SIZE { 56 | return Err(Error::InvalidSeedSize(buf.len())); 57 | } 58 | let mut v = [0u8; SEED_SIZE]; 59 | v[..].clone_from_slice(buf); 60 | Ok(Seed::from_bytes(v)) 61 | } 62 | 63 | /// get the seed from the given [`MnemonicString`] and the given password. 64 | /// 65 | /// [`MnemonicString`]: ./struct.MnemonicString.html 66 | /// 67 | /// Note that the `Seed` is not generated from the `Entropy` directly. It is a 68 | /// design choice of Bip39. 69 | /// 70 | /// # Safety 71 | /// 72 | /// The password is meant to allow plausible deniability. While it is possible 73 | /// not to use a password to protect the HDWallet it is better to add one. 74 | /// 75 | /// # Example 76 | /// 77 | /// ``` 78 | /// # use bip39::*; 79 | /// 80 | /// const MNEMONICS : &'static str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; 81 | /// let mnemonics = MnemonicString::new(&dictionary::ENGLISH, MNEMONICS.to_owned()) 82 | /// .expect("valid Mnemonic phrase"); 83 | /// 84 | /// let seed = Seed::from_mnemonic_string(&mnemonics, b"Bourbaki team rocks!"); 85 | /// ``` 86 | /// 87 | pub fn from_mnemonic_string(mnemonics: &MnemonicString, password: &[u8]) -> Self { 88 | let mut salt = Vec::from("mnemonic"); 89 | salt.extend_from_slice(password); 90 | let mut mac = Hmac::new(Sha512::new(), mnemonics.as_bytes()); 91 | let mut result = [0; SEED_SIZE]; 92 | pbkdf2(&mut mac, &salt, 2048, &mut result); 93 | Self::from_bytes(result) 94 | } 95 | } 96 | 97 | impl PartialEq for Seed { 98 | fn eq(&self, other: &Self) -> bool { 99 | self.0.as_ref() == other.0.as_ref() 100 | } 101 | } 102 | 103 | impl AsRef<[u8]> for Seed { 104 | fn as_ref(&self) -> &[u8] { 105 | &self.0 106 | } 107 | } 108 | 109 | impl Deref for Seed { 110 | type Target = [u8]; 111 | fn deref(&self) -> &Self::Target { 112 | self.as_ref() 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /bip39/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use std::{fmt, result, str}; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum ParseTypeError { 7 | #[error("Expecting a number")] 8 | NaN( 9 | #[source] 10 | #[from] 11 | std::num::ParseIntError, 12 | ), 13 | #[error("Not a valid number of mnemonic, expected one of [9, 12, 15, 18, 21, 24]")] 14 | InvalidNumber, 15 | } 16 | 17 | /// The support type of `Mnemonics`, i.e. the number of words supported in a 18 | /// mnemonic phrase. 19 | /// 20 | /// This enum provide the following properties: 21 | /// 22 | /// | number of words | entropy size (bits) | checksum size (bits) | 23 | /// | --------------- | ------------------- | --------------------- | 24 | /// | 9 | 96 | 3 | 25 | /// | 12 | 128 | 4 | 26 | /// | 15 | 160 | 5 | 27 | /// | 18 | 192 | 6 | 28 | /// | 21 | 224 | 7 | 29 | /// | 24 | 256 | 8 | 30 | /// 31 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 32 | pub enum Type { 33 | Type9Words, 34 | Type12Words, 35 | Type15Words, 36 | Type18Words, 37 | Type21Words, 38 | Type24Words, 39 | } 40 | impl Type { 41 | pub fn from_word_count(len: usize) -> Result { 42 | match len { 43 | 9 => Ok(Type::Type9Words), 44 | 12 => Ok(Type::Type12Words), 45 | 15 => Ok(Type::Type15Words), 46 | 18 => Ok(Type::Type18Words), 47 | 21 => Ok(Type::Type21Words), 48 | 24 => Ok(Type::Type24Words), 49 | _ => Err(Error::WrongNumberOfWords(len)), 50 | } 51 | } 52 | 53 | pub fn from_entropy_size(len: usize) -> Result { 54 | match len { 55 | 96 => Ok(Type::Type9Words), 56 | 128 => Ok(Type::Type12Words), 57 | 160 => Ok(Type::Type15Words), 58 | 192 => Ok(Type::Type18Words), 59 | 224 => Ok(Type::Type21Words), 60 | 256 => Ok(Type::Type24Words), 61 | _ => Err(Error::WrongKeySize(len)), 62 | } 63 | } 64 | 65 | pub fn to_key_size(self) -> usize { 66 | match self { 67 | Type::Type9Words => 96, 68 | Type::Type12Words => 128, 69 | Type::Type15Words => 160, 70 | Type::Type18Words => 192, 71 | Type::Type21Words => 224, 72 | Type::Type24Words => 256, 73 | } 74 | } 75 | 76 | pub fn checksum_size_bits(self) -> usize { 77 | match self { 78 | Type::Type9Words => 3, 79 | Type::Type12Words => 4, 80 | Type::Type15Words => 5, 81 | Type::Type18Words => 6, 82 | Type::Type21Words => 7, 83 | Type::Type24Words => 8, 84 | } 85 | } 86 | 87 | pub fn mnemonic_count(self) -> usize { 88 | match self { 89 | Type::Type9Words => 9, 90 | Type::Type12Words => 12, 91 | Type::Type15Words => 15, 92 | Type::Type18Words => 18, 93 | Type::Type21Words => 21, 94 | Type::Type24Words => 24, 95 | } 96 | } 97 | } 98 | 99 | impl Default for Type { 100 | fn default() -> Type { 101 | Type::Type24Words 102 | } 103 | } 104 | 105 | impl fmt::Display for Type { 106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 | match self { 108 | Type::Type9Words => 9.fmt(f), 109 | Type::Type12Words => 12.fmt(f), 110 | Type::Type15Words => 15.fmt(f), 111 | Type::Type18Words => 18.fmt(f), 112 | Type::Type21Words => 21.fmt(f), 113 | Type::Type24Words => 24.fmt(f), 114 | } 115 | } 116 | } 117 | 118 | impl str::FromStr for Type { 119 | type Err = ParseTypeError; 120 | fn from_str(s: &str) -> result::Result { 121 | let i = s.parse()?; 122 | match i { 123 | 9 => Ok(Type::Type9Words), 124 | 12 => Ok(Type::Type12Words), 125 | 15 => Ok(Type::Type15Words), 126 | 18 => Ok(Type::Type18Words), 127 | 21 => Ok(Type::Type21Words), 128 | 24 => Ok(Type::Type24Words), 129 | _ => Err(ParseTypeError::InvalidNumber), 130 | } 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::*; 137 | use quickcheck::{Arbitrary, Gen}; 138 | 139 | impl Arbitrary for Type { 140 | fn arbitrary(g: &mut G) -> Self { 141 | use Type::*; 142 | const VALUES: &[Type] = &[ 143 | Type9Words, 144 | Type12Words, 145 | Type15Words, 146 | Type18Words, 147 | Type21Words, 148 | Type24Words, 149 | ]; 150 | let v = usize::arbitrary(g) % VALUES.len(); 151 | VALUES[v] 152 | } 153 | } 154 | 155 | #[test] 156 | fn to_string() { 157 | assert_eq!(Type::Type9Words.to_string(), "9"); 158 | assert_eq!(Type::Type12Words.to_string(), "12"); 159 | assert_eq!(Type::Type15Words.to_string(), "15"); 160 | assert_eq!(Type::Type18Words.to_string(), "18"); 161 | assert_eq!(Type::Type21Words.to_string(), "21"); 162 | assert_eq!(Type::Type24Words.to_string(), "24"); 163 | } 164 | 165 | #[quickcheck] 166 | fn fmt_parse(t: Type) -> bool { 167 | let s = t.to_string(); 168 | let v = s.parse::().unwrap(); 169 | 170 | v == t 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /chain-path-derivation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chain-path-derivation" 3 | version = "0.1.0" 4 | authors = ["Nicolas Di Prima ", "Vincent Hanquez "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | thiserror = { version = "1.0.13", default-features = false } 10 | 11 | [dev-dependencies] 12 | quickcheck = "0.9" 13 | quickcheck_macros = "0.9" 14 | paste = "0.1.8" -------------------------------------------------------------------------------- /chain-path-derivation/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /chain-path-derivation/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /chain-path-derivation/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! BIP44 addressing 2 | //! 3 | //! provides all the logic to create safe sequential addresses 4 | //! using BIP44 specification. 5 | //! 6 | 7 | #[cfg(test)] 8 | extern crate quickcheck; 9 | #[cfg(test)] 10 | #[macro_use(quickcheck)] 11 | extern crate quickcheck_macros; 12 | 13 | pub mod bip44; 14 | mod derivation; 15 | mod derivation_path; 16 | pub mod rindex; 17 | 18 | pub use self::{ 19 | derivation::{ 20 | Derivation, DerivationError, DerivationRange, HardDerivation, HardDerivationRange, 21 | ParseDerivationError, SoftDerivation, SoftDerivationRange, 22 | }, 23 | derivation_path::{AnyScheme, DerivationPath, DerivationPathRange, ParseDerivationPathError}, 24 | }; 25 | -------------------------------------------------------------------------------- /chain-path-derivation/src/rindex.rs: -------------------------------------------------------------------------------- 1 | //! # Random Index scheme 2 | //! 3 | //! here the address is supposed to be 2 level only. It is not clear 4 | //! what is supposed to be the structure of the addressing so no assumption 5 | //! is made here. 6 | //! 7 | //! assumptions is that the two level of derivations are: `m / account / address` 8 | //! 9 | 10 | use crate::{AnyScheme, Derivation, DerivationPath, ParseDerivationPathError}; 11 | use std::str::{self, FromStr}; 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub struct Rindex

(std::marker::PhantomData

); 15 | 16 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub struct Root; 18 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 19 | pub struct Account; 20 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 21 | pub struct Address; 22 | 23 | const INDEX_ACCOUNT: usize = 0; 24 | const INDEX_ADDRESS: usize = 1; 25 | 26 | #[inline] 27 | pub fn new() -> DerivationPath> { 28 | DerivationPath::new_empty() 29 | } 30 | 31 | impl DerivationPath> { 32 | pub fn account(&self, derivation: Derivation) -> DerivationPath> { 33 | let mut a = self.clone(); 34 | a.push(derivation); 35 | a.coerce_unchecked() 36 | } 37 | } 38 | 39 | impl DerivationPath> { 40 | pub fn address(&self, derivation: Derivation) -> DerivationPath> { 41 | let mut a = self.clone(); 42 | a.push(derivation); 43 | a.coerce_unchecked() 44 | } 45 | } 46 | 47 | impl DerivationPath> { 48 | #[inline] 49 | pub fn account(&self) -> Derivation { 50 | self.get_unchecked(INDEX_ACCOUNT) 51 | } 52 | #[inline] 53 | pub fn address(&self) -> Derivation { 54 | self.get_unchecked(INDEX_ADDRESS) 55 | } 56 | } 57 | 58 | /* FromStr ***************************************************************** */ 59 | 60 | macro_rules! mk_from_str_dp_rindex { 61 | ($t:ty, $len:expr) => { 62 | impl FromStr for DerivationPath<$t> { 63 | type Err = ParseDerivationPathError; 64 | 65 | fn from_str(s: &str) -> Result { 66 | let dp = s.parse::>()?; 67 | 68 | if dp.len() == $len { 69 | Ok(dp.coerce_unchecked()) 70 | } else { 71 | Err(ParseDerivationPathError::InvalidNumberOfDerivations { 72 | actual: dp.len(), 73 | expected: $len, 74 | }) 75 | } 76 | } 77 | } 78 | }; 79 | } 80 | 81 | mk_from_str_dp_rindex!(Rindex, 0); 82 | mk_from_str_dp_rindex!(Rindex, 1); 83 | mk_from_str_dp_rindex!(Rindex

, 2); 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use quickcheck::{Arbitrary, Gen}; 89 | 90 | macro_rules! mk_arbitrary_dp_rindex { 91 | ($t:ty, $len:expr) => { 92 | impl Arbitrary for DerivationPath<$t> { 93 | fn arbitrary(g: &mut G) -> Self { 94 | let dp = std::iter::repeat_with(|| Derivation::arbitrary(g)) 95 | .take($len) 96 | .collect::>(); 97 | dp.coerce_unchecked() 98 | } 99 | } 100 | }; 101 | } 102 | 103 | mk_arbitrary_dp_rindex!(Rindex, 0); 104 | mk_arbitrary_dp_rindex!(Rindex, 1); 105 | mk_arbitrary_dp_rindex!(Rindex
, 2); 106 | 107 | macro_rules! mk_quickcheck_dp_rindex { 108 | ($t:ty) => { 109 | paste::item! { 110 | #[quickcheck] 111 | #[allow(non_snake_case)] 112 | fn [< fmt_parse $t>](derivation_path: DerivationPath>) -> bool { 113 | let s = derivation_path.to_string(); 114 | let v = s.parse::>>().unwrap(); 115 | 116 | v == derivation_path 117 | } 118 | } 119 | }; 120 | } 121 | 122 | mk_quickcheck_dp_rindex!(Root); 123 | mk_quickcheck_dp_rindex!(Account); 124 | mk_quickcheck_dp_rindex!(Address); 125 | } 126 | -------------------------------------------------------------------------------- /doc/EME.md: -------------------------------------------------------------------------------- 1 | ### Enhanced Mnemonic Encoding (EME) -- Draft 2 | 3 | We add an extra mnemonic word to the encoding, this allows us to have an extra 4 | 11 bits for the scheme metadata. The EME scheme: 5 | 6 | * Improves the detection and versioning compared to the standard BIP39 7 | * EME mnemonics is not accepted by a compliant BIP39 implementation by always having 1 extra word 8 | * EME mnemonics maps trivially to a BIP39 mnemonics by stripping the first word. 9 | 10 | The first 2 bits are defined for the type, which given different type imply 11 | different parsing of the following bits 12 | 13 | * 2 bits : where the value represent: 14 | * 0: wallet key seed 15 | * 1: reserved for paper wallet 16 | * 2: reserved for extension. UX should report unknown type 17 | * 3: reversed for extension. UX should report unknown type 18 | 19 | The following apply to wallet key seed: 20 | 21 | * 2 bits : master key derivation version. 22 | * Version 0: use the scheme defined below 23 | * 3 bits : mnemonic size (invalid=0b000,12=0b001, 15=0b010, 18=0b011, 21=0b100, 24=0b101, and by extension: 27=0b110, 30=0b111) 24 | * 4 bits : checksum of the first 2 words (22bits) 0b1111. How is it computed ? 25 | 26 | The mnemonic size allows after the words to determine the number of words are 27 | expected for this instance. This can be used for the UI either during the 28 | process of filling the mnemonic or at the end to do simple validation. 29 | 30 | The 2 words checksum allows for early detection of the validity of the EME. 31 | In the UX, on the 3rd word entered, early checksuming can allow detection, with 32 | some margin of error, whether or not the EME encoding is valid. This allow for 33 | extra feedback during the UX, and help detect classic bip39 scheme from EME 34 | scheme. 35 | 36 | ``` 37 | TODO test vectors 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /hdkeygen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hdkeygen" 3 | version = "0.2.0" 4 | authors = ["Nicolas Di Prima ", "Vincent Hanquez "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | cryptoxide = "0.4.2" 10 | ed25519-bip32 = "0.4.0" 11 | bip39 = { path = "../bip39" } 12 | cbor_event = "^2.1.3" 13 | thiserror = { version = "1.0.13", default-features = false } 14 | chain-path-derivation = { path = "../chain-path-derivation" } 15 | hex = "0.4.2" 16 | zeroize = "1.5.3" 17 | 18 | chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 19 | chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 20 | cardano-legacy-address = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 21 | 22 | [dev-dependencies] 23 | quickcheck = "0.9" 24 | quickcheck_macros = "0.9" 25 | -------------------------------------------------------------------------------- /hdkeygen/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /hdkeygen/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /hdkeygen/src/account.rs: -------------------------------------------------------------------------------- 1 | //! account based wallet, does not really have any Hierarchical element in it 2 | //! thought it is part of the `keygen` entity so adding it in 3 | //! 4 | //! On the chain, an account can group stake while a rindex or a bip44 cannot 5 | //! as they represent individual coins, which have stake but they are not grouped 6 | //! and cannot be be controlled without having an account to group them 7 | 8 | use chain_addr::{Address, Discrimination, Kind}; 9 | use chain_crypto::{AsymmetricKey, Ed25519, Ed25519Extended, PublicKey, SecretKey}; 10 | use cryptoxide::ed25519::{self, PRIVATE_KEY_LENGTH}; 11 | use std::{ 12 | convert::TryInto, 13 | fmt::{self, Display}, 14 | str::FromStr, 15 | }; 16 | 17 | pub type Seed = [u8; PRIVATE_KEY_LENGTH]; 18 | 19 | pub struct Account { 20 | secret: SecretKey, 21 | counter: u32, 22 | } 23 | 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 25 | pub struct AccountId { 26 | id: [u8; AccountId::SIZE], 27 | } 28 | 29 | impl Account { 30 | pub fn from_seed(seed: Seed) -> Self { 31 | let secret = SecretKey::::from_binary(&seed).unwrap(); 32 | Account { secret, counter: 0 } 33 | } 34 | } 35 | 36 | impl Account { 37 | pub fn from_secret_key(key: SecretKey) -> Self { 38 | Account { 39 | secret: key, 40 | counter: 0, 41 | } 42 | } 43 | } 44 | 45 | impl Account { 46 | pub fn account_id(&self) -> AccountId { 47 | AccountId { id: self.public() } 48 | } 49 | 50 | pub fn public(&self) -> [u8; AccountId::SIZE] { 51 | self.secret.to_public().as_ref().try_into().unwrap() 52 | } 53 | 54 | /// get the transaction counter 55 | /// 56 | /// this is the counter for the number of times a transaction has been successfully sent 57 | /// to the network with this account. It is used to sign transactions so it is important 58 | /// to keep it up to date as much as possible. 59 | pub fn counter(&self) -> u32 { 60 | self.counter 61 | } 62 | 63 | pub fn set_counter(&mut self, counter: u32) { 64 | self.counter = counter; 65 | } 66 | 67 | /// increase the counter with the given amount 68 | pub fn increase_counter(&mut self, atm: u32) { 69 | self.counter += atm 70 | } 71 | 72 | pub fn secret(&self) -> &SecretKey { 73 | &self.secret 74 | } 75 | 76 | // pub fn seed(&self) -> &SEED { 77 | // &self.seed 78 | // } 79 | } 80 | 81 | impl AccountId { 82 | /// the total size of an account ID 83 | pub const SIZE: usize = ed25519::PUBLIC_KEY_LENGTH; 84 | 85 | /// get the public address associated to this account identifier 86 | pub fn address(&self, discrimination: Discrimination) -> Address { 87 | let pk = if let Ok(pk) = PublicKey::from_binary(&self.id) { 88 | pk 89 | } else { 90 | unsafe { std::hint::unreachable_unchecked() } 91 | }; 92 | let kind = Kind::Account(pk); 93 | 94 | Address(discrimination, kind) 95 | } 96 | } 97 | 98 | // impl Drop for Account { 99 | // fn drop(&mut self) { 100 | // cryptoxide::util::secure_memset(&mut self.seed, 0) 101 | // } 102 | // } 103 | 104 | /* Conversion ************************************************************** */ 105 | 106 | // impl From<[u8; SEED_LENGTH]> for Account { 107 | // fn from(seed: [u8; SEED_LENGTH]) -> Self { 108 | // Self { seed, counter: 0 } 109 | // } 110 | // } 111 | 112 | impl From<[u8; Self::SIZE]> for AccountId { 113 | fn from(id: [u8; Self::SIZE]) -> Self { 114 | Self { id } 115 | } 116 | } 117 | 118 | impl From for PublicKey { 119 | fn from(account: AccountId) -> PublicKey { 120 | if let Ok(pk) = PublicKey::from_binary(&account.id) { 121 | pk 122 | } else { 123 | unsafe { std::hint::unreachable_unchecked() } 124 | } 125 | } 126 | } 127 | 128 | impl AsRef<[u8]> for AccountId { 129 | fn as_ref(&self) -> &[u8] { 130 | self.id.as_ref() 131 | } 132 | } 133 | 134 | /* Display ***************************************************************** */ 135 | 136 | impl Display for AccountId { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | hex::encode(&self.id).fmt(f) 139 | } 140 | } 141 | 142 | impl FromStr for AccountId { 143 | type Err = hex::FromHexError; 144 | fn from_str(s: &str) -> Result { 145 | let mut id = [0; Self::SIZE]; 146 | 147 | hex::decode_to_slice(s, &mut id)?; 148 | 149 | Ok(Self { id }) 150 | } 151 | } 152 | 153 | #[cfg(test)] 154 | mod tests { 155 | use super::*; 156 | use quickcheck::{Arbitrary, Gen}; 157 | 158 | impl Clone for Account { 159 | fn clone(&self) -> Self { 160 | Self { 161 | secret: self.secret.clone(), 162 | counter: self.counter, 163 | } 164 | } 165 | } 166 | 167 | impl Arbitrary for Account { 168 | fn arbitrary(g: &mut G) -> Self { 169 | let mut seed = [0; PRIVATE_KEY_LENGTH]; 170 | g.fill_bytes(&mut seed); 171 | Self::from_seed(seed) 172 | } 173 | } 174 | 175 | impl Arbitrary for AccountId { 176 | fn arbitrary(g: &mut G) -> Self { 177 | let mut id = [0; Self::SIZE]; 178 | g.fill_bytes(&mut id); 179 | Self { id } 180 | } 181 | } 182 | 183 | #[quickcheck] 184 | fn account_id_to_string_parse(account_id: AccountId) -> bool { 185 | let s = account_id.to_string(); 186 | let decoded = s.parse().unwrap(); 187 | 188 | account_id == decoded 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /hdkeygen/src/bip44.rs: -------------------------------------------------------------------------------- 1 | use crate::{Key, KeyRange}; 2 | use chain_addr::{Discrimination, Kind}; 3 | use chain_path_derivation::{ 4 | bip44::{self, Bip44}, 5 | Derivation, DerivationPath, HardDerivation, SoftDerivation, SoftDerivationRange, 6 | }; 7 | use ed25519_bip32::{XPrv, XPub}; 8 | 9 | impl Key> { 10 | pub fn purpose(&self, derivation: HardDerivation) -> Key> { 11 | self.derive_unchecked(derivation.into()) 12 | } 13 | 14 | pub fn bip44(&self) -> Key> { 15 | self.purpose(bip44::PURPOSE_BIP44) 16 | } 17 | 18 | pub fn chimeric_bip44(&self) -> Key> { 19 | self.purpose(bip44::PURPOSE_CHIMERIC) 20 | } 21 | } 22 | 23 | impl Key> { 24 | pub fn coin_type(&self, derivation: HardDerivation) -> Key> { 25 | self.derive_unchecked(derivation.into()) 26 | } 27 | 28 | pub fn cardano(&self) -> Key> { 29 | const COIN_TYPE: HardDerivation = 30 | HardDerivation::new_unchecked(Derivation::new(0x8000_0717)); 31 | self.coin_type(COIN_TYPE) 32 | } 33 | } 34 | 35 | impl Key> { 36 | pub fn account(&self, derivation: HardDerivation) -> Key> { 37 | self.derive_unchecked(derivation.into()) 38 | } 39 | } 40 | 41 | impl Key> { 42 | const EXTERNAL: SoftDerivation = DerivationPath::>::EXTERNAL; 43 | const INTERNAL: SoftDerivation = DerivationPath::>::INTERNAL; 44 | const ACCOUNT: SoftDerivation = DerivationPath::>::ACCOUNT; 45 | 46 | pub fn id(&self) -> HardDerivation { 47 | self.path().account() 48 | } 49 | } 50 | 51 | impl Key> { 52 | pub fn change(&self, derivation: SoftDerivation) -> Key> { 53 | self.derive_unchecked(derivation.into()) 54 | } 55 | 56 | pub fn external(&self) -> Key> { 57 | self.change(Self::EXTERNAL) 58 | } 59 | 60 | pub fn internal(&self) -> Key> { 61 | self.change(Self::INTERNAL) 62 | } 63 | 64 | pub fn account(&self) -> Key> { 65 | self.change(Self::ACCOUNT) 66 | } 67 | } 68 | 69 | impl Key> { 70 | pub fn change(&self, derivation: SoftDerivation) -> Key> { 71 | self.derive_unchecked(derivation) 72 | } 73 | 74 | pub fn external(&self) -> Key> { 75 | self.change(Self::EXTERNAL) 76 | } 77 | 78 | pub fn internal(&self) -> Key> { 79 | self.change(Self::INTERNAL) 80 | } 81 | 82 | pub fn account(&self) -> Key> { 83 | self.change(Self::ACCOUNT) 84 | } 85 | } 86 | 87 | impl Key> { 88 | pub fn address(&self, derivation: SoftDerivation) -> Key> { 89 | self.derive_unchecked(derivation.into()) 90 | } 91 | } 92 | 93 | impl Key> { 94 | pub fn address(&self, derivation: SoftDerivation) -> Key> { 95 | self.derive_unchecked(derivation) 96 | } 97 | 98 | pub fn addresses( 99 | &self, 100 | range: SoftDerivationRange, 101 | ) -> KeyRange, Bip44> { 102 | KeyRange::new(self, range) 103 | } 104 | } 105 | 106 | impl Key> { 107 | pub fn address_single(&self, discrimination: Discrimination) -> chain_addr::Address { 108 | let pk = self.pk(); 109 | let kind = Kind::Single(pk); 110 | 111 | chain_addr::Address(discrimination, kind) 112 | } 113 | 114 | pub fn address_account(&self, discrimination: Discrimination) -> chain_addr::Address { 115 | let pk = self.pk(); 116 | let kind = Kind::Account(pk); 117 | 118 | chain_addr::Address(discrimination, kind) 119 | } 120 | 121 | pub fn address_group( 122 | &self, 123 | discrimination: Discrimination, 124 | group: chain_crypto::PublicKey, 125 | ) -> chain_addr::Address { 126 | let pk = self.pk(); 127 | let kind = Kind::Group(pk, group); 128 | 129 | chain_addr::Address(discrimination, kind) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /hdkeygen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | extern crate quickcheck; 3 | #[cfg(test)] 4 | #[macro_use(quickcheck)] 5 | extern crate quickcheck_macros; 6 | 7 | pub mod account; 8 | pub mod bip44; 9 | mod key; 10 | pub mod rindex; 11 | 12 | pub use self::key::{Key, KeyRange}; 13 | -------------------------------------------------------------------------------- /hdkeygen/src/rindex/mod.rs: -------------------------------------------------------------------------------- 1 | //! random indexes wallet - 2 Level of randomly chosen hard derivation indexes Wallet 2 | 3 | mod hdpayload; 4 | 5 | use crate::Key; 6 | use chain_path_derivation::{ 7 | rindex::{self, Rindex}, 8 | DerivationPath, 9 | }; 10 | use ed25519_bip32::XPrv; 11 | pub use hdpayload::{decode_derivation_path, HdKey}; 12 | 13 | impl Key> { 14 | pub fn key( 15 | &self, 16 | derivation_path: &DerivationPath>, 17 | ) -> Key> { 18 | self.derive_path_unchecked(derivation_path) 19 | } 20 | 21 | /// get an address recovering object, this object can be used to check the 22 | /// ownership of addresses 23 | pub fn hd_key(&self) -> HdKey { 24 | HdKey::new(self.public().public_key()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["plugins/markdown"] 3 | } -------------------------------------------------------------------------------- /symmetric-cipher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nicolas Di Prima ", "Vincent Hanquez ", "Enzo Cioppettini ", "Vincent Hanquez "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | cryptoxide = "0.4.2" 10 | ed25519-bip32 = "0.4.0" 11 | cbor_event = "^2.1.3" 12 | thiserror = { version = "1.0.13", default-features = false } 13 | bip39 = { path = "../bip39" } 14 | chain-path-derivation = { path = "../chain-path-derivation" } 15 | hdkeygen = { path = "../hdkeygen" } 16 | hex = "0.4.2" 17 | itertools = "0.9" 18 | hashlink = "0.7.0" 19 | zeroize = "1.5.3" 20 | 21 | chain-time = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 22 | chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 23 | chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 24 | chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 25 | cardano-legacy-address = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 26 | imhamt = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 27 | 28 | [dev-dependencies] 29 | quickcheck = "0.9" 30 | quickcheck_macros = "0.9" 31 | chain-ser = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" } 32 | -------------------------------------------------------------------------------- /wallet/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /wallet/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Input Output HK 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /wallet/src/blockchain.rs: -------------------------------------------------------------------------------- 1 | use chain_addr::Discrimination; 2 | use chain_impl_mockchain::{ 3 | block::Block, 4 | config::{Block0Date, ConfigParam}, 5 | fee::{FeeAlgorithm as _, LinearFee}, 6 | fragment::Fragment, 7 | header::HeaderId, 8 | ledger::{Block0Error, Error, Ledger}, 9 | transaction::Input, 10 | }; 11 | use chain_time::TimeEra; 12 | 13 | #[derive(Clone)] 14 | pub struct Settings { 15 | pub fees: LinearFee, 16 | pub discrimination: Discrimination, 17 | pub block0_initial_hash: HeaderId, 18 | pub block0_date: Block0Date, 19 | pub slot_duration: u8, 20 | pub time_era: TimeEra, 21 | pub transaction_max_expiry_epochs: u8, 22 | } 23 | 24 | impl Settings { 25 | pub fn new(block: &Block) -> Result { 26 | let header_id = block.header().id(); 27 | let ledger = Ledger::new(header_id, block.contents().iter())?; 28 | 29 | let static_parameters = ledger.get_static_parameters().clone(); 30 | let parameters = ledger.settings(); 31 | 32 | // TODO: I think there is a bug in Ledger::new(), as it doesn't set the slot_duration in 33 | // the Settings. 34 | // This doesn't seem to matter for jormungandr anyway, because it gets the initial value 35 | // directly from the block0, and then just doesn't use the field in the settings anymore. 36 | // For now, just get the setting directly from the block0 here too, but should probably be 37 | // fixed in Ledger::new (or at least checked). 38 | let mut slot_duration = None; 39 | 40 | for fragment in block.contents().iter() { 41 | if let Fragment::Initial(initials) = fragment { 42 | for initial in initials.iter() { 43 | match initial { 44 | ConfigParam::Block0Date(_) => {} 45 | ConfigParam::Discrimination(_) => {} 46 | ConfigParam::ConsensusVersion(_) => {} 47 | ConfigParam::SlotsPerEpoch(_) => {} 48 | ConfigParam::SlotDuration(sd) => { 49 | slot_duration.replace(sd); 50 | } 51 | ConfigParam::EpochStabilityDepth(_) => {} 52 | ConfigParam::ConsensusGenesisPraosActiveSlotsCoeff(_) => {} 53 | ConfigParam::BlockContentMaxSize(_) => {} 54 | ConfigParam::AddBftLeader(_) => {} 55 | ConfigParam::RemoveBftLeader(_) => {} 56 | ConfigParam::LinearFee(_) => {} 57 | ConfigParam::ProposalExpiration(_) => {} 58 | ConfigParam::KesUpdateSpeed(_) => {} 59 | ConfigParam::TreasuryAdd(_) => {} 60 | ConfigParam::TreasuryParams(_) => {} 61 | ConfigParam::RewardPot(_) => {} 62 | ConfigParam::RewardParams(_) => {} 63 | ConfigParam::PerCertificateFees(_) => {} 64 | ConfigParam::FeesInTreasury(_) => {} 65 | ConfigParam::RewardLimitNone => {} 66 | ConfigParam::RewardLimitByAbsoluteStake(_) => {} 67 | ConfigParam::PoolRewardParticipationCapping(_) => {} 68 | ConfigParam::AddCommitteeId(_) => {} 69 | ConfigParam::RemoveCommitteeId(_) => {} 70 | ConfigParam::PerVoteCertificateFees(_) => {} 71 | ConfigParam::TransactionMaxExpiryEpochs(_) => {} 72 | } 73 | } 74 | } 75 | } 76 | 77 | Ok(Self { 78 | fees: parameters.linear_fees.clone(), 79 | discrimination: static_parameters.discrimination, 80 | block0_initial_hash: static_parameters.block0_initial_hash, 81 | block0_date: static_parameters.block0_start_time, 82 | slot_duration: *slot_duration 83 | .ok_or(Error::Block0(Block0Error::InitialMessageNoSlotDuration))?, 84 | time_era: ledger.era().clone(), 85 | transaction_max_expiry_epochs: ledger.settings().transaction_max_expiry_epochs, 86 | }) 87 | } 88 | 89 | /// convenient function to check if a given input 90 | /// is covering at least its own input fees for a given transaction 91 | pub fn is_input_worth(&self, input: &Input) -> bool { 92 | let value = input.value(); 93 | let minimal_value = self.fees.fees_for_inputs_outputs(1, 0); 94 | 95 | value > minimal_value 96 | } 97 | 98 | pub fn discrimination(&self) -> Discrimination { 99 | self.discrimination 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /wallet/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod account; 2 | mod blockchain; 3 | mod password; 4 | mod scheme; 5 | mod states; 6 | pub mod time; 7 | pub mod transaction; 8 | 9 | pub use self::{ 10 | account::{Wallet, MAX_LANES}, 11 | blockchain::Settings, 12 | password::{Password, ScrubbedBytes}, 13 | transaction::{AccountWitnessBuilder, TransactionBuilder}, 14 | }; 15 | pub use hdkeygen::account::AccountId; 16 | -------------------------------------------------------------------------------- /wallet/src/password.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | #[derive(Debug, Default, Clone, zeroize::ZeroizeOnDrop)] 4 | pub struct ScrubbedBytes(Vec); 5 | 6 | pub type Password = ScrubbedBytes; 7 | 8 | impl From> for ScrubbedBytes { 9 | fn from(v: Vec) -> Self { 10 | Self(v) 11 | } 12 | } 13 | 14 | impl From for ScrubbedBytes { 15 | fn from(v: String) -> Self { 16 | Self(v.into_bytes()) 17 | } 18 | } 19 | 20 | impl AsRef<[u8]> for ScrubbedBytes { 21 | fn as_ref(&self) -> &[u8] { 22 | self.0.as_ref() 23 | } 24 | } 25 | 26 | impl Deref for ScrubbedBytes { 27 | type Target = [u8]; 28 | fn deref(&self) -> &Self::Target { 29 | self.0.deref() 30 | } 31 | } 32 | impl DerefMut for ScrubbedBytes { 33 | fn deref_mut(&mut self) -> &mut Self::Target { 34 | self.0.deref_mut() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /wallet/src/scheme/mod.rs: -------------------------------------------------------------------------------- 1 | use chain_impl_mockchain::{ 2 | fragment::Fragment, 3 | transaction::{Input, Output, Witness}, 4 | }; 5 | 6 | pub(crate) fn on_tx_output(fragment: &Fragment, on_output: FO) 7 | where 8 | FO: FnMut((usize, Output)), 9 | { 10 | match fragment { 11 | Fragment::Initial(_config_params) => {} 12 | Fragment::UpdateProposal(_update_proposal) => {} 13 | Fragment::UpdateVote(_signed_update) => {} 14 | Fragment::OldUtxoDeclaration(_utxos) => {} 15 | Fragment::Evm(_) => {} 16 | Fragment::Transaction(tx) => for_each_output(tx, on_output), 17 | Fragment::OwnerStakeDelegation(tx) => for_each_output(tx, on_output), 18 | Fragment::StakeDelegation(tx) => for_each_output(tx, on_output), 19 | Fragment::PoolRegistration(tx) => for_each_output(tx, on_output), 20 | Fragment::PoolRetirement(tx) => for_each_output(tx, on_output), 21 | Fragment::PoolUpdate(tx) => for_each_output(tx, on_output), 22 | Fragment::VotePlan(tx) => for_each_output(tx, on_output), 23 | Fragment::VoteCast(tx) => for_each_output(tx, on_output), 24 | Fragment::VoteTally(tx) => for_each_output(tx, on_output), 25 | Fragment::MintToken(tx) => for_each_output(tx, on_output), 26 | Fragment::EvmMapping(tx) => for_each_output(tx, on_output), 27 | } 28 | } 29 | 30 | fn for_each_output( 31 | tx: &chain_impl_mockchain::transaction::Transaction, 32 | on_output: F, 33 | ) where 34 | F: FnMut((usize, Output)), 35 | { 36 | tx.as_slice() 37 | .outputs() 38 | .iter() 39 | .enumerate() 40 | .for_each(on_output) 41 | } 42 | 43 | pub(crate) fn on_tx_input_and_witnesses(fragment: &Fragment, on_input: FI) 44 | where 45 | FI: FnMut((Input, Witness)), 46 | { 47 | match fragment { 48 | Fragment::Initial(_config_params) => {} 49 | Fragment::UpdateProposal(_update_proposal) => {} 50 | Fragment::UpdateVote(_signed_update) => {} 51 | Fragment::OldUtxoDeclaration(_utxos) => {} 52 | Fragment::Evm(_) => {} 53 | Fragment::Transaction(tx) => tx 54 | .as_slice() 55 | .inputs_and_witnesses() 56 | .iter() 57 | .for_each(on_input), 58 | Fragment::OwnerStakeDelegation(tx) => tx 59 | .as_slice() 60 | .inputs_and_witnesses() 61 | .iter() 62 | .for_each(on_input), 63 | Fragment::StakeDelegation(tx) => tx 64 | .as_slice() 65 | .inputs_and_witnesses() 66 | .iter() 67 | .for_each(on_input), 68 | Fragment::PoolRegistration(tx) => tx 69 | .as_slice() 70 | .inputs_and_witnesses() 71 | .iter() 72 | .for_each(on_input), 73 | Fragment::PoolRetirement(tx) => tx 74 | .as_slice() 75 | .inputs_and_witnesses() 76 | .iter() 77 | .for_each(on_input), 78 | Fragment::PoolUpdate(tx) => tx 79 | .as_slice() 80 | .inputs_and_witnesses() 81 | .iter() 82 | .for_each(on_input), 83 | Fragment::VotePlan(tx) => tx 84 | .as_slice() 85 | .inputs_and_witnesses() 86 | .iter() 87 | .for_each(on_input), 88 | Fragment::VoteCast(tx) => tx 89 | .as_slice() 90 | .inputs_and_witnesses() 91 | .iter() 92 | .for_each(on_input), 93 | Fragment::VoteTally(tx) => tx 94 | .as_slice() 95 | .inputs_and_witnesses() 96 | .iter() 97 | .for_each(on_input), 98 | Fragment::MintToken(tx) => tx 99 | .as_slice() 100 | .inputs_and_witnesses() 101 | .iter() 102 | .for_each(on_input), 103 | Fragment::EvmMapping(tx) => tx 104 | .as_slice() 105 | .inputs_and_witnesses() 106 | .iter() 107 | .for_each(on_input), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /wallet/src/states.rs: -------------------------------------------------------------------------------- 1 | use hashlink::LinkedHashMap; 2 | use std::{borrow::Borrow, hash::Hash}; 3 | 4 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] 5 | pub enum Status { 6 | Confirmed, 7 | Pending, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct StateRef { 12 | state: S, 13 | status: Status, 14 | } 15 | 16 | pub struct States { 17 | states: LinkedHashMap>, 18 | } 19 | 20 | impl std::fmt::Debug for States { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | f.debug_list().entries(self.iter()).finish() 23 | } 24 | } 25 | 26 | impl StateRef { 27 | fn new(state: S, status: Status) -> Self { 28 | Self { state, status } 29 | } 30 | 31 | pub fn is_confirmed(&self) -> bool { 32 | matches!(self.status, Status::Confirmed) 33 | } 34 | 35 | pub fn is_pending(&self) -> bool { 36 | matches!(self.status, Status::Pending) 37 | } 38 | 39 | pub fn state(&self) -> &S { 40 | &self.state 41 | } 42 | 43 | fn confirm(&mut self) { 44 | self.status = Status::Confirmed 45 | } 46 | } 47 | 48 | impl States 49 | where 50 | K: Hash + Eq, 51 | { 52 | /// create a new States with the given initial state 53 | /// 54 | /// by default this state is always assumed confirmed 55 | pub fn new(key: K, state: S) -> Self { 56 | let state = StateRef::new(state, Status::Confirmed); 57 | let mut states = LinkedHashMap::new(); 58 | states.insert(key, state); 59 | 60 | Self { states } 61 | } 62 | 63 | /// check wether the given state associate to this key is present 64 | /// in the States 65 | pub fn contains(&self, key: &Q) -> bool 66 | where 67 | K: Borrow, 68 | Q: Hash + Eq, 69 | { 70 | self.states.contains_key(key) 71 | } 72 | 73 | /// push a new **unconfirmed** state in the States 74 | pub fn push(&mut self, key: K, state: S) { 75 | let state = StateRef::new(state, Status::Pending); 76 | 77 | assert!(self.states.insert(key, state).is_none()); 78 | } 79 | 80 | pub fn confirm(&mut self, key: &Q) 81 | where 82 | K: Borrow, 83 | Q: Hash + Eq, 84 | { 85 | if let Some(state) = self.states.get_mut(key) { 86 | state.confirm(); 87 | } 88 | 89 | self.pop_old_confirmed_states() 90 | } 91 | 92 | fn pop_old_confirmed_states(&mut self) { 93 | // the first state in the list is always confirmed, so it is fine to skip it in the first 94 | // iteration. 95 | while self 96 | .states 97 | .iter() 98 | .nth(1) 99 | .map(|(_, state)| state.is_confirmed()) 100 | .unwrap_or(false) 101 | { 102 | self.states.pop_front(); 103 | } 104 | 105 | debug_assert!(self.states.front().unwrap().1.is_confirmed()); 106 | } 107 | } 108 | 109 | impl States { 110 | /// iterate through the states from the confirmed one up to the most 111 | /// recent one. 112 | /// 113 | /// there is always at least one element in the iterator (the confirmed one). 114 | pub fn iter(&self) -> impl Iterator)> { 115 | self.states.iter() 116 | } 117 | 118 | pub fn unconfirmed_states(&self) -> impl Iterator)> { 119 | self.states.iter().filter(|(_, s)| s.is_pending()) 120 | } 121 | 122 | /// access the confirmed state of the store verse 123 | pub fn confirmed_state(&self) -> &StateRef { 124 | self.states.front().map(|(_, v)| v).unwrap() 125 | } 126 | 127 | /// get the last state of the store 128 | pub fn last_state(&self) -> &StateRef { 129 | self.states.back().unwrap().1 130 | } 131 | } 132 | 133 | #[cfg(test)] 134 | mod tests { 135 | use super::*; 136 | 137 | impl PartialEq for StateRef<()> { 138 | fn eq(&self, other: &Self) -> bool { 139 | self.status.eq(&(other.status)) 140 | } 141 | } 142 | 143 | impl PartialOrd for StateRef<()> { 144 | fn partial_cmp(&self, other: &Self) -> Option { 145 | self.status.partial_cmp(&(other.status)) 146 | } 147 | } 148 | 149 | impl StateRef<()> { 150 | fn new_confirmed() -> Self { 151 | Self { 152 | state: (), 153 | status: Status::Confirmed, 154 | } 155 | } 156 | 157 | fn new_pending() -> Self { 158 | Self { 159 | state: (), 160 | status: Status::Pending, 161 | } 162 | } 163 | } 164 | 165 | #[test] 166 | fn confirmed_state() { 167 | let mut multiverse = States::new(0u8, ()); 168 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 169 | 170 | assert_eq!(&StateRef::new_confirmed(), multiverse.last_state()); 171 | 172 | multiverse.push(1, ()); 173 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 174 | assert_eq!(&StateRef::new_pending(), multiverse.last_state()); 175 | 176 | multiverse.push(2, ()); 177 | multiverse.push(3, ()); 178 | multiverse.push(4, ()); 179 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 180 | assert_eq!(&StateRef::new_pending(), multiverse.last_state()); 181 | 182 | multiverse.confirm(&1); 183 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 184 | assert_eq!(&StateRef::new_pending(), multiverse.last_state()); 185 | 186 | multiverse.confirm(&4); 187 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 188 | 189 | assert_eq!(&StateRef::new_confirmed(), multiverse.last_state()); 190 | 191 | multiverse.confirm(&3); 192 | multiverse.confirm(&2); 193 | assert_eq!(&StateRef::new_confirmed(), multiverse.confirmed_state()); 194 | 195 | assert_eq!(&StateRef::new_confirmed(), multiverse.last_state()); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /wallet/src/time.rs: -------------------------------------------------------------------------------- 1 | use crate::Settings; 2 | use chain_impl_mockchain::block::BlockDate; 3 | use chain_time::{SlotDuration, TimeFrame, Timeline}; 4 | use std::time::{Duration, SystemTime}; 5 | 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum Error { 8 | #[error("date is outside valid ttl range")] 9 | FinalDateOutOfRange, 10 | #[error("blockchain has not started")] 11 | BeforeBlock0Date, 12 | } 13 | 14 | pub fn block_date_from_system_time( 15 | settings: &Settings, 16 | date: SystemTime, 17 | ) -> Result { 18 | let start_time = SystemTime::UNIX_EPOCH + Duration::from_secs(settings.block0_date.0); 19 | let timeline = Timeline::new(start_time); 20 | let tf = TimeFrame::new( 21 | timeline, 22 | SlotDuration::from_secs(settings.slot_duration as u32), 23 | ); 24 | 25 | let final_slot_offset = tf.slot_at(&date).unwrap(); 26 | 27 | let date = settings 28 | .time_era 29 | .from_slot_to_era(final_slot_offset) 30 | .unwrap(); 31 | 32 | Ok(BlockDate { 33 | epoch: date.epoch.0, 34 | slot_id: date.slot.0, 35 | }) 36 | } 37 | 38 | pub fn max_expiration_date( 39 | settings: &Settings, 40 | current_time: SystemTime, 41 | ) -> Result { 42 | let start_time = SystemTime::UNIX_EPOCH + Duration::from_secs(settings.block0_date.0); 43 | let timeline = Timeline::new(start_time); 44 | let tf = TimeFrame::new( 45 | timeline, 46 | SlotDuration::from_secs(settings.slot_duration as u32), 47 | ); 48 | 49 | let current_slot_offset = tf.slot_at(¤t_time).ok_or(Error::BeforeBlock0Date)?; 50 | 51 | let current_date = settings 52 | .time_era 53 | .from_slot_to_era(current_slot_offset) 54 | .unwrap(); 55 | 56 | let last_valid_epoch = current_date.epoch.0 + settings.transaction_max_expiry_epochs as u32; 57 | 58 | Ok(BlockDate { 59 | epoch: last_valid_epoch, 60 | slot_id: settings 61 | .time_era 62 | .slots_per_epoch() 63 | .checked_sub(1) 64 | .expect("slots per epoch can't be zero"), 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /wallet/src/transaction/builder.rs: -------------------------------------------------------------------------------- 1 | use super::witness_builder::WitnessBuilder; 2 | use crate::Settings; 3 | use chain_addr::Address; 4 | use chain_impl_mockchain::{ 5 | block::BlockDate, 6 | fee::FeeAlgorithm as _, 7 | transaction::{ 8 | Balance, Input, Output, Payload, SetAuthData, SetIOs, SetTtl, SetWitnesses, Transaction, 9 | TxBuilderState, 10 | }, 11 | value::Value, 12 | }; 13 | use thiserror::Error; 14 | 15 | #[derive(Debug, Error)] 16 | #[error("Cannot balance the transaction")] 17 | pub struct BalancingError; 18 | 19 | pub struct TransactionBuilder<'settings, P: Payload> { 20 | settings: &'settings Settings, 21 | payload: P, 22 | validity: BlockDate, 23 | outputs: Vec>, 24 | inputs: Vec, 25 | witness_builders: Vec>, 26 | } 27 | 28 | pub enum AddInputStatus { 29 | Added, 30 | Skipped(Input), 31 | NotEnoughSpace, 32 | } 33 | 34 | impl<'settings, P: Payload> TransactionBuilder<'settings, P> { 35 | /// create a new transaction builder with the given settings and outputs 36 | pub fn new(settings: &'settings Settings, payload: P, validity: BlockDate) -> Self { 37 | Self { 38 | settings, 39 | payload, 40 | validity, 41 | outputs: Vec::with_capacity(255), 42 | inputs: Vec::with_capacity(255), 43 | witness_builders: Vec::with_capacity(255), 44 | } 45 | } 46 | 47 | #[inline] 48 | pub fn inputs(&self) -> &[Input] { 49 | &self.inputs 50 | } 51 | 52 | #[inline] 53 | pub fn outputs(&self) -> &[Output
] { 54 | &self.outputs 55 | } 56 | 57 | #[inline] 58 | pub fn inputs_value(&self) -> Value { 59 | self.inputs().iter().map(|i| i.value()).sum() 60 | } 61 | 62 | #[inline] 63 | pub fn outputs_value(&self) -> Value { 64 | self.outputs().iter().map(|i| i.value).sum() 65 | } 66 | 67 | #[inline] 68 | pub fn estimate_fee_with(&self, extra_inputs: u8, extra_outputs: u8) -> Value { 69 | self.settings.fees.calculate( 70 | self.payload 71 | .payload_data() 72 | .borrow() 73 | .into_certificate_slice(), 74 | self.inputs.len() as u8 + extra_inputs, 75 | self.outputs.len() as u8 + extra_outputs, 76 | ) 77 | } 78 | 79 | #[inline] 80 | pub fn estimate_fee(&self) -> Value { 81 | self.estimate_fee_with(0, 0) 82 | } 83 | 84 | pub fn add_input_if_worth( 85 | &mut self, 86 | input: Input, 87 | witness_builder: B, 88 | ) -> AddInputStatus { 89 | if self.settings.is_input_worth(&input) { 90 | match self.add_input(input, witness_builder) { 91 | true => AddInputStatus::Added, 92 | false => AddInputStatus::NotEnoughSpace, 93 | } 94 | } else { 95 | AddInputStatus::Skipped(input) 96 | } 97 | } 98 | 99 | pub fn add_input( 100 | &mut self, 101 | input: Input, 102 | witness_builder: B, 103 | ) -> bool { 104 | match self.inputs.len().cmp(&255) { 105 | std::cmp::Ordering::Less => { 106 | self.inputs.push(input); 107 | self.witness_builders.push(Box::new(witness_builder)); 108 | true 109 | } 110 | _ => false, 111 | } 112 | } 113 | 114 | pub fn add_output(&mut self, output: Output
) -> bool { 115 | if self.outputs().len() < 255 { 116 | self.outputs.push(output); 117 | true 118 | } else { 119 | false 120 | } 121 | } 122 | 123 | pub fn check_balance(&self) -> Balance { 124 | self.check_balance_with(0, 0) 125 | } 126 | 127 | pub fn check_balance_with(&self, extra_inputs: u8, extra_outputs: u8) -> Balance { 128 | let total_in = self.inputs_value(); 129 | let total_out = self.outputs_value(); 130 | let total_fee = self.estimate_fee_with(extra_inputs, extra_outputs); 131 | 132 | let total_out = total_out.saturating_add(total_fee); 133 | 134 | match total_in.cmp(&total_out) { 135 | std::cmp::Ordering::Greater => { 136 | Balance::Positive(total_in.checked_sub(total_out).unwrap()) 137 | } 138 | std::cmp::Ordering::Equal => Balance::Zero, 139 | std::cmp::Ordering::Less => Balance::Negative(total_out.checked_sub(total_in).unwrap()), 140 | } 141 | } 142 | 143 | pub fn finalize_tx(self, auth:

::Auth) -> Result, BalancingError> { 144 | if !matches!(self.check_balance(), Balance::Zero) { 145 | return Err(BalancingError); 146 | } 147 | 148 | let builder = TxBuilderState::new(); 149 | let builder = builder.set_payload(&self.payload); 150 | 151 | let builder = self.set_validity(builder); 152 | let builder = self.set_ios(builder); 153 | let builder = self.set_witnesses(builder); 154 | 155 | Ok(builder.set_payload_auth(&auth)) 156 | } 157 | 158 | fn set_validity(&self, builder: TxBuilderState>) -> TxBuilderState> { 159 | builder.set_expiry_date(self.validity) 160 | } 161 | 162 | fn set_ios(&self, builder: TxBuilderState>) -> TxBuilderState> { 163 | builder.set_ios(&self.inputs, &self.outputs) 164 | } 165 | 166 | fn set_witnesses( 167 | &self, 168 | builder: TxBuilderState>, 169 | ) -> TxBuilderState> 170 | where 171 | P: Payload, 172 | { 173 | let header_id = self.settings.block0_initial_hash; 174 | let auth_data = builder.get_auth_data_for_witness().hash(); 175 | let witnesses: Vec<_> = self 176 | .witness_builders 177 | .iter() 178 | .map(|wb| wb.build(&header_id, &auth_data)) 179 | .collect(); 180 | 181 | builder.set_witnesses(&witnesses) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /wallet/src/transaction/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod strategy; 3 | mod witness_builder; 4 | 5 | pub use self::{ 6 | builder::{AddInputStatus, TransactionBuilder}, 7 | strategy::{InputStrategy, OutputStrategy, Strategy, StrategyBuilder, DEFAULT_STRATEGIES}, 8 | witness_builder::AccountWitnessBuilder, 9 | }; 10 | -------------------------------------------------------------------------------- /wallet/src/transaction/strategy.rs: -------------------------------------------------------------------------------- 1 | /// the default strategies, in order of importance, to apply 2 | /// when selecting inputs and outputs of a transaction 3 | pub const DEFAULT_STRATEGIES: &[Strategy] = &[ 4 | StrategyBuilder::most_private().build(), 5 | StrategyBuilder::most_efficient().build(), 6 | ]; 7 | 8 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 9 | pub enum InputStrategy { 10 | /// try to get the most optimise transaction consuming the 11 | /// wallet's utxos in the most efficient way. 12 | /// 13 | BestEffort, 14 | 15 | /// preserve the privacy of the UTxOs 16 | /// 17 | /// This means the transaction will be only composed of 18 | /// inputs of the same public key. If a change needs created 19 | /// it will create it to a different unused change, this may 20 | /// create dust 21 | /// 22 | /// This option is incompatible with the `UTXO_CHANGE_TO_ACCOUNT` 23 | PrivacyPreserving, 24 | } 25 | 26 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 27 | pub enum OutputStrategy { 28 | /// If the transaction needs to offload extra inputs in an 29 | /// extra change output then chose the best solution with the 30 | /// given circumstances 31 | /// 32 | /// if there are only single utxos as input the change will be 33 | /// offloaded to a new change output with a new utxo. 34 | /// 35 | /// if there's group inputs for the same key, the group account 36 | /// will be used to offload the change (order may matter, i.e. 37 | /// if there's multiple inputs with different account the first 38 | /// account will be used). 39 | BestEffort, 40 | 41 | /// Along with privacy preserving, this one will have the interesting 42 | /// property to redistribute the change into multiple distinct utxos 43 | /// 44 | /// however, if the change is only too small (less than 10x dust like): 45 | /// 46 | /// * if one of the inputs contain a group key, the change will be distributed 47 | /// to the group account 48 | /// * if there's no account, the change will go to a new utxo 49 | UtxoReshuffle, 50 | } 51 | 52 | pub struct Strategy { 53 | input: InputStrategy, 54 | output: OutputStrategy, 55 | } 56 | 57 | pub struct StrategyBuilder { 58 | input: InputStrategy, 59 | output: OutputStrategy, 60 | } 61 | 62 | impl Strategy { 63 | pub fn input(&self) -> InputStrategy { 64 | self.input 65 | } 66 | 67 | pub fn output(&self) -> OutputStrategy { 68 | self.output 69 | } 70 | } 71 | 72 | impl StrategyBuilder { 73 | pub const fn most_private() -> Self { 74 | Self { 75 | input: InputStrategy::PrivacyPreserving, 76 | output: OutputStrategy::UtxoReshuffle, 77 | } 78 | } 79 | 80 | pub const fn most_efficient() -> Self { 81 | Self { 82 | input: InputStrategy::BestEffort, 83 | output: OutputStrategy::BestEffort, 84 | } 85 | } 86 | 87 | pub const fn build(&self) -> Strategy { 88 | Strategy { 89 | input: self.input, 90 | output: self.output, 91 | } 92 | } 93 | } 94 | 95 | impl Default for StrategyBuilder { 96 | fn default() -> Self { 97 | Self { 98 | input: InputStrategy::BestEffort, 99 | output: OutputStrategy::BestEffort, 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /wallet/src/transaction/witness_builder.rs: -------------------------------------------------------------------------------- 1 | use chain_crypto::{Ed25519, Ed25519Extended, SecretKey, Signature}; 2 | use chain_impl_mockchain::{ 3 | accounting::account::SpendingCounter, 4 | block::HeaderId, 5 | transaction::{TransactionSignDataHash, Witness, WitnessUtxoData}, 6 | }; 7 | use ed25519_bip32::XPrv; 8 | 9 | use hdkeygen::Key; 10 | 11 | pub trait WitnessBuilder { 12 | fn build(&self, block0: &HeaderId, sign_data_hash: &TransactionSignDataHash) -> Witness; 13 | } 14 | 15 | pub struct UtxoWitnessBuilder(pub K); 16 | pub enum AccountWitnessBuilder { 17 | Ed25519(SecretKey, SpendingCounter), 18 | Ed25519Extended(SecretKey, SpendingCounter), 19 | } 20 | 21 | impl WitnessBuilder for UtxoWitnessBuilder> { 22 | fn build(&self, block0: &HeaderId, sign_data_hash: &TransactionSignDataHash) -> Witness { 23 | let xprv = &self.0; 24 | Witness::new_utxo(block0, sign_data_hash, |data| { 25 | Signature::from_binary(xprv.sign::(data.as_ref()).as_ref()) 26 | .unwrap() 27 | }) 28 | } 29 | } 30 | 31 | impl WitnessBuilder for UtxoWitnessBuilder> { 32 | fn build(&self, block0: &HeaderId, sign_data_hash: &TransactionSignDataHash) -> Witness { 33 | let key = &self.0; 34 | Witness::new_utxo(block0, sign_data_hash, |data| { 35 | Signature::from_binary(key.sign(data).as_ref()).unwrap() 36 | }) 37 | } 38 | } 39 | 40 | impl WitnessBuilder for AccountWitnessBuilder { 41 | fn build(&self, block0: &HeaderId, sign_data_hash: &TransactionSignDataHash) -> Witness { 42 | match self { 43 | AccountWitnessBuilder::Ed25519(key, spending_counter) => { 44 | Witness::new_account(block0, sign_data_hash, *spending_counter, |data| { 45 | key.sign(data) 46 | }) 47 | } 48 | AccountWitnessBuilder::Ed25519Extended(key, spending_counter) => { 49 | Witness::new_account(block0, sign_data_hash, *spending_counter, |data| { 50 | key.sign(data) 51 | }) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /wallet/tests/account.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use self::utils::State; 4 | use chain_crypto::SecretKey; 5 | use chain_impl_mockchain::{ 6 | account::SpendingCounter, 7 | certificate::VoteCast, 8 | fragment::Fragment, 9 | value::Value, 10 | vote::{Choice, Payload}, 11 | }; 12 | use wallet::MAX_LANES; 13 | 14 | const BLOCK0: &[u8] = include_bytes!("../../test-vectors/block0"); 15 | const ACCOUNT_KEY: &str = include_str!("../../test-vectors/free_keys/key1.prv"); 16 | 17 | #[test] 18 | fn update_state_overrides_old() { 19 | let mut account = wallet::Wallet::new_from_key( 20 | SecretKey::from_binary( 21 | hex::decode(String::from(ACCOUNT_KEY).trim()) 22 | .unwrap() 23 | .as_ref(), 24 | ) 25 | .unwrap(), 26 | ); 27 | 28 | assert_eq!(account.confirmed_value(), Value::zero()); 29 | 30 | account 31 | .set_state( 32 | Value(110), 33 | (0..MAX_LANES) 34 | .map(|lane| SpendingCounter::new(lane, 1)) 35 | .collect(), 36 | ) 37 | .unwrap(); 38 | 39 | assert_eq!(account.confirmed_value(), Value(110)); 40 | } 41 | 42 | #[test] 43 | fn cast_vote() { 44 | let mut account = wallet::Wallet::new_from_key( 45 | SecretKey::from_binary( 46 | hex::decode(String::from(ACCOUNT_KEY).trim()) 47 | .unwrap() 48 | .as_ref(), 49 | ) 50 | .unwrap(), 51 | ); 52 | 53 | let mut state = State::new(BLOCK0); 54 | let settings = state.settings().expect("valid initial settings"); 55 | 56 | for fragment in state.initial_contents() { 57 | account.check_fragment(&fragment.hash(), fragment).unwrap(); 58 | account.confirm(&fragment.hash()); 59 | } 60 | 61 | let vote_plan_id = &state.active_vote_plans()[0]; 62 | 63 | let choice = Choice::new(1); 64 | 65 | let current_time = 66 | std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(settings.block0_date.0); 67 | 68 | for i in 0..16 { 69 | let payload = Payload::Public { choice }; 70 | let cast = VoteCast::new(vote_plan_id.clone(), i, payload); 71 | 72 | let mut builder = wallet::TransactionBuilder::new( 73 | &settings, 74 | cast.clone(), 75 | wallet::time::max_expiration_date(&settings, current_time).unwrap(), 76 | ); 77 | 78 | let value = builder.estimate_fee_with(1, 0); 79 | 80 | let account_tx_builder = account.new_transaction(value, i % 8).unwrap(); 81 | let input = account_tx_builder.input(); 82 | let witness_builder = account_tx_builder.witness_builder(); 83 | 84 | builder.add_input(input, witness_builder); 85 | 86 | let tx = builder.finalize_tx(()).unwrap(); 87 | 88 | let fragment = Fragment::VoteCast(tx); 89 | let id = fragment.hash(); 90 | 91 | account_tx_builder.add_fragment_id(id); 92 | 93 | state 94 | .apply_fragments(&[fragment]) 95 | .expect("couldn't apply votecast fragment"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /wallet/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use chain_impl_mockchain::{ 2 | accounting::account::SpendingCounterIncreasing, 3 | block::Block, 4 | certificate::VotePlanId, 5 | fragment::Fragment, 6 | ledger::{Error as LedgerError, Ledger}, 7 | value::Value, 8 | }; 9 | use chain_ser::{deser::DeserializeFromSlice, packer::Codec}; 10 | use wallet::Settings; 11 | 12 | pub struct State { 13 | block0: Block, 14 | pub ledger: Ledger, 15 | } 16 | 17 | impl State { 18 | pub fn new(block0_bytes: B) -> Self 19 | where 20 | B: AsRef<[u8]>, 21 | { 22 | let block0 = Block::deserialize_from_slice(&mut Codec::new(block0_bytes.as_ref())) 23 | .expect("valid block0"); 24 | let hh = block0.header().id(); 25 | let ledger = Ledger::new(hh, block0.fragments()).unwrap(); 26 | 27 | Self { block0, ledger } 28 | } 29 | 30 | #[allow(dead_code)] 31 | pub fn initial_contents(&self) -> impl Iterator { 32 | self.block0.contents().iter() 33 | } 34 | 35 | pub fn settings(&self) -> Result { 36 | Settings::new(&self.block0) 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub fn active_vote_plans(&self) -> Vec { 41 | self.ledger 42 | .active_vote_plans() 43 | .into_iter() 44 | .map(|plan| plan.id) 45 | .collect() 46 | } 47 | 48 | pub fn apply_fragments<'a, F>(&'a mut self, fragments: F) -> Result<(), LedgerError> 49 | where 50 | F: IntoIterator, 51 | { 52 | let block_date = self.ledger.date(); 53 | let mut new_ledger = self.ledger.clone(); 54 | for fragment in fragments { 55 | new_ledger = self.ledger.apply_fragment(fragment, block_date)?; 56 | } 57 | 58 | self.ledger = new_ledger; 59 | 60 | Ok(()) 61 | } 62 | 63 | #[allow(dead_code)] 64 | pub fn get_account_state( 65 | &self, 66 | account_id: wallet::AccountId, 67 | ) -> Option<(SpendingCounterIncreasing, Value)> { 68 | self.ledger 69 | .accounts() 70 | .get_state(&chain_crypto::PublicKey::from(account_id).into()) 71 | .ok() 72 | .map(|account_state| (account_state.spending.clone(), account_state.value)) 73 | } 74 | } 75 | --------------------------------------------------------------------------------