package io.rudefox.cold; import com.bjdweck.bitcoin.address.BitcoinAddress; import com.bjdweck.bitcoin.extendedkey.AddressChain; import com.bjdweck.bitcoin.extendedkey.DerivationPath; import com.bjdweck.bitcoin.extendedkey.KeyOrigin; import com.bjdweck.bitcoin.extendedkey.PrivateExtendedKey; import com.bjdweck.bitcoin.hdwallet.Bip44Purpose; import com.bjdweck.bitcoin.hdwallet.Bip44SigningAccount; import com.bjdweck.bitcoin.hdwallet.Bip44Wallet; import com.bjdweck.bitcoin.mnemonic.Mnemonic; import com.bjdweck.bitcoin.mnemonic.Seed; import com.bjdweck.bitcoin.psbt.Psbt; import com.bjdweck.bitcoin.transaction.scriptpubkey.ScriptPubKey; import picocli.CommandLine; @SuppressWarnings("ALL") @CommandLine.Command(name = "wallet", description = "BIP44 wallet operations") public class WalletCommand implements Runnable { @CommandLine.ParentCommand private RudefoxCold globalOptions; @CommandLine.Option(names = {"-s", "--sentence"}, description = "mnemonic sentence", required = true) String sentence; @CommandLine.Option(names = {"-p", "--passphrase"}, description = "optional mnemonic passphrase") String passphrase; Bip44Wallet getWallet() { Seed seed = Mnemonic.fromSentence(sentence).generateSeed(passphrase); return new Bip44Wallet(PrivateExtendedKey.fromSeed(seed), globalOptions.getNetworkParameters()); } Bip44SigningAccount getAccount(Bip44Purpose purpose, int accountSeq) { return getWallet().deriveAccount(purpose, accountSeq); } @CommandLine.Command(name = "listxpub", description = "list xpub (default: SLIP-0132)") public int listXpub( @CommandLine.Option(names = {"-q", "--qrcode"}, description = "output QR code") boolean isQRCode, @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", defaultValue = "P2PKH") Bip44Purpose purpose, @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", paramLabel = "n", defaultValue = "0") int accountSeq) { Bip44SigningAccount bip44SigningAccount = getAccount(purpose, accountSeq); String slip0132 = bip44SigningAccount.toXpubSlip0132Base58(); String keyOrigin = bip44SigningAccount.getKeyOrigin().toString(); if (isQRCode) { System.out.println(QRCode.toQRCode(slip0132)); return 0; } System.out.printf("[%s]%s%n", keyOrigin, slip0132); return 0; } @CommandLine.Command(name = "listaddress", description = "list account addresses") public int listAddress( @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", defaultValue = "P2PKH") Bip44Purpose purpose, @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", paramLabel = "", defaultValue = "0") int accountSeq, @CommandLine.Option(names = {"-c", "--address-chain"}, description = "address chain", paramLabel = "[INTERNAL|EXTERNAL]", defaultValue = "EXTERNAL") AddressChain addressChain, @CommandLine.Option(names = {"-s", "--address-seq"}, description = "starting address sequence number", paramLabel = "", defaultValue = "0") int addressSeq, @CommandLine.Option(names = {"-n", "--count"}, description = "number of addresses to list", paramLabel = "", defaultValue = "10") int count) { Bip44SigningAccount account = getAccount(purpose, accountSeq); for (int i = 0; i < count; i++) { int seq = addressSeq + i; DerivationPath addressPath = DerivationPath.m().slash(addressChain.getSequence()).slash(seq); KeyOrigin keyOrigin = account.getKeyOrigin().combineRelativePath(addressPath); ScriptPubKey scriptPubKey = account.deriveScriptPubKey(addressChain, seq); BitcoinAddress address = scriptPubKey.getAddress(globalOptions.getNetworkParameters()); System.out.printf("%s: %s%n", keyOrigin, address); } return 0; } @CommandLine.Command(name = "signpsbt", description = "sign a PSBT") public int signPsbt( @CommandLine.Parameters() String psbtBase64) { Psbt psbt = Psbt.fromBase64(psbtBase64); psbt.getValidationViolationMap(); /* Satoshi fee = psbt.getFee(); Satoshi spendAmount = psbt.getSpendAmount(getWallet()); BitcoinAddress spendAddress = psbt.getSpendAddress(getWallet()); System.out.printf("Confirm spend of %s to %s with a fee of %s", spendAmount.toBitcoinString(), spendAddress.getAddressString(), fee.toBitcoinString()); */ psbt.signPsbt(getWallet()); System.out.println(psbt.toBase64()); return 0; } @CommandLine.Spec CommandLine.Model.CommandSpec commandSpec; public void run() { throw new CommandLine.ParameterException(commandSpec.commandLine(), "Missing required wallet subcommand"); } }