mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 01:56:44 +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;
|
package com.sparrowwallet.drongo;
|
||||||
|
|
||||||
public enum Network {
|
public enum Network {
|
||||||
MAINNET("mainnet", 0, "1", 5, "3", "bc", ExtendedKey.Header.xprv, ExtendedKey.Header.xpub, 8332),
|
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, 18332),
|
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, 18443),
|
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, 38332);
|
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";
|
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.name = name;
|
||||||
this.p2pkhAddressHeader = p2pkhAddressHeader;
|
this.p2pkhAddressHeader = p2pkhAddressHeader;
|
||||||
this.p2pkhAddressPrefix = p2pkhAddressPrefix;
|
this.p2pkhAddressPrefix = p2pkhAddressPrefix;
|
||||||
|
@ -17,6 +17,7 @@ public enum Network {
|
||||||
this.bech32AddressHrp = bech32AddressHrp;
|
this.bech32AddressHrp = bech32AddressHrp;
|
||||||
this.xprvHeader = xprvHeader;
|
this.xprvHeader = xprvHeader;
|
||||||
this.xpubHeader = xpubHeader;
|
this.xpubHeader = xpubHeader;
|
||||||
|
this.dumpedPrivateKeyHeader = dumpedPrivateKeyHeader;
|
||||||
this.defaultPort = defaultPort;
|
this.defaultPort = defaultPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ public enum Network {
|
||||||
private final String bech32AddressHrp;
|
private final String bech32AddressHrp;
|
||||||
private final ExtendedKey.Header xprvHeader;
|
private final ExtendedKey.Header xprvHeader;
|
||||||
private final ExtendedKey.Header xpubHeader;
|
private final ExtendedKey.Header xpubHeader;
|
||||||
|
private final int dumpedPrivateKeyHeader;
|
||||||
private final int defaultPort;
|
private final int defaultPort;
|
||||||
|
|
||||||
private static Network currentNetwork;
|
private static Network currentNetwork;
|
||||||
|
@ -56,6 +58,10 @@ public enum Network {
|
||||||
return xpubHeader;
|
return xpubHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDumpedPrivateKeyHeader() {
|
||||||
|
return dumpedPrivateKeyHeader;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDefaultPort() {
|
public int getDefaultPort() {
|
||||||
return defaultPort;
|
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