3
0
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:
Mike Hearn 2013-02-11 12:18:16 +01:00
parent 1c8ddaad36
commit 90662e9238
3 changed files with 69 additions and 13 deletions

View File

@ -16,19 +16,36 @@
package com.google.bitcoin.core;
import com.google.common.base.Preconditions;
import org.spongycastle.util.Arrays;
import java.math.BigInteger;
/**
* 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 {
private boolean compressed;
// Used by ECKey.getPrivateKeyEncoded()
DumpedPrivateKey(NetworkParameters params, byte[] keyBytes) {
super(params.dumpedPrivateKeyHeader, keyBytes);
if (keyBytes.length != 32) // 256 bit keys
throw new RuntimeException("Keys are 256 bits, so you must provide 32 bytes, got " +
keyBytes.length + " bytes");
DumpedPrivateKey(NetworkParameters params, byte[] keyBytes, boolean compressed) {
super(params.dumpedPrivateKeyHeader, encode(keyBytes, compressed));
this.compressed = compressed;
}
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)
throw new AddressFormatException("Mismatched version number, trying to cross networks? " + version +
" 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.
*/
public ECKey getKey() {
return new ECKey(new BigInteger(1, bytes));
return new ECKey(new BigInteger(1, bytes), null, compressed);
}
}

View File

@ -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
// 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 compressed = new ECPoint.Fp(ecParams.getCurve(), uncompressed.getX(), uncompressed.getY(), true);
ECPoint compressed = compressPoint(uncompressed);
pub = compressed.getEncoded();
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
* 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
* the public key already correctly matches the public key. If only the public key is supplied, this ECKey cannot
* 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.pub = null;
if (pubKey == null && privKey != null) {
// Derive public from private.
this.pub = publicKeyFromPrivate(privKey);
this.pub = publicKeyFromPrivate(privKey, compressed);
} else if (pubKey != null) {
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
// a special marker byte.
@ -160,6 +165,16 @@ public class ECKey implements Serializable {
}
}
/**
* 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) */
public ECKey(BigInteger privKey) {
this(privKey, (byte[])null);
@ -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>
* new BigInteger(1, bytes);</tt>
*/
public static byte[] publicKeyFromPrivate(BigInteger privKey) {
return ecParams.getG().multiply(privKey).getEncoded();
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) {
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). */
@ -545,7 +563,7 @@ public class ECKey implements Serializable {
* @return Private key bytes as a {@link DumpedPrivateKey}.
*/
public DumpedPrivateKey getPrivateKeyEncoded(NetworkParameters params) {
return new DumpedPrivateKey(params, getPrivKeyBytes());
return new DumpedPrivateKey(params, getPrivKeyBytes(), isCompressed());
}
/**

View File

@ -21,6 +21,7 @@ import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.Arrays;
import static com.google.bitcoin.core.Utils.reverseBytes;
import static org.junit.Assert.*;
@ -173,4 +174,16 @@ public class ECKeyTest {
}
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()));
}
}