All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoinj.crypto.DeterministicKey Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Matija Mazi.
 * Copyright 2014 Andreas Schildbach
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bitcoinj.crypto;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.core.Utils.HEX;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.script.Script;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.math.ec.ECPoint;

import com.google.common.base.MoreObjects;

/**
 * A deterministic key is a node in a {@link DeterministicHierarchy}. As per
 * the BIP 32 specification it is a pair
 * (key, chaincode). If you know its path in the tree and its chain code you can derive more keys from this. To obtain
 * one of these, you can call {@link HDKeyDerivation#createMasterPrivateKey(byte[])}.
 */
public class DeterministicKey extends ECKey {

    /** Sorts deterministic keys in the order of their child number. That's usually the order used to derive them. */
    public static final Comparator CHILDNUM_ORDER = new Comparator() {
        @Override
        public int compare(ECKey k1, ECKey k2) {
            ChildNumber cn1 = ((DeterministicKey) k1).getChildNumber();
            ChildNumber cn2 = ((DeterministicKey) k2).getChildNumber();
            return cn1.compareTo(cn2);
        }
    };

    private final DeterministicKey parent;
    private final HDPath childNumberPath;
    private final int depth;
    private int parentFingerprint; // 0 if this key is root node of key hierarchy

    /** 32 bytes */
    private final byte[] chainCode;

    /** Constructs a key from its components. This is not normally something you should use. */
    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            LazyECPoint publicAsPoint,
                            @Nullable BigInteger priv,
                            @Nullable DeterministicKey parent) {
        super(priv, compressPoint(checkNotNull(publicAsPoint)));
        checkArgument(chainCode.length == 32);
        this.parent = parent;
        this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
        this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
        this.depth = parent == null ? 0 : parent.depth + 1;
        this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
    }

    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            ECPoint publicAsPoint,
                            boolean compressed,
                            @Nullable BigInteger priv,
                            @Nullable DeterministicKey parent) {
        this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint, compressed), priv, parent);
    }

    /** Constructs a key from its components. This is not normally something you should use. */
    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            BigInteger priv,
                            @Nullable DeterministicKey parent) {
        super(priv, ECKey.publicPointFromPrivate(priv), true);
        checkArgument(chainCode.length == 32);
        this.parent = parent;
        this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
        this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
        this.depth = parent == null ? 0 : parent.depth + 1;
        this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
    }

    /** Constructs a key from its components. This is not normally something you should use. */
    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            KeyCrypter crypter,
                            LazyECPoint pub,
                            EncryptedData priv,
                            @Nullable DeterministicKey parent) {
        this(childNumberPath, chainCode, pub, null, parent);
        this.encryptedPrivateKey = checkNotNull(priv);
        this.keyCrypter = checkNotNull(crypter);
    }

    /**
     * Return the fingerprint of this key's parent as an int value, or zero if this key is the
     * root node of the key hierarchy.  Raise an exception if the arguments are inconsistent.
     * This method exists to avoid code repetition in the constructors.
     */
    private int ascertainParentFingerprint(DeterministicKey parentKey, int parentFingerprint)
    throws IllegalArgumentException {
        if (parentFingerprint != 0) {
            if (parent != null)
                checkArgument(parent.getFingerprint() == parentFingerprint,
                              "parent fingerprint mismatch",
                              Integer.toHexString(parent.getFingerprint()), Integer.toHexString(parentFingerprint));
            return parentFingerprint;
        } else return 0;
    }

    /**
     * Constructs a key from its components, including its public key data and possibly-redundant
     * information about its parent key.  Invoked when deserializing, but otherwise not something that
     * you normally should use.
     */
    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            LazyECPoint publicAsPoint,
                            @Nullable DeterministicKey parent,
                            int depth,
                            int parentFingerprint) {
        super(null, compressPoint(checkNotNull(publicAsPoint)));
        checkArgument(chainCode.length == 32);
        this.parent = parent;
        this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
        this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
        this.depth = depth;
        this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
    }

    /**
     * Constructs a key from its components, including its private key data and possibly-redundant
     * information about its parent key.  Invoked when deserializing, but otherwise not something that
     * you normally should use.
     */
    public DeterministicKey(List childNumberPath,
                            byte[] chainCode,
                            BigInteger priv,
                            @Nullable DeterministicKey parent,
                            int depth,
                            int parentFingerprint) {
        super(priv, ECKey.publicPointFromPrivate(priv), true);
        checkArgument(chainCode.length == 32);
        this.parent = parent;
        this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
        this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
        this.depth = depth;
        this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
    }


    /** Clones the key */
    public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
        super(keyToClone.priv, keyToClone.pub.get(), keyToClone.pub.isCompressed());
        this.parent = newParent;
        this.childNumberPath = keyToClone.childNumberPath;
        this.chainCode = keyToClone.chainCode;
        this.encryptedPrivateKey = keyToClone.encryptedPrivateKey;
        this.depth = this.childNumberPath.size();
        this.parentFingerprint = this.parent.getFingerprint();
    }

    /**
     * Returns the path through some {@link DeterministicHierarchy} which reaches this keys position in the tree.
     * A path can be written as 0/1/0 which means the first child of the root, the second child of that node, then
     * the first child of that node.
     */
    public HDPath getPath() {
        return childNumberPath;
    }

    /**
     * Returns the path of this key as a human readable string starting with M or m to indicate the master key.
     */
    public String getPathAsString() {
        return getPath().toString();
    }

    /**
     * Return this key's depth in the hierarchy, where the root node is at depth zero.
     * This may be different than the number of segments in the path if this key was
     * deserialized without access to its parent.
     */
    public int getDepth() {
        return depth;
    }

    /** Returns the last element of the path returned by {@link DeterministicKey#getPath()} */
    public ChildNumber getChildNumber() {
        return childNumberPath.size() == 0 ? ChildNumber.ZERO : childNumberPath.get(childNumberPath.size() - 1);
    }

    /**
     * Returns the chain code associated with this key. See the specification to learn more about chain codes.
     */
    public byte[] getChainCode() {
        return chainCode;
    }

    /**
     * Returns RIPE-MD160(SHA256(pub key bytes)).
     */
    public byte[] getIdentifier() {
        return Utils.sha256hash160(getPubKey());
    }

    /** Returns the first 32 bits of the result of {@link #getIdentifier()}. */
    public int getFingerprint() {
        // TODO: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint."
        return ByteBuffer.wrap(Arrays.copyOfRange(getIdentifier(), 0, 4)).getInt();
    }

    @Nullable
    public DeterministicKey getParent() {
        return parent;
    }

    /**
     * Return the fingerprint of the key from which this key was derived, if this is a
     * child key, or else an array of four zero-value bytes.
     */
    public int getParentFingerprint() {
        return parentFingerprint;
    }

    /**
     * Returns private key bytes, padded with zeros to 33 bytes.
     * @throws java.lang.IllegalStateException if the private key bytes are missing.
     */
    public byte[] getPrivKeyBytes33() {
        byte[] bytes33 = new byte[33];
        byte[] priv = getPrivKeyBytes();
        System.arraycopy(priv, 0, bytes33, 33 - priv.length, priv.length);
        return bytes33;
    }

    /**
     * Returns the same key with the private bytes removed. May return the same instance. The purpose of this is to save
     * memory: the private key can always be very efficiently rederived from a parent that a private key, so storing
     * all the private keys in RAM is a poor tradeoff especially on constrained devices. This means that the returned
     * key may still be usable for signing and so on, so don't expect it to be a true pubkey-only object! If you want
     * that then you should follow this call with a call to {@link #dropParent()}.
     */
    public DeterministicKey dropPrivateBytes() {
        if (isPubKeyOnly())
            return this;
        else
            return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
    }

    /**
     * 

Returns the same key with the parent pointer removed (it still knows its own path and the parent fingerprint).

* *

If this key doesn't have private key bytes stored/cached itself, but could rederive them from the parent, then * the new key returned by this method won't be able to do that. Thus, using dropPrivateBytes().dropParent() on a * regular DeterministicKey will yield a new DeterministicKey that cannot sign or do other things involving the * private key at all.

*/ public DeterministicKey dropParent() { DeterministicKey key = new DeterministicKey(getPath(), getChainCode(), pub, priv, null); key.parentFingerprint = parentFingerprint; return key; } static byte[] addChecksum(byte[] input) { int inputLength = input.length; byte[] checksummed = new byte[inputLength + 4]; System.arraycopy(input, 0, checksummed, 0, inputLength); byte[] checksum = Sha256Hash.hashTwice(input); System.arraycopy(checksum, 0, checksummed, inputLength, 4); return checksummed; } @Override public DeterministicKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { throw new UnsupportedOperationException("Must supply a new parent for encryption"); } public DeterministicKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey, @Nullable DeterministicKey newParent) throws KeyCrypterException { // Same as the parent code, except we construct a DeterministicKey instead of an ECKey. checkNotNull(keyCrypter); if (newParent != null) checkArgument(newParent.isEncrypted()); final byte[] privKeyBytes = getPrivKeyBytes(); checkState(privKeyBytes != null, "Private key is not available"); EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, aesKey); DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, keyCrypter, pub, encryptedPrivateKey, newParent); if (newParent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; } /** * A deterministic key is considered to be 'public key only' if it hasn't got a private key part and it cannot be * rederived. If the hierarchy is encrypted this returns true. */ @Override public boolean isPubKeyOnly() { return super.isPubKeyOnly() && (parent == null || parent.isPubKeyOnly()); } @Override public boolean hasPrivKey() { return findParentWithPrivKey() != null; } @Nullable @Override public byte[] getSecretBytes() { return priv != null ? getPrivKeyBytes() : null; } /** * A deterministic key is considered to be encrypted if it has access to encrypted private key bytes, OR if its * parent does. The reason is because the parent would be encrypted under the same key and this key knows how to * rederive its own private key bytes from the parent, if needed. */ @Override public boolean isEncrypted() { return priv == null && (super.isEncrypted() || (parent != null && parent.isEncrypted())); } /** * Returns this keys {@link KeyCrypter} or the keycrypter of its parent key. */ @Override @Nullable public KeyCrypter getKeyCrypter() { if (keyCrypter != null) return keyCrypter; else if (parent != null) return parent.getKeyCrypter(); else return null; } @Override public ECDSASignature sign(Sha256Hash input, @Nullable KeyParameter aesKey) throws KeyCrypterException { if (isEncrypted()) { // If the key is encrypted, ECKey.sign will decrypt it first before rerunning sign. Decryption walks the // key hierarchy to find the private key (see below), so, we can just run the inherited method. return super.sign(input, aesKey); } else { // If it's not encrypted, derive the private via the parents. final BigInteger privateKey = findOrDerivePrivateKey(); if (privateKey == null) { // This key is a part of a public-key only hierarchy and cannot be used for signing throw new MissingPrivateKeyException(); } return super.doSign(input, privateKey); } } @Override public DeterministicKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { checkNotNull(keyCrypter); // Check that the keyCrypter matches the one used to encrypt the keys, if set. if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) throw new KeyCrypterException("The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it"); BigInteger privKey = findOrDeriveEncryptedPrivateKey(keyCrypter, aesKey); DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, privKey, parent); if (!Arrays.equals(key.getPubKey(), getPubKey())) throw new KeyCrypterException.PublicPrivateMismatch("Provided AES key is wrong"); if (parent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; } @Override public DeterministicKey decrypt(KeyParameter aesKey) throws KeyCrypterException { return (DeterministicKey) super.decrypt(aesKey); } // For when a key is encrypted, either decrypt our encrypted private key bytes, or work up the tree asking parents // to decrypt and re-derive. private BigInteger findOrDeriveEncryptedPrivateKey(KeyCrypter keyCrypter, KeyParameter aesKey) { if (encryptedPrivateKey != null) { byte[] decryptedKey = keyCrypter.decrypt(encryptedPrivateKey, aesKey); if (decryptedKey.length != 32) throw new KeyCrypterException.InvalidCipherText( "Decrypted key must be 32 bytes long, but is " + decryptedKey.length); return new BigInteger(1, decryptedKey); } // Otherwise we don't have it, but maybe we can figure it out from our parents. Walk up the tree looking for // the first key that has some encrypted private key data. DeterministicKey cursor = parent; while (cursor != null) { if (cursor.encryptedPrivateKey != null) break; cursor = cursor.parent; } if (cursor == null) throw new KeyCrypterException("Neither this key nor its parents have an encrypted private key"); byte[] parentalPrivateKeyBytes = keyCrypter.decrypt(cursor.encryptedPrivateKey, aesKey); if (parentalPrivateKeyBytes.length != 32) throw new KeyCrypterException.InvalidCipherText( "Decrypted key must be 32 bytes long, but is " + parentalPrivateKeyBytes.length); return derivePrivateKeyDownwards(cursor, parentalPrivateKeyBytes); } private DeterministicKey findParentWithPrivKey() { DeterministicKey cursor = this; while (cursor != null) { if (cursor.priv != null) break; cursor = cursor.parent; } return cursor; } @Nullable private BigInteger findOrDerivePrivateKey() { DeterministicKey cursor = findParentWithPrivKey(); if (cursor == null) return null; return derivePrivateKeyDownwards(cursor, cursor.priv.toByteArray()); } private BigInteger derivePrivateKeyDownwards(DeterministicKey cursor, byte[] parentalPrivateKeyBytes) { DeterministicKey downCursor = new DeterministicKey(cursor.childNumberPath, cursor.chainCode, cursor.pub, new BigInteger(1, parentalPrivateKeyBytes), cursor.parent); // Now we have to rederive the keys along the path back to ourselves. That path can be found by just truncating // our path with the length of the parents path. List path = childNumberPath.subList(cursor.getPath().size(), childNumberPath.size()); for (ChildNumber num : path) { downCursor = HDKeyDerivation.deriveChildKey(downCursor, num); } // downCursor is now the same key as us, but with private key bytes. // If it's not, it means we tried decrypting with an invalid password and earlier checks e.g. for padding didn't // catch it. if (!downCursor.pub.equals(pub)) throw new KeyCrypterException.PublicPrivateMismatch("Could not decrypt bytes"); return checkNotNull(downCursor.priv); } /** * Derives a child at the given index using hardened derivation. Note: {@code index} is * not the "i" value. If you want the softened derivation, then use instead * {@code HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, false))}. */ public DeterministicKey derive(int child) { return HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, true)); } /** * Returns the private key of this deterministic key. Even if this object isn't storing the private key, * it can be re-derived by walking up to the parents if necessary and this is what will happen. * @throws java.lang.IllegalStateException if the parents are encrypted or a watching chain. */ @Override public BigInteger getPrivKey() { final BigInteger key = findOrDerivePrivateKey(); checkState(key != null, "Private key bytes not available"); return key; } @Deprecated public byte[] serializePublic(NetworkParameters params) { return serialize(params, true, Script.ScriptType.P2PKH); } @Deprecated public byte[] serializePrivate(NetworkParameters params) { return serialize(params, false, Script.ScriptType.P2PKH); } private byte[] serialize(NetworkParameters params, boolean pub, Script.ScriptType outputScriptType) { ByteBuffer ser = ByteBuffer.allocate(78); if (outputScriptType == Script.ScriptType.P2PKH) ser.putInt(pub ? params.getBip32HeaderP2PKHpub() : params.getBip32HeaderP2PKHpriv()); else if (outputScriptType == Script.ScriptType.P2WPKH) ser.putInt(pub ? params.getBip32HeaderP2WPKHpub() : params.getBip32HeaderP2WPKHpriv()); else throw new IllegalStateException(outputScriptType.toString()); ser.put((byte) getDepth()); ser.putInt(getParentFingerprint()); ser.putInt(getChildNumber().i()); ser.put(getChainCode()); ser.put(pub ? getPubKey() : getPrivKeyBytes33()); checkState(ser.position() == 78); return ser.array(); } public String serializePubB58(NetworkParameters params, Script.ScriptType outputScriptType) { return toBase58(serialize(params, true, outputScriptType)); } public String serializePrivB58(NetworkParameters params, Script.ScriptType outputScriptType) { return toBase58(serialize(params, false, outputScriptType)); } public String serializePubB58(NetworkParameters params) { return serializePubB58(params, Script.ScriptType.P2PKH); } public String serializePrivB58(NetworkParameters params) { return serializePrivB58(params, Script.ScriptType.P2PKH); } static String toBase58(byte[] ser) { return Base58.encode(addChecksum(ser)); } /** Deserialize a base-58-encoded HD Key with no parent */ public static DeterministicKey deserializeB58(String base58, NetworkParameters params) { return deserializeB58(null, base58, params); } /** * Deserialize a base-58-encoded HD Key. * @param parent The parent node in the given key's deterministic hierarchy. * @throws IllegalArgumentException if the base58 encoded key could not be parsed. */ public static DeterministicKey deserializeB58(@Nullable DeterministicKey parent, String base58, NetworkParameters params) { return deserialize(params, Base58.decodeChecked(base58), parent); } /** * Deserialize an HD Key with no parent */ public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey) { return deserialize(params, serializedKey, null); } /** * Deserialize an HD Key. * @param parent The parent node in the given key's deterministic hierarchy. */ public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) { ByteBuffer buffer = ByteBuffer.wrap(serializedKey); int header = buffer.getInt(); final boolean pub = header == params.getBip32HeaderP2PKHpub() || header == params.getBip32HeaderP2WPKHpub(); final boolean priv = header == params.getBip32HeaderP2PKHpriv() || header == params.getBip32HeaderP2WPKHpriv(); if (!(pub || priv)) throw new IllegalArgumentException("Unknown header bytes: " + toBase58(serializedKey).substring(0, 4)); int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative final int parentFingerprint = buffer.getInt(); final int i = buffer.getInt(); final ChildNumber childNumber = new ChildNumber(i); HDPath path; if (parent != null) { if (parentFingerprint == 0) throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); if (parent.getFingerprint() != parentFingerprint) throw new IllegalArgumentException("Parent fingerprints don't match"); path = parent.getPath().extend(childNumber); if (path.size() != depth) throw new IllegalArgumentException("Depth does not match"); } else { if (depth >= 1) // We have been given a key that is not a root key, yet we lack the object representing the parent. // This can happen when deserializing an account key for a watching wallet. In this case, we assume that // the client wants to conceal the key's position in the hierarchy. The path is truncated at the // parent's node. path = HDPath.M(childNumber); else path = HDPath.M(); } byte[] chainCode = new byte[32]; buffer.get(chainCode); byte[] data = new byte[33]; buffer.get(data); checkArgument(!buffer.hasRemaining(), "Found unexpected data in key"); if (pub) { return new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint); } else { return new DeterministicKey(path, chainCode, new BigInteger(1, data), parent, depth, parentFingerprint); } } /** * The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree * in which case the time is stored alongside the key as per normal, see {@link ECKey#getCreationTimeSeconds()}. */ @Override public long getCreationTimeSeconds() { if (parent != null) return parent.getCreationTimeSeconds(); else return super.getCreationTimeSeconds(); } /** * The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree. * Thus, setting the creation time on a leaf is forbidden. */ @Override public void setCreationTimeSeconds(long newCreationTimeSeconds) { if (parent != null) throw new IllegalStateException("Creation time can only be set on root keys."); else super.setCreationTimeSeconds(newCreationTimeSeconds); } /** * Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate hierarchy * objects will equal each other. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DeterministicKey other = (DeterministicKey) o; return super.equals(other) && Arrays.equals(this.chainCode, other.chainCode) && Objects.equals(this.childNumberPath, other.childNumberPath); } @Override public int hashCode() { return Objects.hash(super.hashCode(), Arrays.hashCode(chainCode), childNumberPath); } @Override public String toString() { final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues(); helper.add("pub", Utils.HEX.encode(pub.getEncoded())); helper.add("chainCode", HEX.encode(chainCode)); helper.add("path", getPathAsString()); if (parent != null) helper.add("creationTimeSeconds", getCreationTimeSeconds() + " (inherited)"); else helper.add("creationTimeSeconds", getCreationTimeSeconds()); helper.add("isEncrypted", isEncrypted()); helper.add("isPubKeyOnly", isPubKeyOnly()); return helper.toString(); } @Override public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder, NetworkParameters params, Script.ScriptType outputScriptType, @Nullable String comment) { builder.append(" addr:").append(Address.fromKey(params, this, outputScriptType).toString()); builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash())); builder.append(" (").append(getPathAsString()); if (comment != null) builder.append(", ").append(comment); builder.append(")\n"); if (includePrivateKeys) { builder.append(" ").append(toStringWithPrivate(aesKey, params)).append("\n"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy