mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +00:00
Support compressed pubkeys in the dumpprivkey format encoder/decoder.
Resolves issue 308.
This commit is contained in:
parent
1c8ddaad36
commit
90662e9238
@ -16,19 +16,36 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.spongycastle.util.Arrays;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and generates private keys in the form used by the Bitcoin "dumpprivkey" command. This is the private key
|
* 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.
|
* 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 {
|
public class DumpedPrivateKey extends VersionedChecksummedBytes {
|
||||||
|
private boolean compressed;
|
||||||
|
|
||||||
// Used by ECKey.getPrivateKeyEncoded()
|
// Used by ECKey.getPrivateKeyEncoded()
|
||||||
DumpedPrivateKey(NetworkParameters params, byte[] keyBytes) {
|
DumpedPrivateKey(NetworkParameters params, byte[] keyBytes, boolean compressed) {
|
||||||
super(params.dumpedPrivateKeyHeader, keyBytes);
|
super(params.dumpedPrivateKeyHeader, encode(keyBytes, compressed));
|
||||||
if (keyBytes.length != 32) // 256 bit keys
|
this.compressed = compressed;
|
||||||
throw new RuntimeException("Keys are 256 bits, so you must provide 32 bytes, got " +
|
}
|
||||||
keyBytes.length + " bytes");
|
|
||||||
|
private static byte[] encode(byte[] keyBytes, boolean compressed) {
|
||||||
|
Preconditions.checkArgument(keyBytes.length == 32, "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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,12 +60,20 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes {
|
|||||||
if (params != null && version != params.dumpedPrivateKeyHeader)
|
if (params != null && version != params.dumpedPrivateKeyHeader)
|
||||||
throw new AddressFormatException("Mismatched version number, trying to cross networks? " + version +
|
throw new AddressFormatException("Mismatched version number, trying to cross networks? " + version +
|
||||||
" vs " + params.dumpedPrivateKeyHeader);
|
" vs " + params.dumpedPrivateKeyHeader);
|
||||||
|
if (bytes.length == 33) {
|
||||||
|
compressed = true;
|
||||||
|
bytes = Arrays.copyOf(bytes, 32); // Chop off the additional marker byte.
|
||||||
|
} else if (bytes.length == 32) {
|
||||||
|
compressed = false;
|
||||||
|
} else {
|
||||||
|
throw new AddressFormatException("Wrong number of bytes for a private key, not 32 or 33");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an ECKey created from this encoded private key.
|
* Returns an ECKey created from this encoded private key.
|
||||||
*/
|
*/
|
||||||
public ECKey getKey() {
|
public ECKey getKey() {
|
||||||
return new ECKey(new BigInteger(1, bytes));
|
return new ECKey(new BigInteger(1, bytes), null, compressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,12 +100,16 @@ public class ECKey implements Serializable {
|
|||||||
// Unfortunately Bouncy Castle does not let us explicitly change a point to be compressed, even though it
|
// Unfortunately Bouncy Castle does not let us explicitly change a point to be compressed, even though it
|
||||||
// could easily do so. We must re-build it here so the ECPoints withCompression flag can be set to true.
|
// could easily do so. We must re-build it here so the ECPoints withCompression flag can be set to true.
|
||||||
ECPoint uncompressed = pubParams.getQ();
|
ECPoint uncompressed = pubParams.getQ();
|
||||||
ECPoint compressed = new ECPoint.Fp(ecParams.getCurve(), uncompressed.getX(), uncompressed.getY(), true);
|
ECPoint compressed = compressPoint(uncompressed);
|
||||||
pub = compressed.getEncoded();
|
pub = compressed.getEncoded();
|
||||||
|
|
||||||
creationTimeSeconds = Utils.now().getTime() / 1000;
|
creationTimeSeconds = Utils.now().getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ECPoint compressPoint(ECPoint uncompressed) {
|
||||||
|
return new ECPoint.Fp(ecParams.getCurve(), uncompressed.getX(), uncompressed.getY(), true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the BitCoin
|
* Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the BitCoin
|
||||||
* reference implementation in its wallet. Note that this is slow because it requires an EC point multiply.
|
* reference implementation in its wallet. Note that this is slow because it requires an EC point multiply.
|
||||||
@ -145,13 +149,14 @@ public class ECKey implements Serializable {
|
|||||||
* is supplied, the public key will be calculated from it (this is slow). If both are supplied, it's assumed
|
* is supplied, the public key will be calculated from it (this is slow). If both are supplied, it's assumed
|
||||||
* the public key already correctly matches the public key. If only the public key is supplied, this ECKey cannot
|
* the public key already correctly matches the public key. If only the public key is supplied, this ECKey cannot
|
||||||
* be used for signing.
|
* be used for signing.
|
||||||
|
* @param compressed If set to true and pubKey is null, the derived public key will be in compressed form.
|
||||||
*/
|
*/
|
||||||
private ECKey(BigInteger privKey, byte[] pubKey) {
|
public ECKey(BigInteger privKey, byte[] pubKey, boolean compressed) {
|
||||||
this.priv = privKey;
|
this.priv = privKey;
|
||||||
this.pub = null;
|
this.pub = null;
|
||||||
if (pubKey == null && privKey != null) {
|
if (pubKey == null && privKey != null) {
|
||||||
// Derive public from private.
|
// Derive public from private.
|
||||||
this.pub = publicKeyFromPrivate(privKey);
|
this.pub = publicKeyFromPrivate(privKey, compressed);
|
||||||
} else if (pubKey != null) {
|
} else if (pubKey != null) {
|
||||||
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
|
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
|
||||||
// a special marker byte.
|
// a special marker byte.
|
||||||
@ -159,6 +164,16 @@ public class ECKey implements Serializable {
|
|||||||
this.pub = pubKey;
|
this.pub = pubKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey given either the private key only, the public key only, or both. If only the private key
|
||||||
|
* is supplied, the public key will be calculated from it (this is slow). If both are supplied, it's assumed
|
||||||
|
* the public key already correctly matches the public key. If only the public key is supplied, this ECKey cannot
|
||||||
|
* be used for signing.
|
||||||
|
*/
|
||||||
|
private ECKey(BigInteger privKey, byte[] pubKey) {
|
||||||
|
this(privKey, pubKey, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates an ECKey given the private key only. The public key is calculated from it (this is slow) */
|
/** Creates an ECKey given the private key only. The public key is calculated from it (this is slow) */
|
||||||
public ECKey(BigInteger privKey) {
|
public ECKey(BigInteger privKey) {
|
||||||
@ -183,8 +198,11 @@ public class ECKey implements Serializable {
|
|||||||
* Returns public key bytes from the given private key. To convert a byte array into a BigInteger, use <tt>
|
* Returns public key bytes from the given private key. To convert a byte array into a BigInteger, use <tt>
|
||||||
* new BigInteger(1, bytes);</tt>
|
* new BigInteger(1, bytes);</tt>
|
||||||
*/
|
*/
|
||||||
public static byte[] publicKeyFromPrivate(BigInteger privKey) {
|
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) {
|
||||||
return ecParams.getG().multiply(privKey).getEncoded();
|
ECPoint point = ecParams.getG().multiply(privKey);
|
||||||
|
if (compressed)
|
||||||
|
point = compressPoint(point);
|
||||||
|
return point.getEncoded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the hash160 form of the public key (as seen in addresses). */
|
/** Gets the hash160 form of the public key (as seen in addresses). */
|
||||||
@ -545,7 +563,7 @@ public class ECKey implements Serializable {
|
|||||||
* @return Private key bytes as a {@link DumpedPrivateKey}.
|
* @return Private key bytes as a {@link DumpedPrivateKey}.
|
||||||
*/
|
*/
|
||||||
public DumpedPrivateKey getPrivateKeyEncoded(NetworkParameters params) {
|
public DumpedPrivateKey getPrivateKeyEncoded(NetworkParameters params) {
|
||||||
return new DumpedPrivateKey(params, getPrivKeyBytes());
|
return new DumpedPrivateKey(params, getPrivKeyBytes(), isCompressed());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,7 @@ import org.spongycastle.util.encoders.Hex;
|
|||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static com.google.bitcoin.core.Utils.reverseBytes;
|
import static com.google.bitcoin.core.Utils.reverseBytes;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
@ -173,4 +174,16 @@ public class ECKeyTest {
|
|||||||
}
|
}
|
||||||
assertTrue(found);
|
assertTrue(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripDumpedPrivKey() throws Exception {
|
||||||
|
ECKey key = new ECKey();
|
||||||
|
assertTrue(key.isCompressed());
|
||||||
|
NetworkParameters params = NetworkParameters.unitTests();
|
||||||
|
String base58 = key.getPrivateKeyEncoded(params).toString();
|
||||||
|
ECKey key2 = new DumpedPrivateKey(params, base58).getKey();
|
||||||
|
assertTrue(key2.isCompressed());
|
||||||
|
assertTrue(Arrays.equals(key.getPrivKeyBytes(), key2.getPrivKeyBytes()));
|
||||||
|
assertTrue(Arrays.equals(key.getPubKey(), key2.getPubKey()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user