diff --git a/CHANGELOG.md b/CHANGELOG.md index c62cae6..aed73d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Upcoming ### Short-term -- Update internal documentation -- Update external documentation - v1.0.0 release ### Long-term @@ -31,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Option to use curve ed25519 or secp256k1 for XRP - XMR subaddresses - AVAX C-chain and P-chain support +- Code documentation ### Changed - Replace CLI "cold"/"hot" options with "solo"/"multi" commands - Replace coin option with subcommands for "solo" wallets diff --git a/README.md b/README.md index d2c5603..1f2d9e5 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,103 @@ # Offline Wallet Generator -## Application Description +## Purpose -Generate offline wallets for several popular cryptocurrencies. +A cryptocurrency "wallet" is the key or keys used to manage one or more cryptocurrency accounts containing funds. -## Installation Instructions +The purpose of this library is to generate wallets for several popular cryptocurrencies "offline" (without access to the +internet, or on a machine which has never been connected to the internet). -Java is required to build and run this project. +The benefit to an "offline" wallet generator is that if you inspect the code and prepare the machine you will use to run +it, you can guarantee the absolute secrecy of the generated wallet. No one but you could possibly know the information +contained within and, depending on how you manage your wallet, it will stay that way forever. -### Build from Source +## Philosophy -Clone this repo using the following command: +Choosing to manage your own wallet is one of the most important decisions that you could make. Although it comes with +increased responsibility, it is the only way to truly embrace the freedom that cryptocurrency offers. Any crypto veteran +will tell you the same thing: "not your keys, not your crypto". As convenient as it is to have an exchange or bank +manage your wallet for you (and take a fee for doing so), the risks of having your funds frozen or confiscated are +immense. Though it may seem daunting at first, it has never been easier to take complete autonomy over your finances. +Rise up to the challenge and embrace the financial system that you deserve. -```shell -git clone git@github.com:ashelkovnykov/offline-wallet-generator.git -``` +## What to Do With Your Wallet + +### Receiving Funds + +The addresses in the wallets produced by this tool are yours and ready to receive funds. It is safe to share these +addresses for the purpose of receiving funds. + +### Protecting Your Wallet + +There are many options for how to protect your wallet: + +- Secure the mnemonic (e.g. encrypt it, password protect it, hide it inside an image, etc.) and store it in the cloud +- Secure the mnemonic and store it locally (e.g. USB, external hard drive, etc.) +- Store it in a specialized "hardware wallets" +- Write it down on a piece of paper +- Memorize the mnemonic and delete the files +- Some combination of the above + +Each of the above options has advantages and disadvantages in terms of security and convenience. It is ultimately up to +each individual to evaluate his needs and risks. Each individual's attack surface depends on who he is, where he lives, +what he does, and how he uses his crypto. -Inside the project root directory, run: +## Installation -- Windows: `gradlew.bat build` -- Linux/MacOS: `./gradlew.sh build` +There are three methods to install and use this tool: -Alternatively, import the project as a Gradle project into Eclipse or IDEA, then build it. +- Docker +- Released build +- Build it yourself -### Release Version +Windows users should use Docker. Linux and MacOS users may use whichever method they like. Note that: +1. Building the tool from source is the safest way to use the tool +2. There's nothing stopping Windows users from building the tool from source; the process is just not documented here -TODO +### Docker -## Running the Application +1. Install [Docker](https://docs.docker.com/get-docker/) +2. Ready to go! -The `lib` folder contains the latest release of the offline-wallet-generator application. In addition, the `bin` folder -contains scripts for running the application without using the `java` command. To run the latest release version of the -application, use the following command from the root folder: +The default Docker command for the tool is: ```shell -bin/release.sh +docker run --rm -v [/path/to/output/]:/app/output/:rw ashelkov/owg:latest ``` -To run the latest unreleased version of the application (using the latest code), build the application from source and -then run the following command: +### Released Build + +1. Install [OpenJDK 16.0.1](https://jdk.java.net/archive/) +2. Download the [latest release](https://github.com/ashelkovnykov/offline-wallet-generator/releases) +3. Ready to go! + +The default release build command for the tool is: ```shell -bin/local.sh +./owg.sh -h ``` -### Usage +### Built it Yourself -The following documentation is displayed in the console when the wallet application is run with the `-h` or `--help` -option: +1. Install [OpenJDK 16.0.1](https://jdk.java.net/archive/) +2. Pull the latest code using `git`: + - `git clone https://github.com/ashelkovnykov/offline-wallet-generator.git` +3. Navigate to `./offline-wallet-generator/` +4. Build the tool: + - `./gradlew.sh clean build` +5. Ready to go! +The default local build command for the tool is: + +```shell +./bin/local.sh -h ``` + +## Usage + +The default commands documented above will display the following documentation when run: + +```text Usage:
[options] [command] [command options] Options: -m, --custom-mnemonic @@ -61,7 +109,7 @@ Usage:
[options] [command] [command options] -f, --format Generated wallet output format Default: WALLET - Possible Values: [CONSOLE, SECURE_CONSOLE, WALLET] + Possible Values: [CONSOLE, WALLET] -h, --help Show this usage details page -p, --mnemonic-password @@ -80,90 +128,198 @@ Usage:
[options] [command] [command options] Default: false Commands: solo Generate a wallet for a single cryptocurrency - Usage: solo [options] + Usage: solo [options] [command] [command options] Options: - -a, --account - BIP 44 account field for address - -i, --address-index - BIP 44 index field for address - -g, --change - BIP 44 change field for address - * -c, --coin - Crypto currency code of coin for which to generate wallet - Possible Values: [BTC, LTC, DOGE, ETH, XMR, XRP, XLM, ALGO, ERG, HNS, AVAX] -n, --num-addresses Number of addresses to generate Default: 1 + Commands: + BTC Generate a Bitcoin wallet + Usage: BTC [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + -c, --change + BIP 44 change field for address + Default: 0 + -i, --index + BIP 44 index field for address + Default: 0 + + LTC Generate a Litecoin wallet + Usage: LTC [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + -c, --change + BIP 44 change field for address + Default: 0 + -i, --index + BIP 44 index field for address + Default: 0 + + DOGE Generate a Dogecoin wallet + Usage: DOGE [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + -c, --change + BIP 44 change field for address + Default: 0 + -i, --index + BIP 44 index field for address + Default: 0 + + ETH Generate an Ethereum wallet + Usage: ETH [options] + Options: + -i, --index + BIP 44 index field for address + Default: 0 + + XMR Generate a Monero wallet + Usage: XMR [options] + Options: + -s, --spend-key + Output the spend key(s) + Default: false + -a, --sub-addr-acc + Monero subaddress account + Default: 0 + -i, --sub-addr-index + Monero subaddress index + Default: 0 + -v, --view-key + Output the view key(s) + Default: false + + XRP Generate an XRP wallet + Usage: XRP [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + -l, --legacy + Use curve secp256k1 instead of ed25519 to generate address + Default: false + + XLM Generate a Stellar wallet + Usage: XLM [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + + ALGO Generate an Algorand wallet + Usage: ALGO [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + + ERG Generate an Ergo wallet + Usage: ERG [options] + Options: + -i, --index + BIP 44 index field for address + Default: 0 + + HNS Generate a Handshake wallet + Usage: HNS [options] + Options: + -a, --account + BIP 44 account field for address + Default: 0 + -c, --change + BIP 44 change field for address + Default: 0 + -i, --index + BIP 44 index field for address + Default: 0 + + AVAX Generate an Avalanche wallet + Usage: AVAX [options] + Options: + -c, --chains + Chains for which to generate address(es) + Default: [EXCHANGE] + -i, --index + BIP 44 index field for address + Default: 0 + multi Generate a wallet for multiple cryptocurrencies Usage: multi ``` -#### Examples +## Examples Generate a wallet file for the first 10 Dogecoin addresses using a custom mnemonic, custom password, and the default output directory: ```shell -./bin/release.sh \ --m \ --p \ -solo \ ---coin=DOGE \ --n=10 +# Docker (note that Docker always needs a provided output directory) +docker run --rm -v /home/user/wallets/:/app/output/:rw ashelkov/owg:latest -m -p solo -n 10 DOGE + +# Release build +./owg.sh -m -p solo -n 10 DOGE + +# Local build +./bin/local.sh -m -p solo -n 10 DOGE ``` Generate a wallet file for the Bitcoin addresses `m/84'/0'/2'/1'/3'` and `m/84'/0'/2'/1'/4'` using a random 12-word mnemonic, no password, and a custom output directory: ```shell -./bin/release.sh \ --o ~/btc/BTC-1.wal \ --e 128 \ -solo \ --c=BTC \ ---account=2 \ ---change=1 \ ---index=3 \ ---num-addresses=2 +# Docker (note that Docker always uses a default file name) +docker run --rm -v /home/user/wallets/:/app/output/:rw ashelkov/owg:latest -m -p solo -n 10 DOGE + +# Release build +./owg.sh -e 128 -o ~/wallets/my-btc-wallet.wal solo -n 2 BTC --account=2 --change=1 --index=3 + +# Local build +./bin/local.sh -e 128 -o ~/wallets/my-btc-wallet.wal solo -n 2 BTC -a 2 -c 1 -i 3 ``` Generate a multi-coin wallet containing the default address for every supported coin using a random 24-word mnemonic, no -password, and output the results to the console: +password, and output the results to the console (including public and private keys): ```shell -./bin/release.sh \ --f CONSOLE \ -multi +# Docker (note that the Docker command for console output is slightly different than usual) +docker run --rm --entrypoint ./bin/release.sh ashelkov/owg:latest -f CONSOLE -k -K multi + +# Release build +./owg.sh -f CONSOLE -k -K multi + +# Local build +./bin/local.sh -f CONSOLE -k -K multi ``` -### Output +## Output -This tool generates the mnemonic, the specified address(es), and its/their BIP 44/84 path(s). Unless a custom name and -location are specified, the wallet will be written to one of the locations specified below, determined by operating -system. The name of the file will be `{coin}.wal`, where `{coin}` is the cryptocurrency code of the wallet, if it's a -single-coin wallet. The name of the file will be `multi.wal` if it's a multi-coin wallet. +### Docker -#### Linux -``` -$HOME/.wallets/ -``` -#### Mac -``` -$HOME/Library/Wallets/ -``` -#### Windows -``` -%APPDATA%\ -``` +If saving to file, Docker always requires an explicit output directory and always uses a default file name for the +wallet. -## Docker +Printing to console requires a slightly different Docker command: ```shell -docker build -t owg:latest . -docker run --rm -v [/path/to/output/]:/app/output/:rw owg:latest +docker run --rm --entrypoint ./bin/release.sh ashelkov/owg:latest -f CONSOLE [options] ``` -## Acknowledgements +### Release and Local Builds + +When saving to a file, if no custom output location is specified then the tool will write the wallet to the default +wallet directory. The name of the file will be `{coin}.{ext}`, where: +- `{coin}` is the cryptocurrency code (for single-coin wallets) or `multi` (for a multi-coin wallet) +- `{ext}` is the file extension of the output type -TODO +The default wallet directory is determined by the operating system: +- Linux: `$HOME/.wallets/` +- MacOS: `$HOME/Library/Wallets/` +- Windows: `%APPDATA%\` diff --git a/cli/src/main/java/com/ashelkov/owg/Application.java b/cli/src/main/java/com/ashelkov/owg/Application.java index e125d15..48ccba4 100644 --- a/cli/src/main/java/com/ashelkov/owg/Application.java +++ b/cli/src/main/java/com/ashelkov/owg/Application.java @@ -21,14 +21,24 @@ import com.ashelkov.owg.wallet.Wallet; import com.ashelkov.owg.wallet.generators.*; +/** + * Main class for OWG CLI. + */ public final class Application { private static final Logger logger = LoggerFactory.getLogger(Application.class); private static final Params params = Params.getInstance(); private Application() {} - - private static WalletGenerator getWalletGenerator(Coin coin, byte[] seed) { + + /** + * Create factory for selected coin using given random seed. + * + * @param coin Coin for which to create factory + * @param seed Random seed + * @return Factory ready to generate wallet + */ + private static SingleCoinWalletGenerator getWalletGenerator(Coin coin, byte[] seed) { return switch (coin) { case BTC -> new BitcoinWalletGenerator(seed, params.isGenPrivKey(), params.isGenPubKey()); @@ -77,14 +87,20 @@ private static WalletGenerator getWalletGenerator(Coin coin, byte[] seed) { }; } + /** + * Generate a wallet containing the default address for every coin supported by the OWG. + * + * @param seed Random seed + * @return New wallet + */ private static MultiCoinWallet generateMultiWallet(byte[] seed) { List subWallets = new ArrayList<>(Coin.values().length); for (Coin coin : Coin.values()) { try { - WalletGenerator walletGenerator = getWalletGenerator(coin, seed); - subWallets.add(walletGenerator.generateDefaultWallet()); + SingleCoinWalletGenerator singleCoinWalletGenerator = getWalletGenerator(coin, seed); + subWallets.add(singleCoinWalletGenerator.generateDefaultWallet()); } catch (Exception e) { System.exit(1); } @@ -93,6 +109,11 @@ private static MultiCoinWallet generateMultiWallet(byte[] seed) { return new MultiCoinWallet(subWallets); } + /** + * Application entry point. + * + * @param args CLI arguments (commands and options) + */ public static void main(String[] args) { // @@ -151,8 +172,8 @@ public static void main(String[] args) { switch (params.getCommand()) { case SoloCommand.NAME -> { try { - WalletGenerator walletGenerator = getWalletGenerator(params.getCoin(), seedFromMnemonic); - wallet = walletGenerator.generatePathWallet(params.getBipPath(), params.getNumAddresses()); + SingleCoinWalletGenerator singleCoinWalletGenerator = getWalletGenerator(params.getCoin(), seedFromMnemonic); + wallet = singleCoinWalletGenerator.generatePathWallet(params.getBipPath(), params.getNumAddresses()); } catch (Exception e) { System.exit(1); } diff --git a/cli/src/main/java/com/ashelkov/owg/io/Constants.java b/cli/src/main/java/com/ashelkov/owg/io/Constants.java index dc42660..207b4e9 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/Constants.java +++ b/cli/src/main/java/com/ashelkov/owg/io/Constants.java @@ -1,5 +1,8 @@ package com.ashelkov.owg.io; +/** + * CLI constants. + */ public class Constants { // Limit on number of addresses to generate for a single coin diff --git a/cli/src/main/java/com/ashelkov/owg/io/Params.java b/cli/src/main/java/com/ashelkov/owg/io/Params.java index 5743a98..384fd5b 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/Params.java +++ b/cli/src/main/java/com/ashelkov/owg/io/Params.java @@ -14,15 +14,14 @@ import com.ashelkov.owg.io.command.SoloCommand; import com.ashelkov.owg.io.command.MultiCommand; import com.ashelkov.owg.io.command.coin.*; -import com.ashelkov.owg.io.conversion.OutputFormatConverter; -import com.ashelkov.owg.io.storage.OutputFormat; -import com.ashelkov.owg.io.storage.Writer; -import com.ashelkov.owg.io.storage.WriterFactory; +import com.ashelkov.owg.io.output.OutputFormat; +import com.ashelkov.owg.io.output.Writer; +import com.ashelkov.owg.io.output.WriterFactory; import com.ashelkov.owg.io.util.CommandUtils; import com.ashelkov.owg.io.validation.*; /** - * + * JCommander parameter specification for CLI. */ final public class Params { @@ -92,8 +91,7 @@ final public class Params { @Parameter( names = {OPT_OUTPUT_FORMAT_S, OPT_OUTPUT_FORMAT_L}, - description = "Generated wallet output format", - converter = OutputFormatConverter.class) + description = "Generated wallet output format") private OutputFormat outputFormat = DEFAULT_OUTPUT_TYPE; @Parameter( diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/MultiCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/MultiCommand.java index 6e2e6b6..0108661 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/MultiCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/MultiCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * JCommander command for producing a wallet of multiple cryptocurrency types. */ @Parameters(commandDescription = "Generate a wallet for multiple cryptocurrencies") final public class MultiCommand { diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/SoloCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/SoloCommand.java index cb60409..7064343 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/SoloCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/SoloCommand.java @@ -7,7 +7,7 @@ import com.ashelkov.owg.io.validation.*; /** - * + * JCommander command for producing a wallet for a single cryptocurrency. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/ACICoinSubCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/ACICoinSubCommand.java index 85755e1..5b2dfac 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/ACICoinSubCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/ACICoinSubCommand.java @@ -5,8 +5,11 @@ import com.ashelkov.owg.io.validation.BinaryIntegerValidator; import com.ashelkov.owg.io.validation.PositiveIntegerValidator; -import com.ashelkov.owg.wallet.generators.WalletGenerator; +import com.ashelkov.owg.wallet.generators.SingleCoinWalletGenerator; +/** + * Specialization of [[CoinSubCommand]] for coins which make use of the full BIP-44 path. + */ public abstract class ACICoinSubCommand extends CoinSubCommand { // @@ -31,21 +34,21 @@ public abstract class ACICoinSubCommand extends CoinSubCommand { description = "BIP 44 account field for address", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int account = WalletGenerator.DEFAULT_FIELD_VAL; + protected int account = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; @Parameter( names = {OPT_CHANGE_S, OPT_CHANGE_L}, description = "BIP 44 change field for address", converter = IntegerConverter.class, validateValueWith = BinaryIntegerValidator.class) - protected int change = WalletGenerator.DEFAULT_FIELD_VAL; + protected int change = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; @Parameter( names = {OPT_INDEX_S, OPT_INDEX_L}, description = "BIP 44 index field for address", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int index = WalletGenerator.DEFAULT_FIELD_VAL; + protected int index = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; // // Getters diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AccountCoinSubCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AccountCoinSubCommand.java index 8628e2b..1ba153a 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AccountCoinSubCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AccountCoinSubCommand.java @@ -4,8 +4,11 @@ import com.beust.jcommander.converters.IntegerConverter; import com.ashelkov.owg.io.validation.PositiveIntegerValidator; -import com.ashelkov.owg.wallet.generators.WalletGenerator; +import com.ashelkov.owg.wallet.generators.SingleCoinWalletGenerator; +/** + * Specialization of [[CoinSubCommand]] for coins which make use of the BIP-44 'account' field. + */ public abstract class AccountCoinSubCommand extends CoinSubCommand { // @@ -24,7 +27,7 @@ public abstract class AccountCoinSubCommand extends CoinSubCommand { description = "BIP 44 account field for address", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int account = WalletGenerator.DEFAULT_FIELD_VAL; + protected int account = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; // // Getters diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AlgorandCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AlgorandCommand.java index 4619913..b7ae563 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AlgorandCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AlgorandCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing an Algorand wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AvalancheCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AvalancheCommand.java index cd73e6c..7e0c0d9 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/AvalancheCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/AvalancheCommand.java @@ -9,7 +9,7 @@ import com.ashelkov.owg.coin.avax.Chain; /** - * + * Sub-command to [[SoloCommand]] for producing an Avalanche wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/BitcoinCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/BitcoinCommand.java index dc88713..fd86649 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/BitcoinCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/BitcoinCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing a Bitcoin wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/CoinSubCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/CoinSubCommand.java index e02bcdb..f553568 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/CoinSubCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/CoinSubCommand.java @@ -1,5 +1,8 @@ package com.ashelkov.owg.io.command.coin; +/** + * Sub-command for [[SoloCommand]] JCommander command to select type of coin for which to produce wallet. + */ public abstract class CoinSubCommand { // diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/DogecoinCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/DogecoinCommand.java index 374301b..678df03 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/DogecoinCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/DogecoinCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing a Dogecoin wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/ErgoCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/ErgoCommand.java index 6846f51..d73a00b 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/ErgoCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/ErgoCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing an Ergo wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/EthereumCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/EthereumCommand.java index 565de52..223893d 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/EthereumCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/EthereumCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing an Ethereum wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/HandshakeCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/HandshakeCommand.java index cf19da2..d579518 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/HandshakeCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/HandshakeCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing a Handshake wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/IndexCoinSubCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/IndexCoinSubCommand.java index 6739eb0..9571e12 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/IndexCoinSubCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/IndexCoinSubCommand.java @@ -4,8 +4,11 @@ import com.beust.jcommander.converters.IntegerConverter; import com.ashelkov.owg.io.validation.PositiveIntegerValidator; -import com.ashelkov.owg.wallet.generators.WalletGenerator; +import com.ashelkov.owg.wallet.generators.SingleCoinWalletGenerator; +/** + * Specialization of [[CoinSubCommand]] for coins which make use of the BIP-44 'index' field. + */ public abstract class IndexCoinSubCommand extends CoinSubCommand { // @@ -24,7 +27,7 @@ public abstract class IndexCoinSubCommand extends CoinSubCommand { description = "BIP 44 index field for address", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int index = WalletGenerator.DEFAULT_FIELD_VAL; + protected int index = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; // // Getters diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/LitecoinCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/LitecoinCommand.java index ce97088..4ccb303 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/LitecoinCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/LitecoinCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing a Litecoin wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/MoneroCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/MoneroCommand.java index 998234f..62be44b 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/MoneroCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/MoneroCommand.java @@ -5,10 +5,10 @@ import com.beust.jcommander.converters.IntegerConverter; import com.ashelkov.owg.io.validation.PositiveIntegerValidator; -import com.ashelkov.owg.wallet.generators.WalletGenerator; +import com.ashelkov.owg.wallet.generators.SingleCoinWalletGenerator; /** - * + * Sub-command to [[SoloCommand]] for producing a Monero wallet. */ @Parameters( separators = "=", @@ -40,14 +40,14 @@ final public class MoneroCommand extends CoinSubCommand { description = "Monero subaddress account", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int account = WalletGenerator.DEFAULT_FIELD_VAL; + protected int account = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; @Parameter( names = {OPT_SUB_ADDRESS_INDEX_S, OPT_SUB_ADDRESS_INDEX_L}, description = "Monero subaddress index", converter = IntegerConverter.class, validateValueWith = PositiveIntegerValidator.class) - protected int index = WalletGenerator.DEFAULT_FIELD_VAL; + protected int index = SingleCoinWalletGenerator.DEFAULT_FIELD_VAL; @Parameter( names = {OPT_SPEND_KEY_S, OPT_SPEND_KEY_L}, diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/StellarCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/StellarCommand.java index 016948c..7530dfb 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/StellarCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/StellarCommand.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameters; /** - * + * Sub-command to [[SoloCommand]] for producing a Stellar wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/command/coin/XRPCommand.java b/cli/src/main/java/com/ashelkov/owg/io/command/coin/XRPCommand.java index addc65b..9e28150 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/command/coin/XRPCommand.java +++ b/cli/src/main/java/com/ashelkov/owg/io/command/coin/XRPCommand.java @@ -2,8 +2,9 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; + /** - * + * Sub-command to [[SoloCommand]] for producing a XRP wallet. */ @Parameters( separators = "=", diff --git a/cli/src/main/java/com/ashelkov/owg/io/conversion/OutputFormatConverter.java b/cli/src/main/java/com/ashelkov/owg/io/conversion/OutputFormatConverter.java deleted file mode 100644 index 699c11a..0000000 --- a/cli/src/main/java/com/ashelkov/owg/io/conversion/OutputFormatConverter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ashelkov.owg.io.conversion; - -import com.beust.jcommander.converters.EnumConverter; - -import com.ashelkov.owg.io.storage.OutputFormat; - -public class OutputFormatConverter extends EnumConverter { - public OutputFormatConverter(String optionName, Class clazz) { - super(optionName, clazz); - } -} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/ConsoleWriter.java b/cli/src/main/java/com/ashelkov/owg/io/output/ConsoleWriter.java similarity index 54% rename from cli/src/main/java/com/ashelkov/owg/io/storage/ConsoleWriter.java rename to cli/src/main/java/com/ashelkov/owg/io/output/ConsoleWriter.java index 0222cd5..eb982d2 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/ConsoleWriter.java +++ b/cli/src/main/java/com/ashelkov/owg/io/output/ConsoleWriter.java @@ -1,13 +1,21 @@ -package com.ashelkov.owg.io.storage; +package com.ashelkov.owg.io.output; import com.ashelkov.owg.wallet.Wallet; +/** + * Derived [[Writer]] to output wallet to console. + */ public class ConsoleWriter extends Writer { public ConsoleWriter() {} + /** + * Print wallet to console. + * + * @param mnemonic Mnemonic phrase used to produce wallet + * @param wallet Wallet to output + */ public void saveWallet(String mnemonic, Wallet wallet) { - System.out.println(mnemonic); System.out.println(); System.out.println(wallet.toString()); diff --git a/cli/src/main/java/com/ashelkov/owg/io/output/OutputFormat.java b/cli/src/main/java/com/ashelkov/owg/io/output/OutputFormat.java new file mode 100644 index 0000000..74212bf --- /dev/null +++ b/cli/src/main/java/com/ashelkov/owg/io/output/OutputFormat.java @@ -0,0 +1,9 @@ +package com.ashelkov.owg.io.output; + +/** + * Supported methods to output produced wallets. + */ +public enum OutputFormat { + CONSOLE, + WALLET +} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/WalletWriter.java b/cli/src/main/java/com/ashelkov/owg/io/output/WalletWriter.java similarity index 85% rename from cli/src/main/java/com/ashelkov/owg/io/storage/WalletWriter.java rename to cli/src/main/java/com/ashelkov/owg/io/output/WalletWriter.java index e514ab9..402ecbb 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/WalletWriter.java +++ b/cli/src/main/java/com/ashelkov/owg/io/output/WalletWriter.java @@ -1,4 +1,4 @@ -package com.ashelkov.owg.io.storage; +package com.ashelkov.owg.io.output; import java.io.BufferedWriter; import java.io.IOException; @@ -11,6 +11,9 @@ import com.ashelkov.owg.io.util.FileUtils; import com.ashelkov.owg.wallet.Wallet; +/** + * Derived [[Writer]] to save wallet to OWG custom '.wal' file format. + */ public final class WalletWriter extends Writer { private static final Logger logger = LoggerFactory.getLogger(WalletWriter.class); @@ -25,6 +28,12 @@ public WalletWriter(Path basePath, boolean overwrite) { this.overwrite = overwrite; } + /** + * Save the given wallet to disk in '.wal' format. + * + * @param mnemonic Mnemonic phrase used to produce wallet + * @param wallet Wallet to output + */ public void saveWallet(String mnemonic, Wallet wallet) { logger.debug(String.format("WalletWriter.save() called with base path '%s'", basePath)); diff --git a/cli/src/main/java/com/ashelkov/owg/io/output/Writer.java b/cli/src/main/java/com/ashelkov/owg/io/output/Writer.java new file mode 100644 index 0000000..44fa64e --- /dev/null +++ b/cli/src/main/java/com/ashelkov/owg/io/output/Writer.java @@ -0,0 +1,17 @@ +package com.ashelkov.owg.io.output; + +import com.ashelkov.owg.wallet.Wallet; + +/** + * Base class for output methods to save produced wallets. + */ +public abstract class Writer { + + /** + * Output the given wallet. + * + * @param mnemonic Mnemonic phrase used to produce wallet + * @param wallet Wallet to output + */ + public abstract void saveWallet(String mnemonic, Wallet wallet); +} diff --git a/cli/src/main/java/com/ashelkov/owg/io/output/WriterFactory.java b/cli/src/main/java/com/ashelkov/owg/io/output/WriterFactory.java new file mode 100644 index 0000000..06e617f --- /dev/null +++ b/cli/src/main/java/com/ashelkov/owg/io/output/WriterFactory.java @@ -0,0 +1,27 @@ +package com.ashelkov.owg.io.output; + +import java.nio.file.Path; + +/** + * Factory class for the various wallet output options. + */ +public final class WriterFactory { + + private WriterFactory() {} + + /** + * Map output settings to an object of the appropriate class for outputting produced wallets. + * + * @param format Type of output to produce + * @param outputPath Location to produce output + * @param overwrite Overwrite existing output of the same name, if true + * @return [[Writer]] object for saving produced wallets + */ + public static Writer buildWriter(OutputFormat format, Path outputPath, boolean overwrite) { + + return switch (format) { + case CONSOLE -> new ConsoleWriter(); + case WALLET -> new WalletWriter(outputPath, overwrite); + }; + } +} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/OutputFormat.java b/cli/src/main/java/com/ashelkov/owg/io/storage/OutputFormat.java deleted file mode 100644 index d3f58db..0000000 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/OutputFormat.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.ashelkov.owg.io.storage; - -public enum OutputFormat { - CONSOLE, - SECURE_CONSOLE, - WALLET; -} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/SecureConsoleWriter.java b/cli/src/main/java/com/ashelkov/owg/io/storage/SecureConsoleWriter.java deleted file mode 100644 index cd75885..0000000 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/SecureConsoleWriter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ashelkov.owg.io.storage; - -import com.ashelkov.owg.wallet.Wallet; - -public class SecureConsoleWriter extends Writer { - - public SecureConsoleWriter() {} - - public void saveWallet(String mnemonic, Wallet wallet) { - System.out.println(wallet.toString()); - } -} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/Writer.java b/cli/src/main/java/com/ashelkov/owg/io/storage/Writer.java deleted file mode 100644 index 78b3627..0000000 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/Writer.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ashelkov.owg.io.storage; - -import com.ashelkov.owg.wallet.Wallet; - -public abstract class Writer { - - public abstract void saveWallet(String mnemonic, Wallet wallet); - -} diff --git a/cli/src/main/java/com/ashelkov/owg/io/storage/WriterFactory.java b/cli/src/main/java/com/ashelkov/owg/io/storage/WriterFactory.java deleted file mode 100644 index 727e7c9..0000000 --- a/cli/src/main/java/com/ashelkov/owg/io/storage/WriterFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ashelkov.owg.io.storage; - -import java.nio.file.Path; - -public final class WriterFactory { - - private WriterFactory() {} - - public static Writer buildWriter(OutputFormat format, Path outputPath, boolean overwrite) { - - return switch (format) { - case CONSOLE -> new ConsoleWriter(); - case SECURE_CONSOLE -> new SecureConsoleWriter(); - case WALLET -> new WalletWriter(outputPath, overwrite); - }; - } -} diff --git a/cli/src/main/java/com/ashelkov/owg/io/util/CommandUtils.java b/cli/src/main/java/com/ashelkov/owg/io/util/CommandUtils.java index b111172..ad29b18 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/util/CommandUtils.java +++ b/cli/src/main/java/com/ashelkov/owg/io/util/CommandUtils.java @@ -4,10 +4,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * JCommander utilities. + */ public class CommandUtils { private static final Logger logger = LoggerFactory.getLogger(CommandUtils.class); + /** + * Add a sub-command to a JCommander command. + * + * @param root Root JCommander object which contains all commands + * @param parent Name of command to which to add sub-command + * @param name Name of sub-command + * @param command Sub-command object + * @param aliases Sub-command aliases + */ public static void addSubCommand( JCommander root, String parent, diff --git a/cli/src/main/java/com/ashelkov/owg/io/util/FileUtils.java b/cli/src/main/java/com/ashelkov/owg/io/util/FileUtils.java index c46e058..14b6eb5 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/util/FileUtils.java +++ b/cli/src/main/java/com/ashelkov/owg/io/util/FileUtils.java @@ -12,6 +12,9 @@ import static java.nio.charset.StandardCharsets.US_ASCII; +/** + * Utilities for managing file I/O. + */ public class FileUtils { private static final Set DEFAULT_DIR_PERMISSIONS = @@ -21,18 +24,39 @@ public class FileUtils { private static final String FILE_DELIMITER = "."; + /** + * Create a directory at the given path with default file permissions. + * + * @param dir Path to directory + * @throws IOException + */ public static void createDirectory(Path dir) throws IOException { createDirectory(dir, DEFAULT_DIR_PERMISSIONS); } + /** + * Create a directory at the given path with the given file permissions. + * + * @param dir Path to directory + * @param permissions Directory file permissions + * @throws IOException + */ public static void createDirectory(Path dir, Set permissions) throws IOException { Files.createDirectories(dir, PosixFilePermissions.asFileAttribute(permissions)); } + /** + * Create a [[BufferedWriter]] for new file at the given path. + * + * @param outputPath Path to new file + * @param overwrite Overwrite file if true, throw exception if false and file exists already + * @return BufferedWriter object for file at path + * @throws IOException + */ public static BufferedWriter getBufferedWriter(Path outputPath, boolean overwrite) throws IOException { @@ -47,6 +71,13 @@ public static BufferedWriter getBufferedWriter(Path outputPath, boolean overwrit return Files.newBufferedWriter(outputPath, US_ASCII); } + /** + * Set permissions of file/directory. + * + * @param path Path to file/directory + * @param permissions Permissions to set + * @throws IOException + */ public static void setPermissions(Path path, Set permissions) throws IOException { @@ -57,18 +88,41 @@ public static void setPermissions(Path path, Set permission Files.setPosixFilePermissions(path, permissions); } + /** + * Set file permissions to default. + * + * @param filePath Path to file + * @throws IOException + */ public static void setFilePermissions(Path filePath) throws IOException { setFilePermissions(filePath, DEFAULT_FILE_PERMISSIONS); } + /** + * Set file permissions. + * + * @param filePath Path to file + * @param permissions Permissions to set + * @throws IOException + */ public static void setFilePermissions(Path filePath, Set permissions) throws IOException { setPermissions(filePath, permissions); } + /** + * Resolve a path to a file: + * - If given a file path, return the path + * - If given a directory path, append the file name and file extension to the path + * + * @param path Path to file/directory + * @param fileName File name, if path doesn't resolve to file + * @param fileExtension File extension, if path doesn't resolve to file + * @return Path to file + */ public static Path resolvePath(Path path, String fileName, String fileExtension) { if (Files.isDirectory(path)) { diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/AddressLimitValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/AddressLimitValidator.java index 2c3176a..59347ba 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/AddressLimitValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/AddressLimitValidator.java @@ -5,6 +5,9 @@ import static com.ashelkov.owg.io.Constants.ADDRESS_LIMIT; +/** + * JCommander validator for number of addresses to produce per coin per wallet. + */ public class AddressLimitValidator implements IValueValidator { @Override diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/BinaryIntegerValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/BinaryIntegerValidator.java index 1b123b3..cebb3c2 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/BinaryIntegerValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/BinaryIntegerValidator.java @@ -3,6 +3,9 @@ import com.beust.jcommander.IValueValidator; import com.beust.jcommander.ParameterException; +/** + * JCommander validator for integer with binary value. + */ public class BinaryIntegerValidator implements IValueValidator { @Override diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/EntropyValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/EntropyValidator.java index 67df3be..3c10611 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/EntropyValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/EntropyValidator.java @@ -3,6 +3,9 @@ import com.beust.jcommander.IValueValidator; import com.beust.jcommander.ParameterException; +/** + * JCommander validator for bits of entropy to use when generating mnemonic seed phrase. + */ public class EntropyValidator implements IValueValidator { private static final int MAX_ENTROPY = 256; diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/MnemonicValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/MnemonicValidator.java index e2cb8da..cb1a909 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/MnemonicValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/MnemonicValidator.java @@ -4,6 +4,9 @@ import com.beust.jcommander.ParameterException; import org.web3j.crypto.MnemonicUtils; +/** + * JCommander validator for mnemonic seed phrase. + */ public class MnemonicValidator implements IParameterValidator { @Override diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/PasswordValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/PasswordValidator.java index 3c6f3a9..26a281c 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/PasswordValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/PasswordValidator.java @@ -8,6 +8,9 @@ import com.ashelkov.owg.io.exception.NullConsoleException; +/** + * JCommander validator for password (user must enter identical password twice). + */ public class PasswordValidator implements IParameterValidator { @Override diff --git a/cli/src/main/java/com/ashelkov/owg/io/validation/PositiveIntegerValidator.java b/cli/src/main/java/com/ashelkov/owg/io/validation/PositiveIntegerValidator.java index f0a7bb6..5bf04d9 100644 --- a/cli/src/main/java/com/ashelkov/owg/io/validation/PositiveIntegerValidator.java +++ b/cli/src/main/java/com/ashelkov/owg/io/validation/PositiveIntegerValidator.java @@ -3,6 +3,9 @@ import com.beust.jcommander.IValueValidator; import com.beust.jcommander.ParameterException; +/** + * JCommander validator for integer >= 1. + */ public class PositiveIntegerValidator implements IValueValidator { @Override diff --git a/cli/src/main/java/com/ashelkov/owg/util/Utils.java b/cli/src/main/java/com/ashelkov/owg/util/Utils.java index a5d172d..64c6036 100644 --- a/cli/src/main/java/com/ashelkov/owg/util/Utils.java +++ b/cli/src/main/java/com/ashelkov/owg/util/Utils.java @@ -8,13 +8,17 @@ import org.slf4j.LoggerFactory; import org.web3j.crypto.LinuxSecureRandom; +/** + * Generic utilities used by CLI. + */ public final class Utils { private static final Logger logger = LoggerFactory.getLogger(Utils.class); private static SecureRandom random; - private Utils() {} - + /** + * Setup the source of randomness used for generating entropy. + */ private static void initSecureRandom() { // Preferred method for secure RNG on UNIX systems @@ -33,6 +37,11 @@ private static void initSecureRandom() { random = new SecureRandom(); } + /** + * Initialize and pass the source of entropy which will be used by the CLI. + * + * @return [[SecureRandom]] + */ public static SecureRandom getSecureRandom() { if (random == null) { @@ -41,4 +50,6 @@ public static SecureRandom getSecureRandom() { return random; } + + private Utils() {} } diff --git a/core/src/main/java/com/ashelkov/owg/address/AvalancheAddress.java b/core/src/main/java/com/ashelkov/owg/address/AvalancheAddress.java index e3fe22f..b080378 100644 --- a/core/src/main/java/com/ashelkov/owg/address/AvalancheAddress.java +++ b/core/src/main/java/com/ashelkov/owg/address/AvalancheAddress.java @@ -5,14 +5,30 @@ import com.ashelkov.owg.coin.avax.Chain; import com.ashelkov.owg.wallet.util.BIP44Utils; +/** + * Representation of a single Avalanche address. + * + * An Avalanche wallet has addresses for multiple chains, at the very least the Exchange, Contract, and Platform chains + * which are core to the network. + */ public class AvalancheAddress extends BIP44Address { protected final Map addressMap; + /** + * @param addressMap Map of Avalanche chain to address + * @param path BIP-44 path from which the address was derived + */ public AvalancheAddress(Map addressMap, int[] path) { this(addressMap, path, null, null); } + /** + * @param addressMap Map of Avalanche chain to address + * @param path BIP-44 path from which the address was derived + * @param privKey Private key used to derive the address + * @param pubKey Public key used to derive the address + */ public AvalancheAddress(Map addressMap, int[] path, String privKey, String pubKey) { super("", path, privKey, pubKey); this.addressMap = addressMap; @@ -23,10 +39,12 @@ public String toString() { StringBuilder result = new StringBuilder(); + // append path result.append('('); result.append(BIP44Utils.convertPathToText(path)); result.append(')'); + // append addresses for each chain for (Map.Entry entry : addressMap.entrySet()) { result.append('\n'); result.append(entry.getKey().toString()); @@ -34,6 +52,7 @@ public String toString() { result.append(entry.getValue()); } + // append keys, if present if (privKey != null) { result.append("\nPRIV =\t"); result.append(privKey); diff --git a/core/src/main/java/com/ashelkov/owg/address/BIP44Address.java b/core/src/main/java/com/ashelkov/owg/address/BIP44Address.java index 02eb1cc..75f1a31 100644 --- a/core/src/main/java/com/ashelkov/owg/address/BIP44Address.java +++ b/core/src/main/java/com/ashelkov/owg/address/BIP44Address.java @@ -1,37 +1,73 @@ package com.ashelkov.owg.address; +import com.ashelkov.owg.bip.Constants; import com.ashelkov.owg.wallet.util.BIP44Utils; +/** + * Representation of a single [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) style address. + * Contains the [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) path, the address, and possibly + * the private/public keys. + */ public class BIP44Address { + public static final int PURPOSE = 44; + protected final String address; protected final String privKey; protected final String pubKey; protected final int[] path; + /** + * @param address Address + * @param path BIP-44 path from which the address was derived + */ public BIP44Address(String address, int[] path) { this(address, path, null, null); } + /** + * @param address Address + * @param path BIP-44 path from which the address was derived + * @param privKey Private key used to derive the address + * @param pubKey Public key used to derive the address + */ public BIP44Address(String address, int[] path, String privKey, String pubKey) { + if (!isValidPurpose(path[0])) { + String purposeString = BIP44Utils.chainValToString(path[0]); + throw new IllegalArgumentException(String.format("Bad 'purpose' value for BIP44 path: %s", purposeString)); + } + this.address = address; this.privKey = privKey; this.pubKey = pubKey; this.path = path; } + /** + * Validate that purpose value is acceptable for BIP44 address. + * + * @param purpose Purpose value to validate + * @return true if the input value matches BIP44 purpose, false otherwise + */ + protected boolean isValidPurpose(int purpose) { + return (purpose == (Constants.HARDENED | PURPOSE)); + } + @Override public String toString() { StringBuilder result = new StringBuilder(); + // append BIP-32 path result.append('('); result.append(BIP44Utils.convertPathToText(path)); result.append(')'); + // append address result.append('\t'); result.append(address); + // append keys, if present if (privKey != null) { result.append("\nPRIV =\t"); result.append(privKey); diff --git a/core/src/main/java/com/ashelkov/owg/address/BIP84Address.java b/core/src/main/java/com/ashelkov/owg/address/BIP84Address.java index b9c831d..288e794 100644 --- a/core/src/main/java/com/ashelkov/owg/address/BIP84Address.java +++ b/core/src/main/java/com/ashelkov/owg/address/BIP84Address.java @@ -1,12 +1,41 @@ package com.ashelkov.owg.address; +import com.ashelkov.owg.bip.Constants; + +/** + * Representation of a single [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) style address + * ([[BIP44Address]] with an updated purpose field). + */ public class BIP84Address extends BIP44Address { + public static final int PURPOSE = 84; + + /** + * @param address Address + * @param path BIP-84 path from which the address was derived + */ public BIP84Address(String address, int[] path) { super(address, path, null, null); } + /** + * @param address Address + * @param path BIP-84 path from which the address was derived + * @param privKey Private key used to derive the address + * @param pubKey Public key used to derive the address + */ public BIP84Address(String address, int[] path, String privKey, String pubKey) { super(address, path, privKey, pubKey); } + + /** + * Validate that purpose value is acceptable for BIP84 address. + * + * @param purpose Purpose value to validate + * @return true if the input value matches BIP84 purpose, false otherwise + */ + @Override + protected boolean isValidPurpose(int purpose) { + return (purpose == (Constants.HARDENED | PURPOSE)); + } } diff --git a/core/src/main/java/com/ashelkov/owg/address/MoneroAddress.java b/core/src/main/java/com/ashelkov/owg/address/MoneroAddress.java index fba62b0..54702d2 100644 --- a/core/src/main/java/com/ashelkov/owg/address/MoneroAddress.java +++ b/core/src/main/java/com/ashelkov/owg/address/MoneroAddress.java @@ -4,6 +4,13 @@ import com.ashelkov.owg.wallet.util.BIP44Utils; +/** + * Representation of a single Monero address. + * + * A Monero address is a lot like a [[BIP44Address]], except that it doesn't publish the address path (since all Monero + * addresses use the same path) and it *does* publish whether the address is a standard address or a subaddress (and if + * so, which subaddress). + */ public class MoneroAddress extends BIP44Address { protected final String pubSpendKey; @@ -11,10 +18,20 @@ public class MoneroAddress extends BIP44Address { protected final int account; protected final int index; + /** + * @param address Address + * @param path An ordered set of indices used to verify that the address was derived from BIP-44 path m/44'/128'/0' + * and which 'account' and 'index' indices were used to compute the address (if it is a subaddress) + */ public MoneroAddress(String address, int[] path) { this(address, path, null, null); } + /** + * @param address Address + * @param path An ordered set of indices used to verify that the address was derived from BIP-44 path m/44'/128'/0' + * and which 'account' and 'index' indices were used to compute the address (if it is a subaddress) + */ public MoneroAddress(String address, int[] path, String pubSpendKey, String pubViewKey) { super(address, Arrays.copyOfRange(path, 0, 2), null, null); @@ -30,15 +47,17 @@ public String toString() { StringBuilder result = new StringBuilder(); boolean isSubaddress = (account != 0) || (index != 0); - // Monero addresses don't publish paths, since their path is always the same + // append address type if (isSubaddress) { result.append(String.format("SUBADDRESS(%d, %d)", account, index)); } else { result.append("STANDARD ADDRESS"); } + // append address result.append('\t'); result.append(address); + // append keys, if present if (pubSpendKey != null) { result.append("\n\tPUB SPEND\t"); result.append(pubSpendKey); diff --git a/core/src/main/java/com/ashelkov/owg/bip/Coin.java b/core/src/main/java/com/ashelkov/owg/bip/Coin.java index 355c766..66f8c29 100644 --- a/core/src/main/java/com/ashelkov/owg/bip/Coin.java +++ b/core/src/main/java/com/ashelkov/owg/bip/Coin.java @@ -1,9 +1,10 @@ package com.ashelkov.owg.bip; +/** + * Enumeration of supported coins and their generally-accepted [coin codes]( + * https://github.com/satoshilabs/slips/blob/master/slip-0044.md). + */ public enum Coin { - - // Coin chain codes derived from here: - // https://github.com/satoshilabs/slips/blob/master/slip-0044.md BTC(0), LTC(2), DOGE(3), diff --git a/core/src/main/java/com/ashelkov/owg/bip/Constants.java b/core/src/main/java/com/ashelkov/owg/bip/Constants.java index 9f6fd99..d05eb85 100644 --- a/core/src/main/java/com/ashelkov/owg/bip/Constants.java +++ b/core/src/main/java/com/ashelkov/owg/bip/Constants.java @@ -1,19 +1,14 @@ package com.ashelkov.owg.bip; -public class Constants { +/** + * Collection of constants used by all modern coin wallets. + */ +public final class Constants { // Hardened keys have index >= 0x80000000: // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki public static final int HARDENED = 0x80000000; - // All generated wallets are BIP-44 compliant: - // https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki - public static final int BIP44_PURPOSE = 44; - - // Bitcoin and Litecoin wallets are BIP-84 compliant: - // https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki - public static final int BIP84_PURPOSE = 84; - // Standard checksum length, whether using starting or trailing bytes public static final int CHECKSUM_LENGTH = 4; diff --git a/core/src/main/java/com/ashelkov/owg/coin/avax/Chain.java b/core/src/main/java/com/ashelkov/owg/coin/avax/Chain.java index 6cf3727..32d5b84 100644 --- a/core/src/main/java/com/ashelkov/owg/coin/avax/Chain.java +++ b/core/src/main/java/com/ashelkov/owg/coin/avax/Chain.java @@ -1,7 +1,10 @@ package com.ashelkov.owg.coin.avax; +/** + * Enumeration of core Avalanche chains, each of which may have an address in an [[AvalancheAddress]] object (see + * https://docs.avax.network/learn/platform-overview/#exchange-chain-x-chain). + */ public enum Chain { - // Chain overview located here: https://docs.avax.network/learn/platform-overview/#exchange-chain-x-chain PLATFORM, EXCHANGE, CONTRACT diff --git a/core/src/main/java/com/ashelkov/owg/wallet/AlgorandWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/AlgorandWallet.java index 2476d58..62a72ac 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/AlgorandWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/AlgorandWallet.java @@ -2,15 +2,14 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; +import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Algorand addresses. + */ public class AlgorandWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public AlgorandWallet(List derivedAddresses) { super(derivedAddresses, Coin.ALGO); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/AvalancheWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/AvalancheWallet.java index 919eb66..ce0beac 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/AvalancheWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/AvalancheWallet.java @@ -2,15 +2,14 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; +import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Avalanche addresses. + */ public class AvalancheWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public AvalancheWallet(List derivedAddresses) { super(derivedAddresses, Coin.AVAX); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/BitcoinWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/BitcoinWallet.java index fb6d0ab..48f1503 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/BitcoinWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/BitcoinWallet.java @@ -3,16 +3,14 @@ import java.util.List; import com.ashelkov.owg.address.BIP44Address; -import com.ashelkov.owg.address.BIP84Address; import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP84_PURPOSE; +/** + * Wallet for storing Bitcoin addresses. + */ +public final class BitcoinWallet extends XPubWallet { -public class BitcoinWallet extends XPubWallet { - - public static final int PURPOSE = BIP84_PURPOSE; - - public BitcoinWallet(BIP84Address xpub, List addresses) { + public BitcoinWallet(BIP44Address xpub, List addresses) { super(xpub, addresses, Coin.BTC); } } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/DogecoinWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/DogecoinWallet.java index 7442a14..4e81298 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/DogecoinWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/DogecoinWallet.java @@ -5,12 +5,11 @@ import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Dogecoin addresses. + */ public class DogecoinWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public DogecoinWallet(List derivedAddresses) { super(derivedAddresses, Coin.DOGE); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/ErgoWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/ErgoWallet.java index 8980cdb..303e489 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/ErgoWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/ErgoWallet.java @@ -2,15 +2,14 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; +import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Ergo addresses. + */ public class ErgoWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public ErgoWallet(List derivedAddresses) { super(derivedAddresses, Coin.ERG); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/EthereumWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/EthereumWallet.java index 5f4f50b..82dcc18 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/EthereumWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/EthereumWallet.java @@ -5,12 +5,11 @@ import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Ethereum addresses. + */ public class EthereumWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public EthereumWallet(List derivedAddresses) { super(derivedAddresses, Coin.ETH); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/HandshakeWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/HandshakeWallet.java index c3fa5f5..1b58294 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/HandshakeWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/HandshakeWallet.java @@ -3,16 +3,14 @@ import java.util.List; import com.ashelkov.owg.address.BIP44Address; -import com.ashelkov.owg.address.BIP84Address; import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Handshake addresses. + */ public class HandshakeWallet extends XPubWallet { - public static final int PURPOSE = BIP44_PURPOSE; - - public HandshakeWallet(BIP84Address xpub, List addresses) { + public HandshakeWallet(BIP44Address xpub, List addresses) { super(xpub, addresses, Coin.HNS); } } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/LitecoinWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/LitecoinWallet.java index b7a4536..8bfaa92 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/LitecoinWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/LitecoinWallet.java @@ -3,16 +3,14 @@ import java.util.List; import com.ashelkov.owg.address.BIP44Address; -import com.ashelkov.owg.address.BIP84Address; import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP84_PURPOSE; - +/** + * Wallet for storing Litecoin addresses. + */ public class LitecoinWallet extends XPubWallet { - public static final int PURPOSE = BIP84_PURPOSE; - - public LitecoinWallet(BIP84Address xpub, List addresses) { + public LitecoinWallet(BIP44Address xpub, List addresses) { super(xpub, addresses, Coin.LTC); } } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/MoneroWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/MoneroWallet.java index 25091d5..c432d21 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/MoneroWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/MoneroWallet.java @@ -2,17 +2,15 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; -import com.ashelkov.owg.wallet.util.BIP44Utils; import static com.ashelkov.owg.bip.Coin.XMR; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; +/** + * Wallet for storing Monero addresses. + */ public class MoneroWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - private final String privateSpendKey; private final String privateViewKey; private final boolean hasSubaddresses; @@ -47,7 +45,7 @@ public String toString() { // append path (all Monero wallets use the exact same path) result.append("\n(m/"); - result.append(PURPOSE); + result.append(BIP44Address.PURPOSE); result.append("'/"); result.append(XMR); result.append("'/0')"); diff --git a/core/src/main/java/com/ashelkov/owg/wallet/MultiCoinWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/MultiCoinWallet.java index 84cf66a..0d1b513 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/MultiCoinWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/MultiCoinWallet.java @@ -2,6 +2,9 @@ import java.util.List; +/** + * Representation of a cryptocurrency wallet for multiple coins. + */ public class MultiCoinWallet extends Wallet { private static final String ID = "multi"; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/SingleCoinWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/SingleCoinWallet.java index 37dd8f6..0b706cb 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/SingleCoinWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/SingleCoinWallet.java @@ -5,6 +5,9 @@ import com.ashelkov.owg.address.BIP44Address; import com.ashelkov.owg.bip.Coin; +/** + * Representation of a cryptocurrency wallet for just one coin. + */ public abstract class SingleCoinWallet extends Wallet { protected final List addresses; @@ -38,11 +41,21 @@ public String toString() { return result.toString(); } + /** + * Get a unique [[String]] identifier for the current wallet type: the coin ticker name. + * + * @return Coin name + */ @Override public String getIdentifier() { return coin.toString(); } + /** + * Get the coin type of this wallet. + * + * @return Wallet coin type + */ public Coin getCoin() { return coin; } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/StellarWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/StellarWallet.java index 9713730..6938c25 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/StellarWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/StellarWallet.java @@ -2,15 +2,14 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; +import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing Stellar addresses. + */ public class StellarWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public StellarWallet(List derivedAddresses) { super(derivedAddresses, Coin.XLM); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/Wallet.java b/core/src/main/java/com/ashelkov/owg/wallet/Wallet.java index 902e666..83e0de8 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/Wallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/Wallet.java @@ -1,5 +1,9 @@ package com.ashelkov.owg.wallet; +/** + * Representation of a cryptocurrency "wallet": one or more addresses for one or more cryptocurrencies, all derived from + * a single random seed. + */ public abstract class Wallet { protected Wallet() {} @@ -7,5 +11,10 @@ protected Wallet() {} @Override public abstract String toString(); + /** + * Get a unique [[String]] identifier for the current wallet type. + * + * @return String identifier + */ public abstract String getIdentifier(); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/XPubWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/XPubWallet.java index 00c6655..baec723 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/XPubWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/XPubWallet.java @@ -3,14 +3,19 @@ import java.util.List; import com.ashelkov.owg.address.BIP44Address; -import com.ashelkov.owg.address.BIP84Address; import com.ashelkov.owg.bip.Coin; +/** + * Representation of a cryptocurrency wallet for just one coin which uses an extended public key (a + * [BIP-32}(https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Extended_keys) feature). Extended public keys + * allow the derivation of all child public keys, meaning that they allow third-parties to verify the balances of all + * derived addresses. + */ public abstract class XPubWallet extends SingleCoinWallet { - protected final BIP84Address xpub; + protected final BIP44Address xpub; - protected XPubWallet(BIP84Address xpub, List addresses, Coin coin) { + protected XPubWallet(BIP44Address xpub, List addresses, Coin coin) { super(addresses, coin); this.xpub = xpub; } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/XRPWallet.java b/core/src/main/java/com/ashelkov/owg/wallet/XRPWallet.java index c4d3a29..be24456 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/XRPWallet.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/XRPWallet.java @@ -2,15 +2,14 @@ import java.util.List; -import com.ashelkov.owg.bip.Coin; import com.ashelkov.owg.address.BIP44Address; +import com.ashelkov.owg.bip.Coin; -import static com.ashelkov.owg.bip.Constants.BIP44_PURPOSE; - +/** + * Wallet for storing XRP addresses. + */ public class XRPWallet extends SingleCoinWallet { - public static final int PURPOSE = BIP44_PURPOSE; - public XRPWallet(List derivedAddresses) { super(derivedAddresses, Coin.XRP); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/ACIWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/ACIWalletGenerator.java index e8a3dc2..935f466 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/ACIWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/ACIWalletGenerator.java @@ -2,7 +2,10 @@ import com.ashelkov.owg.wallet.SingleCoinWallet; -public abstract class ACIWalletGenerator extends WalletGenerator { +/** + * Specialization of [[SingleCoinWalletGenerator]] for coins which only use the full BIP-44 path. + */ +public abstract class ACIWalletGenerator extends SingleCoinWalletGenerator { private static final int PATH_LENGTH = 3; @@ -10,20 +13,41 @@ public ACIWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey) { super(seed, genPrivKey, genPubKey); } + /** + * Core logic for generating a [[SingleCoinWallet]] from a partial path. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + */ @Override - protected SingleCoinWallet generatePathWalletLogic(int[] path, int numAddresses) { - return generateWallet(path[0], path[1], path[2], numAddresses); + protected SingleCoinWallet generatePathWalletLogic(int[] partialPath, int numAddresses) { + return generateWallet(partialPath[0], partialPath[1], partialPath[2], numAddresses); } + /** + * Partial path should have exactly three inputs: 'account' value, 'change' value, and 'index' value, in that order. + * + * @param partialPath Path to verify + * @throws IllegalArgumentException + */ @Override - protected void verifyPartialPath(int[] path) - throws IllegalArgumentException - { - if (path.length != PATH_LENGTH) { + protected void verifyPartialPath(int[] partialPath) { + if (partialPath.length != PATH_LENGTH) { throw new IllegalArgumentException( - String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, path.length)); + String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, partialPath.length)); } } + /** + * Generate a [[SingleCoinWallet]] for a particular BIP-44 path. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New wallet + */ public abstract SingleCoinWallet generateWallet(int account, int change, int index, int numAddresses); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountIndexWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountIndexWalletGenerator.java index 4d2ac5b..8b39316 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountIndexWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountIndexWalletGenerator.java @@ -2,7 +2,11 @@ import com.ashelkov.owg.wallet.SingleCoinWallet; -public abstract class AccountIndexWalletGenerator extends WalletGenerator { +/** + * Specialization of [[SingleCoinWalletGenerator]] for coins which only use the 'account' and 'index' field of a BIP-44 + * path. + */ +public abstract class AccountIndexWalletGenerator extends SingleCoinWalletGenerator { private static final int PATH_LENGTH = 2; @@ -10,20 +14,41 @@ public AccountIndexWalletGenerator(byte[] seed, boolean genPrivKey, boolean genP super(seed, genPrivKey, genPubKey); } + /** + * Core logic for generating a [[SingleCoinWallet]] from a partial path. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + */ @Override - protected SingleCoinWallet generatePathWalletLogic(int[] path, int numAddresses) { - return generateWallet(path[0], path[1], numAddresses); + protected SingleCoinWallet generatePathWalletLogic(int[] partialPath, int numAddresses) { + return generateWallet(partialPath[0], partialPath[1], numAddresses); } + /** + * Partial path should have exactly two inputs: 'account' value and 'index' value, in that order. + * + * @param partialPath Path to verify + * @throws IllegalArgumentException + */ @Override - protected void verifyPartialPath(int[] path) - throws IllegalArgumentException - { - if (path.length != PATH_LENGTH) { + protected void verifyPartialPath(int[] partialPath) { + if (partialPath.length != PATH_LENGTH) { throw new IllegalArgumentException( - String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, path.length)); + String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, partialPath.length)); } } + /** + * Generate a [[SingleCoinWallet]] for a particular 'account' and 'index' (the 'purpose' and 'coin' fields are + * determined by the derived class; 'change' field is unused). Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New wallet + */ public abstract SingleCoinWallet generateWallet(int account, int index, int numAddresses); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountWalletGenerator.java index de7c1c9..dc344bb 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/AccountWalletGenerator.java @@ -2,7 +2,10 @@ import com.ashelkov.owg.wallet.SingleCoinWallet; -public abstract class AccountWalletGenerator extends WalletGenerator { +/** + * Specialization of [[SingleCoinWalletGenerator]] for coins which only use the 'account' field of a BIP-44 path. + */ +public abstract class AccountWalletGenerator extends SingleCoinWalletGenerator { private static final int PATH_LENGTH = 1; @@ -10,20 +13,40 @@ public AccountWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey super(seed, genPrivKey, genPubKey); } + /** + * Core logic for generating a [[SingleCoinWallet]] from a partial path. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + */ @Override - protected SingleCoinWallet generatePathWalletLogic(int[] path, int numAddresses) { - return generateWallet(path[0], numAddresses); + protected SingleCoinWallet generatePathWalletLogic(int[] partialPath, int numAddresses) { + return generateWallet(partialPath[0], numAddresses); } + /** + * Partial path should have only one input, namely the 'account' value. + * + * @param partialPath Path to verify + * @throws IllegalArgumentException + */ @Override - protected void verifyPartialPath(int[] path) - throws IllegalArgumentException - { - if (path.length != PATH_LENGTH) { + protected void verifyPartialPath(int[] partialPath) { + if (partialPath.length != PATH_LENGTH) { throw new IllegalArgumentException( - String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, path.length)); + String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, partialPath.length)); } } + /** + * Generate a [[SingleCoinWallet]] for a particular 'account' (the 'purpose' and 'coin' fields are determined by the + * derived class; the 'change' and 'index' fields are unused). Optionally, generate more than one address by + * incrementing the 'account' field. + * + * @param account Account value + * @param numAddresses Number of addresses to generate + * @return New wallet + */ public abstract SingleCoinWallet generateWallet(int account, int numAddresses); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/AlgorandWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/AlgorandWalletGenerator.java index 7c6d0cc..53df37d 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/AlgorandWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/AlgorandWalletGenerator.java @@ -16,6 +16,9 @@ import static com.ashelkov.owg.bip.Constants.HARDENED; import static com.ashelkov.owg.wallet.util.DigestUtils.SHA_512_256; +/** + * Factory class to generate [[AlgorandWallet]] objects. + */ public class AlgorandWalletGenerator extends AccountWalletGenerator { private static final int ADDRESS_LENGTH = 58; @@ -29,6 +32,11 @@ public AlgorandWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKe super(seed, genPrivKey, genPubKey); } + /** + * Generate the default [[AlgorandWallet]] ('account' field has default value). + * + * @return New Algorand wallet containing only the address m/44'/283'/0' + */ @Override public AlgorandWallet generateDefaultWallet() { @@ -38,6 +46,14 @@ public AlgorandWallet generateDefaultWallet() { return new AlgorandWallet(wrapper); } + /** + * Generate an [[AlgorandWallet]] for a particular BIP-44 'account'. Optionally, generate more than one address by + * incrementing the 'account' field. + * + * @param account Account value + * @param numAddresses Number of addresses to generate + * @return New Algorand wallet + */ @Override public AlgorandWallet generateWallet(int account, int numAddresses) { @@ -50,6 +66,12 @@ public AlgorandWallet generateWallet(int account, int numAddresses) { return new AlgorandWallet(addresses); } + /** + * Generate the Algorand address for a particular BIP-44 'account'. + * + * @param account Account value + * @return Algorand address + */ private BIP44Address generateAddress(int account) { int[] addressPath = getAddressPath(account); @@ -76,8 +98,14 @@ private BIP44Address generateAddress(int account) { return new BIP44Address(address, addressPath); } + /** + * Generate the full BIP-44 path for a given Algorand account value. + * + * @param account Account value + * @return BIP-44 path for the given account + */ private int[] getAddressPath(int account) { - int purpose = AlgorandWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = ALGO.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/AvalancheWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/AvalancheWalletGenerator.java index 4355836..f824259 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/AvalancheWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/AvalancheWalletGenerator.java @@ -16,6 +16,9 @@ import static com.ashelkov.owg.bip.Coin.AVAX; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[AvalancheWallet]] objects. + */ public class AvalancheWalletGenerator extends IndexWalletGenerator { // 3 Base chains in Avalanche: Exchange (X), Platform (P), and Contracts (C) @@ -42,6 +45,11 @@ public AvalancheWalletGenerator(byte[] seed, List addressChains, boolean this.addressChains = addressChains; } + /** + * Generate the default [[AvalancheWallet]] ('index' field has default value). + * + * @return New Avalanche wallet containing only the address m/44'/9000'/0'/0/0 + */ @Override public AvalancheWallet generateDefaultWallet() { @@ -51,6 +59,14 @@ public AvalancheWallet generateDefaultWallet() { return new AvalancheWallet(wrapper); } + /** + * Generate a [[AvalancheWallet]] for a particular BIP-44 'index'. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Avalanche wallet + */ @Override public AvalancheWallet generateWallet(int index, int numAddresses) { @@ -64,12 +80,13 @@ public AvalancheWallet generateWallet(int index, int numAddresses) { } /** + * Generate the Avalanche addresses for a particular BIP-44 'index'. * - * https://support.avalabs.org/en/articles/4596397-what-is-an-address - * https://support.avalabs.org/en/articles/4587392-what-is-bech32 + * Avalanche has multiple chains, each of which has its own address for the same BIP-44 path. For more information, + * see https://support.avalabs.org/en/articles/4596397-what-is-an-address . * - * @param index - * @return + * @param index Index value + * @return Ergo address */ private BIP44Address generateAddress(int index) { @@ -112,6 +129,14 @@ private BIP44Address generateAddress(int index) { return new AvalancheAddress(addresses, addressPath, privKeyText, pubKeyText); } + /** + * Generate the Bech32 encoded text addresses used for the X and P chains on Avalanche. For more information, see + * https://support.avalabs.org/en/articles/4587392-what-is-bech32 . + * + * @param keyPair The private-public key-pair of the address + * @param chain The Avalanche chain of the address + * @return Bech32 encoded address + */ private String generateBech32Address(Bip32ECKeyPair keyPair, Chain chain) { StringBuilder addressBuilder = new StringBuilder(ADDRESS_LENGTH); addressBuilder.append(CHAIN_CODE_MAP.get(chain)); @@ -127,8 +152,14 @@ private String generateBech32Address(Bip32ECKeyPair keyPair, Chain chain) { return addressBuilder.toString(); } + /** + * Generate the full BIP-44 path for a given Avalanche index value. + * + * @param index Index value + * @return BIP-44 path for the given index + */ private int[] getAddressPath(int index) { - int purpose = AvalancheWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = AVAX.getCode() | HARDENED; int account = HARDENED; // 0 | HARDENED diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/BitcoinWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/BitcoinWalletGenerator.java index d0049a2..f62a580 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/BitcoinWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/BitcoinWalletGenerator.java @@ -17,6 +17,9 @@ import static com.ashelkov.owg.bip.Constants.CHECKSUM_LENGTH; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[BitcoinWallet]] objects. + */ public class BitcoinWalletGenerator extends ACIWalletGenerator { private static final String BECH32_HRP = "bc"; @@ -26,6 +29,13 @@ public class BitcoinWalletGenerator extends ACIWalletGenerator { // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format private static final int XPUB_CORE_LENGTH = 78; + /** + * Generate a Bitcoin-style private key in text format. + * + * @param rawKeyBytes Raw bytes representing private key as integer + * @param blockchainIdPrefix Unique ID of blockchain for which private key is being generated + * @return Private key as String + */ public static String generatePrivateKey(byte[] rawKeyBytes, byte blockchainIdPrefix) { byte[] privKeyBase = new byte[34]; byte[] privKeyBytes = new byte[38]; @@ -47,6 +57,11 @@ public BitcoinWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[BitcoinWallet]] ('account', 'change', 'index', etc. fields have default values). + * + * @return New Bitcoin wallet containing only the address m/84'/0'/0'/0/0 + */ @Override public BitcoinWallet generateDefaultWallet() { @@ -57,6 +72,16 @@ public BitcoinWallet generateDefaultWallet() { return new BitcoinWallet(masterPubKey, wrapper); } + /** + * Generate a [[BitcoinWallet]] for a particular BIP-44 path. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Bitcoin wallet + */ @Override public BitcoinWallet generateWallet(int account, int change, int index, int numAddresses) { @@ -70,6 +95,14 @@ public BitcoinWallet generateWallet(int account, int change, int index, int numA return new BitcoinWallet(masterPubKey, derivedAddresses); } + /** + * Generate the Bitcoin address for a particular BIP-44 path. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return Bitcoin address + */ private BIP84Address generateDerivedAddress(int account, int change, int index) { int[] addressPath = getDerivedAddressPath(account, change, index); @@ -98,6 +131,12 @@ private BIP84Address generateDerivedAddress(int account, int change, int index) return new BIP84Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the extended public address for a particular Bitcoin account. + * + * @param account Account value + * @return Bitcoin extended public address + */ private BIP84Address generateExtendedKey(int account) { int[] addressPath = getAccountAddressPath(account); @@ -122,15 +161,29 @@ private BIP84Address generateExtendedKey(int account) { return new BIP84Address(xpubKeySerialized, addressPath); } + /** + * Generate the full BIP-44 path of a given Bitcoin account. + * + * @param account Account value + * @return BIP-44 path for the given account + */ private int[] getAccountAddressPath(int account) { - int purpose = BitcoinWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = BTC.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; } + /** + * Generate the full BIP-44 path for given Bitcoin account, change, and index values. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return BIP-44 path for the given values + */ private int[] getDerivedAddressPath(int account, int change, int index) { - int purpose = BitcoinWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = BTC.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED, change, index}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/DogecoinWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/DogecoinWalletGenerator.java index f2497fa..3053243 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/DogecoinWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/DogecoinWalletGenerator.java @@ -16,6 +16,9 @@ import static com.ashelkov.owg.bip.Coin.DOGE; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[DogecoinWallet]] objects. + */ public class DogecoinWalletGenerator extends ACIWalletGenerator { private static final byte DOGE_IDENTIFICATION_PREFIX = (byte)0x9E; @@ -28,6 +31,11 @@ public DogecoinWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKe this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[DogecoinWallet]] ('account', 'change', 'index', etc. fields have default values). + * + * @return New Dogecoin wallet containing only the address m/44'/3'/0'/0/0 + */ @Override public DogecoinWallet generateDefaultWallet() { @@ -37,6 +45,16 @@ public DogecoinWallet generateDefaultWallet() { return new DogecoinWallet(wrapper); } + /** + * Generate a [[DogecoinWallet]] for a particular BIP-44 path. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Dogecoin wallet + */ @Override public DogecoinWallet generateWallet(int account, int change, int index, int numAddresses) { @@ -49,6 +67,14 @@ public DogecoinWallet generateWallet(int account, int change, int index, int num return new DogecoinWallet(addresses); } + /** + * Generate the Dogecoin address for a particular BIP-44 path. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return Dogecoin address + */ private BIP44Address generateAddress(int account, int change, int index) { int[] addressPath = getAddressPath(account, change, index); @@ -79,8 +105,16 @@ private BIP44Address generateAddress(int account, int change, int index) { return new BIP44Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the full BIP-44 path for given Dogecoin account, change, and index values. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return BIP-44 path for the given values + */ private int[] getAddressPath(int account, int change, int index) { - int purpose = DogecoinWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = DOGE.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED, change, index}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/ErgoWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/ErgoWalletGenerator.java index f3b8861..7b32a12 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/ErgoWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/ErgoWalletGenerator.java @@ -14,6 +14,9 @@ import static com.ashelkov.owg.bip.Constants.CHECKSUM_LENGTH; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[ErgoWallet]] objects. + */ public class ErgoWalletGenerator extends IndexWalletGenerator { // First 4 bits: @@ -33,6 +36,11 @@ public ErgoWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey) { this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[ErgoWallet]] ('index' field has default value). + * + * @return New Ergo wallet containing only the address m/44'/429'/0'/0/0 + */ @Override public ErgoWallet generateDefaultWallet() { @@ -42,6 +50,14 @@ public ErgoWallet generateDefaultWallet() { return new ErgoWallet(wrapper); } + /** + * Generate a [[ErgoWallet]] for a particular BIP-44 'index'. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Ergo wallet + */ @Override public ErgoWallet generateWallet(int index, int numAddresses) { @@ -54,6 +70,12 @@ public ErgoWallet generateWallet(int index, int numAddresses) { return new ErgoWallet(addresses); } + /** + * Generate the Ergo address for a particular BIP-44 'index'. + * + * @param index Index value + * @return Ergo address + */ private BIP44Address generateAddress(int index) { int[] addressPath = getAddressPath(index); @@ -95,8 +117,14 @@ private BIP44Address generateAddress(int index) { return new BIP44Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the full BIP-44 path for a given Ergo index value. + * + * @param index Index value + * @return BIP-44 path for the given index + */ private int[] getAddressPath(int index) { - int purpose = ErgoWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = ERG.getCode() | HARDENED; int account = HARDENED; // 0 | HARDENED diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/EthereumWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/EthereumWalletGenerator.java index db63e80..8bcb106 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/EthereumWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/EthereumWalletGenerator.java @@ -13,6 +13,9 @@ import static com.ashelkov.owg.bip.Coin.ETH; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[EthereumWallet]] objects. + */ public class EthereumWalletGenerator extends IndexWalletGenerator { private final Bip32ECKeyPair masterKeyPair; @@ -22,6 +25,11 @@ public EthereumWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKe this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[EthereumWallet]] ('index' field has default value). + * + * @return New Ethereum wallet containing only the address m/44'/44'/0'/0/0 + */ @Override public EthereumWallet generateDefaultWallet() { @@ -31,6 +39,14 @@ public EthereumWallet generateDefaultWallet() { return new EthereumWallet(wrapper); } + /** + * Generate a [[EthereumWallet]] for a particular BIP-44 'index'. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Ethereum wallet + */ @Override public EthereumWallet generateWallet(int index, int numAddresses) { @@ -43,6 +59,12 @@ public EthereumWallet generateWallet(int index, int numAddresses) { return new EthereumWallet(addresses); } + /** + * Generate the Ethereum address for a particular BIP-44 'index'. + * + * @param index Index value + * @return Ethereum address + */ private BIP44Address generateAddress(int index) { int[] addressPath = getAddressPath(index); @@ -66,8 +88,14 @@ private BIP44Address generateAddress(int index) { return new BIP44Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the full BIP-44 path for a given Ethereum index value. + * + * @param index Index value + * @return BIP-44 path for the given index + */ private int[] getAddressPath(int index) { - int purpose = EthereumWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = ETH.getCode() | HARDENED; int account = HARDENED; // 0 | HARDENED diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/HandshakeWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/HandshakeWalletGenerator.java index 6be8732..64b7ec1 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/HandshakeWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/HandshakeWalletGenerator.java @@ -17,6 +17,9 @@ import static com.ashelkov.owg.bip.Coin.HNS; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[HandshakeWallet]] objects. + */ public class HandshakeWalletGenerator extends ACIWalletGenerator { private static final String BECH32_HRP = "hs"; @@ -34,6 +37,11 @@ public HandshakeWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubK this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[HandshakeWallet]] ('account', 'change', 'index', etc. fields have default values). + * + * @return New Handshake wallet containing only the address m/84'/5353'/0'/0/0 + */ @Override public HandshakeWallet generateDefaultWallet() { @@ -44,6 +52,16 @@ public HandshakeWallet generateDefaultWallet() { return new HandshakeWallet(masterPubKey, wrapper); } + /** + * Generate a [[HandshakeWallet]] for a particular BIP-44 path. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Handshake wallet + */ @Override public HandshakeWallet generateWallet(int account, int change, int index, int numAddresses) { @@ -57,6 +75,14 @@ public HandshakeWallet generateWallet(int account, int change, int index, int nu return new HandshakeWallet(masterPubKey, derivedAddresses); } + /** + * Generate the Handshake address for a particular BIP-44 path. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return Handshake address + */ private BIP84Address generateDerivedAddress(int account, int change, int index) { int[] addressPath = getDerivedAddressPath(account, change, index); @@ -86,6 +112,12 @@ private BIP84Address generateDerivedAddress(int account, int change, int index) return new BIP84Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the extended public address for a particular Handshake account. + * + * @param account Account value + * @return Handshake extended public address + */ private BIP84Address generateExtendedKey(int account) { int[] addressPath = getAccountAddressPath(account); @@ -110,15 +142,29 @@ private BIP84Address generateExtendedKey(int account) { return new BIP84Address(xpubKeySerialized, addressPath); } + /** + * Generate the full BIP-44 path of a given Handshake account. + * + * @param account Account value + * @return BIP-44 path for the given account + */ private int[] getAccountAddressPath(int account) { - int purpose = HandshakeWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = HNS.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; } + /** + * Generate the full BIP-44 path for given Handshake account, change, and index values. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return BIP-44 path for the given values + */ private int[] getDerivedAddressPath(int account, int change, int index) { - int purpose = HandshakeWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = HNS.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED, change, index}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/IndexWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/IndexWalletGenerator.java index ce00680..5f287b0 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/IndexWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/IndexWalletGenerator.java @@ -2,7 +2,10 @@ import com.ashelkov.owg.wallet.SingleCoinWallet; -public abstract class IndexWalletGenerator extends WalletGenerator { +/** + * Specialization of [[SingleCoinWalletGenerator]] for coins which only use the 'index' field of a BIP-44 path. + */ +public abstract class IndexWalletGenerator extends SingleCoinWalletGenerator { private static final int PATH_LENGTH = 1; @@ -10,20 +13,40 @@ public IndexWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey) super(seed, genPrivKey, genPubKey); } + /** + * Core logic for generating a [[SingleCoinWallet]] from a partial path. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + */ @Override - protected SingleCoinWallet generatePathWalletLogic(int[] path, int numAddresses) { - return generateWallet(path[0], numAddresses); + protected SingleCoinWallet generatePathWalletLogic(int[] partialPath, int numAddresses) { + return generateWallet(partialPath[0], numAddresses); } + /** + * Partial path should have only one input, namely the 'index' value. + * + * @param partialPath Path to verify + * @throws IllegalArgumentException + */ @Override - protected void verifyPartialPath(int[] path) - throws IllegalArgumentException - { - if (path.length != PATH_LENGTH) { + protected void verifyPartialPath(int[] partialPath) { + if (partialPath.length != PATH_LENGTH) { throw new IllegalArgumentException( - String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, path.length)); + String.format(PATH_ERROR_TEMPLATE, PATH_LENGTH, partialPath.length)); } } + /** + * Generate a [[SingleCoinWallet]] for a particular 'index' (the 'purpose' and 'coin' fields are determined by the + * derived class; the 'account' and 'change' fields are unused). Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New wallet + */ public abstract SingleCoinWallet generateWallet(int index, int numAddresses); } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/LitecoinWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/LitecoinWalletGenerator.java index a089c83..7abbdaa 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/LitecoinWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/LitecoinWalletGenerator.java @@ -16,6 +16,9 @@ import static com.ashelkov.owg.bip.Coin.LTC; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[LitecoinWallet]] objects. + */ public class LitecoinWalletGenerator extends ACIWalletGenerator { private static final String BECH32_HRP = "ltc"; @@ -33,6 +36,11 @@ public LitecoinWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKe this.masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed); } + /** + * Generate the default [[LitecoinWallet]] ('account', 'change', 'index', etc. fields have default values). + * + * @return New Litecoin wallet containing only the address m/84'/2'/0'/0/0 + */ @Override public LitecoinWallet generateDefaultWallet() { @@ -43,6 +51,16 @@ public LitecoinWallet generateDefaultWallet() { return new LitecoinWallet(masterPubKey, wrapper); } + /** + * Generate a [[LitecoinWallet]] for a particular BIP-44 path. Optionally, generate more than one address by + * incrementing the 'index' field. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @param numAddresses Number of addresses to generate + * @return New Litecoin wallet + */ @Override public LitecoinWallet generateWallet(int account, int change, int index, int numAddresses) { @@ -56,6 +74,14 @@ public LitecoinWallet generateWallet(int account, int change, int index, int num return new LitecoinWallet(masterPubKey, derivedAddresses); } + /** + * Generate the Litecoin address for a particular BIP-44 path. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return Litecoin address + */ private BIP84Address generateDerivedAddress(int account, int change, int index) { int[] addressPath = getDerivedAddressPath(account, change, index); @@ -86,6 +112,12 @@ private BIP84Address generateDerivedAddress(int account, int change, int index) return new BIP84Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the extended public address for a particular Litecoin account. + * + * @param account Account value + * @return Litecoin extended public address + */ private BIP84Address generateExtendedKey(int account) { int[] addressPath = getAccountAddressPath(account); @@ -110,15 +142,29 @@ private BIP84Address generateExtendedKey(int account) { return new BIP84Address(xpubKeySerialized, addressPath); } + /** + * Generate the full BIP-44 path of a given Litecoin account. + * + * @param account Account value + * @return BIP-44 path for the given account + */ private int[] getAccountAddressPath(int account) { - int purpose = LitecoinWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = LTC.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; } + /** + * Generate the full BIP-44 path for given Litecoin account, change, and index values. + * + * @param account Account value + * @param change Change value + * @param index Index value + * @return BIP-44 path for the given values + */ private int[] getDerivedAddressPath(int account, int change, int index) { - int purpose = LitecoinWallet.PURPOSE | HARDENED; + int purpose = BIP84Address.PURPOSE | HARDENED; int coinCode = LTC.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED, change, index}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/MoneroWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/MoneroWalletGenerator.java index f0bcd72..540c15a 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/MoneroWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/MoneroWalletGenerator.java @@ -20,12 +20,15 @@ import static com.ashelkov.owg.wallet.util.DigestUtils.KECCAK_256; import static com.ashelkov.owg.wallet.util.Ed25519Utils.ED_25519_CURVE_SPEC; +/** + * Factory class to generate [[MoneroWallet]] objects. + */ public class MoneroWalletGenerator extends AccountIndexWalletGenerator { // All Monero addresses use the same path, but different seeds (not actually sure if the Monero standard is to end // the BIP44 path with a hardened 0 account, though) public static final int[] ADDRESS_PATH = { - MoneroWallet.PURPOSE | HARDENED, + BIP44Address.PURPOSE | HARDENED, XMR.getCode() | HARDENED, HARDENED }; @@ -64,11 +67,28 @@ public MoneroWalletGenerator( this.genSpendKey = genSpendKey; } + /** + * Generate the default [[MoneroWallet]] (just the standard address). + * + * @return New Ethereum wallet containing only the standard address at path m/84'/44'/128' + */ @Override public MoneroWallet generateDefaultWallet() { return generateWallet(DEFAULT_FIELD_VAL, DEFAULT_FIELD_VAL, 1); } + /** + * Generate a [[MoneroWallet]] for the standard address or a particular subaddress. Optionally, generate more than + * one subaddress by incrementing the subaddress index. + * + * If multiple subaddresses are desired, but the 'account' and 'index' fields both start at 0, then include the + * standard address. Do not count it towards the total. + * + * @param account Subaddress account value + * @param index Subaddress index value + * @param numAddresses Number of subaddresses to generate + * @return New Monero wallet + */ @Override public MoneroWallet generateWallet(int account, int index, int numAddresses) { // If the user wants more than 1 address, they actually want subaddresses. However, subaddress 0,0 is the @@ -122,14 +142,15 @@ public MoneroWallet generateWallet(int account, int index, int numAddresses) { } /** - * https://monerodocs.org/public-address/subaddress/ + * Generate the Monero subaddress for the given 'account' and 'index' values. For more information regarding Monero + * subaddresses, see https://monerodocs.org/public-address/subaddress/ . * - * @param addressPath - * @param standardPrivateViewKey - * @param standardPublicSpendKey - * @param account - * @param index - * @return + * @param addressPath Not exactly a BIP-44 path; actually a collection of indices used by the [[MoneroAddress]] + * @param standardPrivateViewKey Private view key of the Monero standard address + * @param standardPublicSpendKey Public spend key of the Monero standard address + * @param account Account for which to generate subaddress + * @param index Index for which to generate subaddress + * @return Monero subaddress */ private MoneroAddress generateSubaddress( int[] addressPath, @@ -158,6 +179,15 @@ private MoneroAddress generateSubaddress( return generateMoneroAddress(MAINNET_SUBADDRESS_NETWORK_BYTE, addressPath, D.toByteArray(), C.toByteArray()); } + /** + * Generate a Monero standard address or subaddress from a pair of Monero public keys. + * + * @param networkByte The address prefix byte which determines what kind of address this is and from which net + * @param addressPath Not exactly a BIP-44 path; actually a collection of indices used by the [[MoneroAddress]] + * @param publicSpendKey Public spend key bytes + * @param publicViewKey Public view key bytes + * @return Monero address + */ private MoneroAddress generateMoneroAddress( byte networkByte, int[] addressPath, @@ -188,8 +218,15 @@ private MoneroAddress generateMoneroAddress( return new MoneroAddress(address, addressPath, publicSpendKeyText, publicViewKeyText); } + /** + * Create an ordered set of indices which are used by [[MoneroAddress]]. + * + * @param account Subaddress account + * @param index Subaddress index + * @return Monero "path" + */ private int[] getSubaddressPath(int account, int index) { - int purpose = MoneroWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = XMR.getCode() | HARDENED; // Since this path is for printing and not key generation, account and index definitely should not be hardened diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/SingleCoinWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/SingleCoinWalletGenerator.java new file mode 100644 index 0000000..67223ce --- /dev/null +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/SingleCoinWalletGenerator.java @@ -0,0 +1,74 @@ +package com.ashelkov.owg.wallet.generators; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ashelkov.owg.wallet.SingleCoinWallet; + +/** + * Base class for factory classes which generate [[SingleCoinWallet]] objects. + */ +public abstract class SingleCoinWalletGenerator { + + protected static final Logger LOGGER = LoggerFactory.getLogger(SingleCoinWalletGenerator.class); + protected static final String PATH_ERROR_TEMPLATE = "wallet generator path length error; expected %d, got %d"; + + public static final int DEFAULT_FIELD_VAL = 0; + + protected final byte[] seed; + protected final boolean genPrivKey; + protected final boolean genPubKey; + + public SingleCoinWalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey) { + + if (seed.length == 0) { + throw new IllegalArgumentException("Empty seed"); + } + + this.seed = seed; + this.genPrivKey = genPrivKey; + this.genPubKey = genPubKey; + } + + /** + * Core logic for generating a [[SingleCoinWallet]] from a partial path. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + */ + protected abstract SingleCoinWallet generatePathWalletLogic(int[] partialPath, int numAddresses); + + /** + * Verify that a given partial path is valid (throws IllegalArgumentException if invalid). + * + * @param partialPath Path to verify + * @throws IllegalArgumentException + */ + protected abstract void verifyPartialPath(int[] partialPath); + + /** + * Generate the default [[SingleCoinWallet]] for a coin ('account', 'change', 'index', etc. fields have default + * value). + * + * @return New wallet containing the single default address + */ + public abstract SingleCoinWallet generateDefaultWallet(); + + /** + * Generate a [[SingleCoinWallet]] from a partial path (the 'purpose' and 'coin code' are determined by the wallet). + * Optionally, generate more than one address by incrementing the 'account' or 'index' fields. + * + * This function is a wrapper around the actual wallet-generation logic. + * + * @param partialPath Array of BIP-32 path elements + * @param numAddresses Number of addresses to generate + * @return New wallet containing addresses for given path + * @throws IllegalArgumentException + */ + public SingleCoinWallet generatePathWallet(int[] partialPath, int numAddresses) + { + verifyPartialPath(partialPath); + return generatePathWalletLogic(partialPath, numAddresses); + } +} diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/StellarWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/StellarWalletGenerator.java index 0dd8016..43f39a3 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/StellarWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/StellarWalletGenerator.java @@ -11,12 +11,20 @@ import static com.ashelkov.owg.bip.Coin.XLM; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[StellarWallet]] objects. + */ public class StellarWalletGenerator extends AccountWalletGenerator { public StellarWalletGenerator(byte[] seed, boolean genPrivKey) { super(seed, genPrivKey, false); } + /** + * Generate the default [[StellarWallet]] ('account' field has default value). + * + * @return New Stellar wallet containing only the address m/44'/148'/0' + */ @Override public StellarWallet generateDefaultWallet() { @@ -26,6 +34,14 @@ public StellarWallet generateDefaultWallet() { return new StellarWallet(wrapper); } + /** + * Generate a [[StellarWallet]] for a particular BIP-44 'account'. Optionally, generate more than one address by + * incrementing the 'account' field. + * + * @param account Account value + * @param numAddresses Number of addresses to generate + * @return New Stellar wallet + */ @Override public StellarWallet generateWallet(int account, int numAddresses) { @@ -38,6 +54,12 @@ public StellarWallet generateWallet(int account, int numAddresses) { return new StellarWallet(addresses); } + /** + * Generate the Stellar address for a particular BIP-44 'account'. + * + * @param account Account value + * @return Stellar address + */ private BIP44Address generateAddress(int account) { int[] addressPath = getAddressPath(account); @@ -53,8 +75,14 @@ private BIP44Address generateAddress(int account) { return new BIP44Address(address, addressPath, privKeyText, null); } + /** + * Generate the full BIP-44 path for a given Stellar account value. + * + * @param account Account value + * @return BIP-44 path for the given account + */ private int[] getAddressPath(int account) { - int purpose = StellarWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = XLM.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/WalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/WalletGenerator.java deleted file mode 100644 index f70f025..0000000 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/WalletGenerator.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.ashelkov.owg.wallet.generators; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.ashelkov.owg.wallet.SingleCoinWallet; - -public abstract class WalletGenerator { - - protected static final Logger LOGGER = LoggerFactory.getLogger(WalletGenerator.class); - protected static final String PATH_ERROR_TEMPLATE = "wallet generator path length error; expected %d, got %d"; - - public static final int DEFAULT_FIELD_VAL = 0; - - protected final byte[] seed; - protected final boolean genPrivKey; - protected final boolean genPubKey; - - public WalletGenerator(byte[] seed, boolean genPrivKey, boolean genPubKey) { - - if (seed.length == 0) { - throw new IllegalArgumentException("Empty seed"); - } - - this.seed = seed; - this.genPrivKey = genPrivKey; - this.genPubKey = genPubKey; - } - - protected abstract SingleCoinWallet generatePathWalletLogic(int[] path, int numAddresses); - - protected abstract void verifyPartialPath(int[] path) - throws IllegalArgumentException; - - public abstract SingleCoinWallet generateDefaultWallet(); - - public SingleCoinWallet generatePathWallet(int[] partialPath, int numAddresses) - throws IllegalArgumentException - { - try { - verifyPartialPath(partialPath); - return generatePathWalletLogic(partialPath, numAddresses); - } catch(IllegalArgumentException e) { - LOGGER.error(e.getMessage()); - throw e; - } - } -} diff --git a/core/src/main/java/com/ashelkov/owg/wallet/generators/XRPWalletGenerator.java b/core/src/main/java/com/ashelkov/owg/wallet/generators/XRPWalletGenerator.java index 753c7ca..d450386 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/generators/XRPWalletGenerator.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/generators/XRPWalletGenerator.java @@ -16,6 +16,9 @@ import static com.ashelkov.owg.bip.Constants.CHECKSUM_LENGTH; import static com.ashelkov.owg.bip.Constants.HARDENED; +/** + * Factory class to generate [[XRPWallet]] objects. + */ public class XRPWalletGenerator extends AccountWalletGenerator { private static final byte MASTER_PUB_KEY_PREFIX = (byte)0xED; @@ -30,11 +33,25 @@ public XRPWalletGenerator(byte[] seed, boolean legacy, boolean genPrivKey, boole this.legacy = legacy; } + /** + * Generate the default [[XRPWallet]] ('account' field has default value). + * + * @return New XRP wallet containing only the address m/44'/144'/0'/0/0 (if legacy XRP wallet) or m/44'/144'/0' (if + * modern XRP wallet) + */ @Override public XRPWallet generateDefaultWallet() { return generateWallet(DEFAULT_FIELD_VAL, 1); } + /** + * Generate a [[XRPWallet]] for a particular BIP-44 path. Optionally, generate more than one address by incrementing + * the 'account' field. + * + * @param account Account value + * @param numAddresses Number of addresses to generate + * @return New XRP wallet + */ @Override public XRPWallet generateWallet(int account, int numAddresses) { @@ -56,11 +73,14 @@ public XRPWallet generateWallet(int account, int numAddresses) { } /** + * Generate the XRP address for a particular BIP-44 path using the secp256k1 curve. * - * https://xrpl.org/accounts.html#address-encoding + * This is the old way of generating XRP addresses. Only use this to regenerate addresses made at the time that this + * was the correct way to generate XRP addresses. New addresses should be generated using curve ed25519. For more + * information, see https://xrpl.org/accounts.html#address-encoding . * - * @param account - * @return + * @param account Account value + * @return XRP address */ private BIP44Address generateAddressSECP256k1(int account) { @@ -77,7 +97,7 @@ private BIP44Address generateAddressSECP256k1(int account) { byte[] rawAddress = new byte[25]; System.arraycopy(payload, 0, rawAddress, 0, 21); System.arraycopy(checksum, 0, rawAddress, 21, CHECKSUM_LENGTH); - String address = EncodingUtils.base58Ripple(rawAddress); + String address = EncodingUtils.base58XRP(rawAddress); String privKeyText = null; String pubKeyText = null; @@ -91,6 +111,15 @@ private BIP44Address generateAddressSECP256k1(int account) { return new BIP44Address(address, addressPath, privKeyText, pubKeyText); } + /** + * Generate the XRP address for a particular BIP-44 path using the ed25519 curve. + * + * This is the correct, modern way of generating XRP addresses. For more information, see + * https://xrpl.org/accounts.html#address-encoding . + * + * @param account Account value + * @return XRP address + */ private BIP44Address generateAddressED25519(int account) { int[] addressPathED25519 = getAddressPathED25519(account); @@ -111,20 +140,32 @@ private BIP44Address generateAddressED25519(int account) { byte[] rawAddress = new byte[25]; System.arraycopy(payload, 0, rawAddress, 0, 21); System.arraycopy(checksum, 0, rawAddress, 21, CHECKSUM_LENGTH); - String address = EncodingUtils.base58Ripple(rawAddress); + String address = EncodingUtils.base58XRP(rawAddress); return new BIP44Address(address, addressPathED25519); } + /** + * Generate the full BIP-44 path for a given legacy XRP account. + * + * @param account Account value + * @return BIP-44 path for the legacy XRP address + */ private int[] getAddressPath(int account) { - int purpose = XRPWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = XRP.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED, 0, 0}; } + /** + * Generate the full BIP-44 path for a given modern XRP account. + * + * @param account Account value + * @return BIP-44 path for the modern XRP address + */ private int[] getAddressPathED25519(int account) { - int purpose = XRPWallet.PURPOSE | HARDENED; + int purpose = BIP44Address.PURPOSE | HARDENED; int coinCode = XRP.getCode() | HARDENED; return new int[] {purpose, coinCode, account | HARDENED}; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/util/BIP44Utils.java b/core/src/main/java/com/ashelkov/owg/wallet/util/BIP44Utils.java index 1152b88..b3af30e 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/util/BIP44Utils.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/util/BIP44Utils.java @@ -2,8 +2,18 @@ import com.ashelkov.owg.bip.Constants; +/** + * Utilities for working with [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) compliant HD + * wallet paths. + */ public class BIP44Utils { + /** + * Convert a BIP-44 path to [[String]] in common BIP-44 notation. + * + * @param path Input path + * @return Path as String in common BIP-44 notation + */ public static String convertPathToText(int[] path) { if (path.length == 0) { @@ -15,18 +25,7 @@ public static String convertPathToText(int[] path) { result.append("m/"); for (int chainVal : path) { - // Hardened - if ((chainVal & Constants.HARDENED) != 0) { - int val = chainVal ^ Constants.HARDENED; - - result.append(val); - result.append('\''); - - // Not hardened - } else { - result.append(chainVal); - } - + result.append(chainValToString(chainVal)); result.append('/'); } @@ -35,5 +34,22 @@ public static String convertPathToText(int[] path) { return result.toString(); } + /** + * Convert a BIP-44 path component value to [[String]] in common BIP-44 notation. + * + * @param val BIP-44 path component value + * @return The input value as a [[String]] in common BIP-44 notation + */ + public static String chainValToString(int val) { + // Hardened + if ((val & Constants.HARDENED) != 0) { + return String.format("%d'", (val ^ Constants.HARDENED)); + + // Not hardened + } else { + return String.valueOf(val); + } + } + private BIP44Utils() {} } diff --git a/core/src/main/java/com/ashelkov/owg/wallet/util/DigestUtils.java b/core/src/main/java/com/ashelkov/owg/wallet/util/DigestUtils.java index d5726bd..818d8d1 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/util/DigestUtils.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/util/DigestUtils.java @@ -7,7 +7,12 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; /** - * Copied from Algorand com.algorand.algosdk.util.Digester and com.algorand.algosdk.util.CryptoProvider + * NOTE: This code is copied from Algorand SDK cource classes com.algorand.algosdk.util.Digester and + * com.algorand.algosdk.util.CryptoProvider. + * + * ==================================================================================================================== + * + * Utilities for performing hashes. */ public class DigestUtils { @@ -16,6 +21,9 @@ public class DigestUtils { public static final String KECCAK_256 = "Keccak-256"; public static final String SHA_512_256 = "SHA-512/256"; + /** + * Add a hash function provider if one is not yet present. + */ private static void setupIfNeeded() { // Add bouncy castle provider for crypto, if it's not already added if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -23,6 +31,14 @@ private static void setupIfNeeded() { } } + /** + * Hash given input data using the chosen named algorithm. + * + * @param alg Algorithm to use + * @param data Input data to hash + * @return Hash output + * @throws NoSuchAlgorithmException if named hash function not found + */ public static byte[] digest(String alg, byte[] data) throws NoSuchAlgorithmException { setupIfNeeded(); @@ -33,6 +49,14 @@ public static byte[] digest(String alg, byte[] data) throws NoSuchAlgorithmExcep return digest.digest(); } + /** + * Hash given input data using the chosen named algorithm, but return an empty array if a problem occurs while + * hashing. + * + * @param alg Algorithm to use + * @param data Input data to hash + * @return Hash output + */ public static byte[] unsafeDigest(String alg, byte[] data) { byte[] encodedData; diff --git a/core/src/main/java/com/ashelkov/owg/wallet/util/Ed25519Utils.java b/core/src/main/java/com/ashelkov/owg/wallet/util/Ed25519Utils.java index 603818b..bc32ce4 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/util/Ed25519Utils.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/util/Ed25519Utils.java @@ -10,6 +10,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Utilities for deriving ed25519 curve key pairs. + */ public class Ed25519Utils { private static final Logger logger = LoggerFactory.getLogger(Ed25519Utils.class); @@ -37,13 +40,13 @@ private static long load_4(byte[] in, int offset) { } /** - * NOTE: Code copied from... - * https://github.com/stellar/java-stellar-sdk/blob/ce7de4f074bc16416b537e583bf06227315b879c/src/main/java/org/stellar/sdk/SLIP10.java#L30 + * NOTE: This code copied from + * https://github.com/stellar/java-stellar-sdk/blob/ce7de4f074bc16416b537e583bf06227315b879c/src/main/java/org/stellar/sdk/SLIP10.java#L30 * - * ================================================================================================================ + * ================================================================================================================= * - * Derives only the private key for ED25519 in the manor defined in - * SLIP-0010. + * Derives only the private key for ED25519 in the manner defined by + * [SLIP-0010](https://github.com/satoshilabs/slips/blob/master/slip-0010.md). * * @param seed Seed, the BIP0039 output. * @param indexes an array of indexes that define the path. E.g. for m/1'/2'/3', pass 1, 2, 3. @@ -94,11 +97,12 @@ public static byte[] deriveEd25519PrivateKey(final byte[] seed, final int... ind } /** - * https://github.com/str4d/ed25519-java/blob/master/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOps.java + * NOTE: This code copied form + * https://github.com/str4d/ed25519-java/blob/master/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOps.java * * ========================================================================================================== * - * Reduction modulo the group order $q$. + * 64-byte integer reduction modulo the group order $q$. *

* Input: * $s[0]+256*s[1]+\dots+256^{63}*s[63] = s$ @@ -106,6 +110,9 @@ public static byte[] deriveEd25519PrivateKey(final byte[] seed, final int... ind * Output: * $s[0]+256*s[1]+\dots+256^{31}*s[31] = s \bmod q$ * where $q = 2^{252} + 27742317777372353535851937790883648493$. + * + * @param s 64-byte integer + * @return Input reduced module q */ public static byte[] reduce(byte[] s) { // s0,..., s22 have 21 bits, s23 has 29 bits @@ -395,10 +402,22 @@ public static byte[] reduce(byte[] s) { } /** - * https://github.com/monero-project/monero/blob/dcba757dd283a3396120f0df90fe746e3ec02292/src/crypto/crypto-ops.c + * NOTE: This code copied from + * https://github.com/monero-project/monero/blob/dcba757dd283a3396120f0df90fe746e3ec02292/src/crypto/crypto-ops.c + * + * ========================================================================================================== + * + * 32-byte integer reduction modulo the group order $q$. + *

+ * Input: + * $s[0]+256*s[1]+\dots+256^{63}*s[63] = s$ + *

+ * Output: + * $s[0]+256*s[1]+\dots+256^{31}*s[31] = s \bmod q$ + * where $q = 2^{252} + 27742317777372353535851937790883648493$. * - * @param s - * @return + * @param s 32-byte integer + * @return Input reduced module q */ public static byte[] reduce32(byte[] s) { long s0 = 0x1FFFFF & load_3(s, 0); diff --git a/core/src/main/java/com/ashelkov/owg/wallet/util/EncodingUtils.java b/core/src/main/java/com/ashelkov/owg/wallet/util/EncodingUtils.java index 67f6fde..1fa973e 100644 --- a/core/src/main/java/com/ashelkov/owg/wallet/util/EncodingUtils.java +++ b/core/src/main/java/com/ashelkov/owg/wallet/util/EncodingUtils.java @@ -6,13 +6,13 @@ import org.web3j.crypto.Hash; /** - * + * Utilities used to encode addresses in text format. */ public class EncodingUtils { private static final char[] BITCOIN_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - private static final char[] RIPPLE_ALPHABET = + private static final char[] XRP_ALPHABET = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz".toCharArray(); private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); private static final int CHECKSUM_BYTES = 4; @@ -26,18 +26,17 @@ private static String doEncodeBase58(byte[] input, char[] alphabet) { } /** - * - * NOTE: This code is mostly copied from... - * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/Base58.java + * NOTE: This code is mostly copied from: + * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/Base58.java * * ================================================================================================================ * - * Encodes the given bytes as a base58 string (no checksum is appended). + * Encodes the given bytes as a base58 string (checksum optionally appended). * - * @param rawInput the bytes to encode - * @param alphabet - * @param checksum - * @return the base58-encoded string + * @param rawInput Bytes to encode + * @param alphabet Ordered alphabet used for encoding + * @param checksum Append checksum to bytes before encoding if true + * @return The input bytes as a base58-encoded [[String]] */ private static String doEncodeBase58(byte[] rawInput, char[] alphabet, boolean checksum) { if (rawInput.length == 0) { @@ -83,16 +82,21 @@ private static String doEncodeBase58(byte[] rawInput, char[] alphabet, boolean c } /** - * Divides a number, represented as an array of bytes each containing a single digit - * in the specified base, by the given divisor. The given number is modified in-place - * to contain the quotient, and the return value is the remainder. + * NOTE: This code is mostly copied from: + * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/Base58.java + * + * ================================================================================================================ * - * @param number the number to divide - * @param firstDigit the index within the array of the first non-zero digit - * (this is used for optimization by skipping the leading zeros) - * @param base the base in which the number's digits are represented (up to 256) - * @param divisor the number to divide by (up to 256) - * @return the remainder of the division operation + * Divides a number, represented as an array of bytes each containing a single digit in the specified base, by the + * given divisor. The given number is modified in-place to contain the quotient, and the return value is the + * remainder. + * + * @param number The number to divide + * @param firstDigit The index within the array of the first non-zero digit (this is used for optimization by + * skipping the leading zeros) + * @param base The base in which the number's digits are represented (up to 256) + * @param divisor The number to divide by (up to 256) + * @return The remainder of the division operation */ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { // this is just long division which accounts for the base of the input digits @@ -106,40 +110,55 @@ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) return (byte) remainder; } + /** + * Encodes the given bytes as a base58 string using the Bitcoin alphabet (checksum optionally appended). + * + * @param input Bytes to encode + * @param checksum Append checksum to bytes before encoding if true + * @return The input bytes as a base58-encoded [[String]] + */ public static String base58Bitcoin(byte[] input, boolean checksum) { return doEncodeBase58(input, BITCOIN_ALPHABET, checksum); } /** - * Encodes the given bytes as a base58 string (no checksum is appended) for Bitcoin. + * Encodes the given bytes as a base58 string using the Bitcoin alphabet (no checksum is appended). * - * @param input the bytes to encode - * @return the base58-encoded string + * @param input Bytes to encode + * @return The input bytes as a base58-encoded [[String]] */ public static String base58Bitcoin(byte[] input) { - return doEncodeBase58(input, BITCOIN_ALPHABET); + return base58Bitcoin(input, false); } - public static String base58Ripple(byte[] input, boolean checksum) { - return doEncodeBase58(input, RIPPLE_ALPHABET, checksum); + /** + * Encodes the given bytes as a base58 string using the XRP alphabet (checksum optionally appended). + * + * @param input Bytes to encode + * @param checksum Append checksum to bytes before encoding if true + * @return The input bytes as a base58-encoded [[String]] + */ + public static String base58XRP(byte[] input, boolean checksum) { + return doEncodeBase58(input, XRP_ALPHABET, checksum); } /** - * Encodes the given bytes as a base58 string (no checksum is appended) for Ripple. + * Encodes the given bytes as a base58 string using the XRP alphabet (no checksum is appended). * - * @param input the bytes to encode - * @return the base58-encoded string + * @param input Bytes to encode + * @return The input bytes as a base58-encoded [[String]] */ - public static String base58Ripple(byte[] input) { - return doEncodeBase58(input, RIPPLE_ALPHABET); + public static String base58XRP(byte[] input) { + return base58XRP(input, false); } /** + * Encodes the given bytes as a base58 string using the Monero alphabet (no checksum is appended). * - * https://monerodocs.org/cryptography/base58/ + * Note: Full documentation here: https://monerodocs.org/cryptography/base58/ * - * @param input - * @return + * @param input Bytes to encode + * @return The input bytes as a base58-encoded [[String]] */ public static String base58Monero(byte[] input) { @@ -169,6 +188,12 @@ public static String base58Monero(byte[] input) { return result.toString(); } + /** + * Append a checksum to then end of a byte array (the right-most SHA256 bytes are used for the checksum). + * + * @param input Bytes to which to append checksum + * @return The input bytes with a SHA256 checksum appended + */ public static byte[] appendChecksum(byte[] input) { byte[] result = new byte[input.length + CHECKSUM_BYTES]; byte[] checksum = Hash.sha256(input); @@ -179,9 +204,14 @@ public static byte[] appendChecksum(byte[] input) { } /** + * Convert an input array of regular bytes to 5-bit bytes for use with a base-32 encoding algorithm. + * + * Clearly, this algorithm works best when the input length is a multiple of 5, as a byte is 8 bits in length and + * the lowest common multiple of 5 and 8 is 40. This algorithm is "safe", in that it will pad the input with 0 bytes + * if the length is not a multiple of 5. * - * @param input - * @return + * @param input Bytes to encode + * @return The input byte array converted to a longer array of 5-bit bytes */ public static byte[] to5BitBytesSafe(byte[] input) { @@ -213,13 +243,15 @@ public static byte[] to5BitBytesSafe(byte[] input) { } /** - * Code taken from the top answer to this question: - * https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java + * NOTE: Code taken from the top answer to this question: + * https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java * * ================================================================================================================ * - * @param bytes - * @return + * Convert bytes to a [[String]] of their hexadecimal values. + * + * @param bytes Bytes to encode + * @return String of hexadecimal characters */ public static String bytesToHex(byte[] bytes) { byte[] hexChars = new byte[bytes.length * 2]; @@ -238,6 +270,13 @@ public static String bytesToHex(byte[] bytes) { return new String(hexChars, StandardCharsets.UTF_8); } + /** + * Convert an integer to a 4-byte array (little or big endian). + * + * @param i Input integer + * @param isLittleEndian If true, interpret input as little-endian, otherwise interpret as big-endian + * @return Input integer as array of 4 bytes + */ public static byte[] intToFourBytes(int i, boolean isLittleEndian) { byte[] result = new byte[4]; diff --git a/lib/offline-wallet-generator-latest.jar b/lib/offline-wallet-generator-latest.jar index a3187fb..2fbfc6c 100644 Binary files a/lib/offline-wallet-generator-latest.jar and b/lib/offline-wallet-generator-latest.jar differ