mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
add support for dumped private keys
This commit is contained in:
parent
da14a9bf34
commit
16d348a91d
3 changed files with 208 additions and 5 deletions
|
@ -1,14 +1,14 @@
|
|||
package com.sparrowwallet.drongo;
|
||||
|
||||
public enum Network {
|
||||
MAINNET("mainnet", 0, "1", 5, "3", "bc", ExtendedKey.Header.xprv, ExtendedKey.Header.xpub, 8332),
|
||||
TESTNET("testnet", 111, "mn", 196, "2", "tb", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 18332),
|
||||
REGTEST("regtest", 111, "mn", 196, "2", "bcrt", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 18443),
|
||||
SIGNET("signet", 111, "mn", 196, "2", "tb", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 38332);
|
||||
MAINNET("mainnet", 0, "1", 5, "3", "bc", ExtendedKey.Header.xprv, ExtendedKey.Header.xpub, 128, 8332),
|
||||
TESTNET("testnet", 111, "mn", 196, "2", "tb", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 239, 18332),
|
||||
REGTEST("regtest", 111, "mn", 196, "2", "bcrt", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 239, 18443),
|
||||
SIGNET("signet", 111, "mn", 196, "2", "tb", ExtendedKey.Header.tprv, ExtendedKey.Header.tpub, 239, 38332);
|
||||
|
||||
public static final String BLOCK_HEIGHT_PROPERTY = "com.sparrowwallet.blockHeight";
|
||||
|
||||
Network(String name, int p2pkhAddressHeader, String p2pkhAddressPrefix, int p2shAddressHeader, String p2shAddressPrefix, String bech32AddressHrp, ExtendedKey.Header xprvHeader, ExtendedKey.Header xpubHeader, int defaultPort) {
|
||||
Network(String name, int p2pkhAddressHeader, String p2pkhAddressPrefix, int p2shAddressHeader, String p2shAddressPrefix, String bech32AddressHrp, ExtendedKey.Header xprvHeader, ExtendedKey.Header xpubHeader, int dumpedPrivateKeyHeader, int defaultPort) {
|
||||
this.name = name;
|
||||
this.p2pkhAddressHeader = p2pkhAddressHeader;
|
||||
this.p2pkhAddressPrefix = p2pkhAddressPrefix;
|
||||
|
@ -17,6 +17,7 @@ public enum Network {
|
|||
this.bech32AddressHrp = bech32AddressHrp;
|
||||
this.xprvHeader = xprvHeader;
|
||||
this.xpubHeader = xpubHeader;
|
||||
this.dumpedPrivateKeyHeader = dumpedPrivateKeyHeader;
|
||||
this.defaultPort = defaultPort;
|
||||
}
|
||||
|
||||
|
@ -28,6 +29,7 @@ public enum Network {
|
|||
private final String bech32AddressHrp;
|
||||
private final ExtendedKey.Header xprvHeader;
|
||||
private final ExtendedKey.Header xpubHeader;
|
||||
private final int dumpedPrivateKeyHeader;
|
||||
private final int defaultPort;
|
||||
|
||||
private static Network currentNetwork;
|
||||
|
@ -56,6 +58,10 @@ public enum Network {
|
|||
return xpubHeader;
|
||||
}
|
||||
|
||||
public int getDumpedPrivateKeyHeader() {
|
||||
return dumpedPrivateKeyHeader;
|
||||
}
|
||||
|
||||
public int getDefaultPort() {
|
||||
return defaultPort;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package com.sparrowwallet.drongo.crypto;
|
||||
|
||||
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Parses and generates private keys in the form used by the Bitcoin "dumpprivkey" command. This is the private key
|
||||
* bytes with a header byte and 4 checksum bytes at the end. If there are 33 private key bytes instead of 32, then
|
||||
* the last byte is a discriminator value for the compressed pubkey.
|
||||
*/
|
||||
public class DumpedPrivateKey extends VersionedChecksummedBytes {
|
||||
private final boolean compressed;
|
||||
|
||||
/**
|
||||
* Construct a private key from its Base58 representation.
|
||||
* @param base58
|
||||
* The textual form of the private key.
|
||||
*/
|
||||
public static DumpedPrivateKey fromBase58(String base58) {
|
||||
return new DumpedPrivateKey(base58);
|
||||
}
|
||||
|
||||
// Used by ECKey.getPrivateKeyEncoded()
|
||||
DumpedPrivateKey(byte[] keyBytes, boolean compressed) {
|
||||
super(Network.get().getDumpedPrivateKeyHeader(), encode(keyBytes, compressed));
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
private static byte[] encode(byte[] keyBytes, boolean compressed) {
|
||||
if(keyBytes.length != 32) {
|
||||
throw new IllegalArgumentException("Private keys must be 32 bytes");
|
||||
}
|
||||
|
||||
if (!compressed) {
|
||||
return keyBytes;
|
||||
} else {
|
||||
// Keys that have compressed public components have an extra 1 byte on the end in dumped form.
|
||||
byte[] bytes = new byte[33];
|
||||
System.arraycopy(keyBytes, 0, bytes, 0, 32);
|
||||
bytes[32] = 1;
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
private DumpedPrivateKey(String encoded) {
|
||||
super(encoded);
|
||||
if(version != Network.get().getDumpedPrivateKeyHeader())
|
||||
throw new IllegalArgumentException("Invalid version " + version + " for network " + Network.get());
|
||||
if(bytes.length == 33 && bytes[32] == 1) {
|
||||
compressed = true;
|
||||
bytes = Arrays.copyOf(bytes, 32); // Chop off the additional marker byte.
|
||||
} else if(bytes.length == 32) {
|
||||
compressed = false;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Wrong number of bytes for a private key, not 32 or 33");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ECKey created from this encoded private key.
|
||||
*/
|
||||
public ECKey getKey() {
|
||||
return ECKey.fromPrivate(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if(!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
DumpedPrivateKey that = (DumpedPrivateKey) o;
|
||||
return compressed == that.compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), compressed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package com.sparrowwallet.drongo.crypto;
|
||||
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Base58;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>In Bitcoin the following format is often used to represent some type of key:</p>
|
||||
* <p/>
|
||||
* <pre>[one version byte] [data bytes] [4 checksum bytes]</pre>
|
||||
* <p/>
|
||||
* <p>and the result is then Base58 encoded. This format is used for addresses, and private keys exported using the
|
||||
* dumpprivkey command.</p>
|
||||
*/
|
||||
public class VersionedChecksummedBytes implements Serializable, Cloneable, Comparable<VersionedChecksummedBytes> {
|
||||
protected final int version;
|
||||
protected byte[] bytes;
|
||||
|
||||
protected VersionedChecksummedBytes(String encoded) {
|
||||
byte[] versionAndDataBytes = Base58.decodeChecked(encoded);
|
||||
byte versionByte = versionAndDataBytes[0];
|
||||
version = versionByte & 0xFF;
|
||||
bytes = new byte[versionAndDataBytes.length - 1];
|
||||
System.arraycopy(versionAndDataBytes, 1, bytes, 0, versionAndDataBytes.length - 1);
|
||||
}
|
||||
|
||||
protected VersionedChecksummedBytes(int version, byte[] bytes) {
|
||||
this.version = version;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base-58 encoded String representation of this
|
||||
* object, including version and checksum bytes.
|
||||
*/
|
||||
public final String toBase58() {
|
||||
// A stringified buffer is:
|
||||
// 1 byte version + data bytes + 4 bytes check code (a truncated hash)
|
||||
byte[] addressBytes = new byte[1 + bytes.length + 4];
|
||||
addressBytes[0] = (byte) version;
|
||||
System.arraycopy(bytes, 0, addressBytes, 1, bytes.length);
|
||||
byte[] checksum = Sha256Hash.hashTwice(addressBytes, 0, bytes.length + 1);
|
||||
System.arraycopy(checksum, 0, addressBytes, bytes.length + 1, 4);
|
||||
return Base58.encode(addressBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toBase58();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
VersionedChecksummedBytes that = (VersionedChecksummedBytes) o;
|
||||
return version == that.version && Arrays.equals(bytes, that.bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(version);
|
||||
result = 31 * result + Arrays.hashCode(bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* This implementation narrows the return type to <code>VersionedChecksummedBytes</code>
|
||||
* and allows subclasses to throw <code>CloneNotSupportedException</code> even though it
|
||||
* is never thrown by this implementation.
|
||||
*/
|
||||
@Override
|
||||
public VersionedChecksummedBytes clone() throws CloneNotSupportedException {
|
||||
return (VersionedChecksummedBytes) super.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* This implementation uses an optimized Google Guava method to compare <code>bytes</code>.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(VersionedChecksummedBytes o) {
|
||||
int result = Integer.compare(this.version, o.version);
|
||||
Utils.LexicographicByteArrayComparator lexicographicByteArrayComparator = new Utils.LexicographicByteArrayComparator();
|
||||
return result != 0 ? result : lexicographicByteArrayComparator.compare(this.bytes, o.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "version" or "header" byte: the first byte of the data. This is used to disambiguate what the
|
||||
* contents apply to, for example, which network the key or address is valid on.
|
||||
*
|
||||
* @return A positive number between 0 and 255.
|
||||
*/
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue