mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
add private key support to eckey
This commit is contained in:
parent
813781902b
commit
06b46e0a02
3 changed files with 440 additions and 76 deletions
|
@ -76,6 +76,43 @@ public class Utils {
|
||||||
return digit;
|
return digit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* The regular {@link BigInteger#toByteArray()} includes the sign bit of the number and
|
||||||
|
* might result in an extra byte addition. This method removes this extra byte.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Assuming only positive numbers, it's possible to discriminate if an extra byte
|
||||||
|
* is added by checking if the first element of the array is 0 (0000_0000).
|
||||||
|
* Due to the minimal representation provided by BigInteger, it means that the bit sign
|
||||||
|
* is the least significant bit 0000_000<b>0</b> .
|
||||||
|
* Otherwise the representation is not minimal.
|
||||||
|
* For example, if the sign bit is 0000_00<b>0</b>0, then the representation is not minimal due to the rightmost zero.
|
||||||
|
* </p>
|
||||||
|
* @param b the integer to format into a byte array
|
||||||
|
* @param numBytes the desired size of the resulting byte array
|
||||||
|
* @return numBytes byte long array.
|
||||||
|
*/
|
||||||
|
public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) {
|
||||||
|
if(b.signum() < 0) {
|
||||||
|
throw new IllegalArgumentException("b must be positive or zero");
|
||||||
|
}
|
||||||
|
if(numBytes <= 0) {
|
||||||
|
throw new IllegalArgumentException("numBytes must be positive");
|
||||||
|
}
|
||||||
|
byte[] src = b.toByteArray();
|
||||||
|
byte[] dest = new byte[numBytes];
|
||||||
|
boolean isFirstByteOnlyForSign = src[0] == 0;
|
||||||
|
int length = isFirstByteOnlyForSign ? src.length - 1 : src.length;
|
||||||
|
if(length > numBytes) {
|
||||||
|
throw new IllegalArgumentException("The given number does not fit in " + numBytes);
|
||||||
|
}
|
||||||
|
int srcPos = isFirstByteOnlyForSign ? 1 : 0;
|
||||||
|
int destPos = numBytes - length;
|
||||||
|
System.arraycopy(src, srcPos, dest, destPos, length);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
/** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. */
|
/** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. */
|
||||||
public static long readUint32(byte[] bytes, int offset) {
|
public static long readUint32(byte[] bytes, int offset) {
|
||||||
return (bytes[offset] & 0xffl) |
|
return (bytes[offset] & 0xffl) |
|
||||||
|
|
|
@ -5,10 +5,13 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
||||||
import org.bouncycastle.asn1.*;
|
import org.bouncycastle.asn1.*;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
import org.bouncycastle.crypto.params.*;
|
||||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||||
|
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
|
||||||
import org.bouncycastle.math.ec.ECPoint;
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||||
import org.bouncycastle.math.ec.FixedPointUtil;
|
import org.bouncycastle.math.ec.FixedPointUtil;
|
||||||
|
@ -21,8 +24,37 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Represents an elliptic curve public and (optionally) private key, usable for digital signatures but not encryption.
|
||||||
|
* Creating a new ECKey with the empty constructor will generate a new random keypair. Other static methods can be used
|
||||||
|
* when you already have the public or private parts. If you create a key with only the public part, you can check
|
||||||
|
* signatures but not create them.</p>
|
||||||
|
*
|
||||||
|
* <p>ECKey also provides access to Bitcoin Core compatible text message signing, as accessible via the UI or JSON-RPC.
|
||||||
|
* This is slightly different to signing raw bytes - if you want to sign your own data and it won't be exposed as
|
||||||
|
* text to people, you don't want to use this. If in doubt, ask on the mailing list.</p>
|
||||||
|
*
|
||||||
|
* <p>The ECDSA algorithm supports <i>key recovery</i> in which a signature plus a couple of discriminator bits can
|
||||||
|
* be reversed to find the public key used to calculate it. This can be convenient when you have a message and a
|
||||||
|
* signature and want to find out who signed it, rather than requiring the user to provide the expected identity.</p>
|
||||||
|
*
|
||||||
|
* <p>This class supports a variety of serialization forms. The methods that accept/return byte arrays serialize
|
||||||
|
* private keys as raw byte arrays and public keys using the SEC standard byte encoding for public keys. Signatures
|
||||||
|
* are encoded using ASN.1/DER inside the Bitcoin protocol.</p>
|
||||||
|
*
|
||||||
|
* <p>A key can be <i>compressed</i> or <i>uncompressed</i>. This refers to whether the public key is represented
|
||||||
|
* when encoded into bytes as an (x, y) coordinate on the elliptic curve, or whether it's represented as just an X
|
||||||
|
* co-ordinate and an extra byte that carries a sign bit. With the latter form the Y coordinate can be calculated
|
||||||
|
* dynamically, however, <b>because the binary serialization is different the address of a key changes if its
|
||||||
|
* compression status is changed</b>. If you deviate from the defaults it's important to understand this: money sent
|
||||||
|
* to a compressed version of the key will have a different address to the same key in uncompressed form. Whether
|
||||||
|
* a public key is compressed or not is recorded in the SEC binary serialisation format, and preserved in a flag in
|
||||||
|
* this class so round-tripping preserves state. Unless you're working with old software or doing unusual things, you
|
||||||
|
* can usually ignore the compressed/uncompressed distinction.</p>
|
||||||
|
*/
|
||||||
public class ECKey {
|
public class ECKey {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
|
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
|
||||||
|
|
||||||
|
@ -49,11 +81,58 @@ public class ECKey {
|
||||||
secureRandom = new SecureRandom();
|
secureRandom = new SecureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The two parts of the key. If "pub" is set but not "priv", we can only verify signatures, not make them.
|
||||||
|
protected final BigInteger priv;
|
||||||
protected final LazyECPoint pub;
|
protected final LazyECPoint pub;
|
||||||
|
|
||||||
|
// Creation time of the key in seconds since the epoch, or zero if the key was deserialized from a version that did
|
||||||
|
// not have this field.
|
||||||
|
protected long creationTimeSeconds;
|
||||||
|
|
||||||
private byte[] pubKeyHash;
|
private byte[] pubKeyHash;
|
||||||
|
|
||||||
protected ECKey(LazyECPoint pub) {
|
/**
|
||||||
|
* Generates an entirely new keypair. Point compression is used so the resulting public key will be 33 bytes
|
||||||
|
* (32 for the co-ordinate and 1 byte to represent the y bit).
|
||||||
|
*/
|
||||||
|
public ECKey() {
|
||||||
|
this(secureRandom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an entirely new keypair with the given {@link SecureRandom} object. Point compression is used so the
|
||||||
|
* resulting public key will be 33 bytes (32 for the co-ordinate and 1 byte to represent the y bit).
|
||||||
|
*/
|
||||||
|
public ECKey(SecureRandom secureRandom) {
|
||||||
|
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||||
|
ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(CURVE, secureRandom);
|
||||||
|
generator.init(keygenParams);
|
||||||
|
AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
|
||||||
|
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
|
||||||
|
ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
|
||||||
|
priv = privParams.getD();
|
||||||
|
pub = getPointWithCompression(pubParams.getQ(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ECKey(BigInteger priv, ECPoint pub, boolean compressed) {
|
||||||
|
this(priv, getPointWithCompression(pub, compressed));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ECKey(BigInteger priv, LazyECPoint pub) {
|
||||||
|
if(priv != null) {
|
||||||
|
if(priv.bitLength() > 32 * 8) {
|
||||||
|
throw new IllegalArgumentException("Private key exceeds 32 bytes: " + priv.bitLength() + " bits");
|
||||||
|
}
|
||||||
|
if(priv.equals(BigInteger.ZERO) || priv.equals(BigInteger.ONE)) {
|
||||||
|
throw new IllegalArgumentException("Private key is illegal: " + priv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pub == null) {
|
||||||
|
throw new IllegalArgumentException("Public key cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.priv = priv;
|
||||||
this.pub = pub;
|
this.pub = pub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,31 +141,125 @@ public class ECKey {
|
||||||
* See the ECKey class docs for a discussion of point compression.
|
* See the ECKey class docs for a discussion of point compression.
|
||||||
*/
|
*/
|
||||||
public static LazyECPoint compressPoint(LazyECPoint point) {
|
public static LazyECPoint compressPoint(LazyECPoint point) {
|
||||||
return point.isCompressed() ? point : new LazyECPoint(point.get(), true);
|
return point.isCompressed() ? point : getPointWithCompression(point.get(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the raw public key value. This appears in transaction scriptSigs. Note that this is <b>not</b> the same
|
* Utility for decompressing an elliptic curve point. Returns the same point if it's already uncompressed.
|
||||||
* as the pubKeyHash/address.
|
* See the ECKey class docs for a discussion of point compression.
|
||||||
*/
|
*/
|
||||||
public byte[] getPubKey() {
|
public static LazyECPoint decompressPoint(LazyECPoint point) {
|
||||||
return pub.getEncoded();
|
return !point.isCompressed() ? point : getPointWithCompression(point.get(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the public key in the form of an elliptic curve point object from Bouncy Castle. */
|
private static LazyECPoint getPointWithCompression(ECPoint point, boolean compressed) {
|
||||||
public ECPoint getPubKeyPoint() {
|
return new LazyECPoint(point, compressed);
|
||||||
return pub.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the hash160 form of the public key (as seen in addresses). */
|
/**
|
||||||
public byte[] getPubKeyHash() {
|
* Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by Bitcoin
|
||||||
if (pubKeyHash == null)
|
* Core in its wallet. Note that this is slow because it requires an EC point multiply.
|
||||||
pubKeyHash = Utils.sha256hash160(this.pub.getEncoded());
|
*/
|
||||||
return pubKeyHash;
|
public static ECKey fromASN1(byte[] asn1privkey) {
|
||||||
|
return extractKeyFromASN1(asn1privkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCompressed() {
|
/**
|
||||||
return pub.isCompressed();
|
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow). The resulting
|
||||||
|
* public key is compressed.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPrivate(BigInteger privKey) {
|
||||||
|
return fromPrivate(privKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
|
||||||
|
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPrivate(BigInteger privKey, boolean compressed) {
|
||||||
|
ECPoint point = publicPointFromPrivate(privKey);
|
||||||
|
return new ECKey(privKey, getPointWithCompression(point, compressed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow). The resulting
|
||||||
|
* public key is compressed.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPrivate(byte[] privKeyBytes) {
|
||||||
|
return fromPrivate(new BigInteger(1, privKeyBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
|
||||||
|
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPrivate(byte[] privKeyBytes, boolean compressed) {
|
||||||
|
return fromPrivate(new BigInteger(1, privKeyBytes), compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given point.
|
||||||
|
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPublicOnly(ECPoint pub, boolean compressed) {
|
||||||
|
return new ECKey(null, pub, compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given encoded point.
|
||||||
|
* The compression state of pub will be preserved.
|
||||||
|
*/
|
||||||
|
public static ECKey fromPublicOnly(byte[] pub) {
|
||||||
|
return new ECKey(null, new LazyECPoint(CURVE.getCurve(), pub));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECKey fromPublicOnly(ECKey key) {
|
||||||
|
return fromPublicOnly(key.getPubKeyPoint(), key.isCompressed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this key doesn't have unencrypted access to private key bytes. This may be because it was never
|
||||||
|
* given any private key bytes to begin with (a watching key), or because the key is encrypted.
|
||||||
|
*/
|
||||||
|
public boolean isPubKeyOnly() {
|
||||||
|
return priv == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by Bitcoin Core
|
||||||
|
* in its wallet storage format.
|
||||||
|
* @throws MissingPrivateKeyException if the private key is missing or encrypted.
|
||||||
|
*/
|
||||||
|
public byte[] toASN1() {
|
||||||
|
try {
|
||||||
|
byte[] privKeyBytes = getPrivKeyBytes();
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(400);
|
||||||
|
|
||||||
|
// ASN1_SEQUENCE(EC_PRIVATEKEY) = {
|
||||||
|
// ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
|
||||||
|
// ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
|
||||||
|
// ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
|
||||||
|
// ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
|
||||||
|
// } ASN1_SEQUENCE_END(EC_PRIVATEKEY)
|
||||||
|
DERSequenceGenerator seq = new DERSequenceGenerator(baos);
|
||||||
|
seq.addObject(new ASN1Integer(1)); // version
|
||||||
|
seq.addObject(new DEROctetString(privKeyBytes));
|
||||||
|
seq.addObject(new DERTaggedObject(0, CURVE_PARAMS.toASN1Primitive()));
|
||||||
|
seq.addObject(new DERTaggedObject(1, new DERBitString(getPubKey())));
|
||||||
|
seq.close();
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e); // Cannot happen, writing to memory stream.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns public key bytes from the given private key. To convert a byte array into a BigInteger,
|
||||||
|
* use {@code new BigInteger(1, bytes);}
|
||||||
|
*/
|
||||||
|
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) {
|
||||||
|
ECPoint point = publicPointFromPrivate(privKey);
|
||||||
|
return point.getEncoded(compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,67 +277,43 @@ public class ECKey {
|
||||||
return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
|
return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Gets the hash160 form of the public key (as seen in addresses). */
|
||||||
* Returns true if the given pubkey is in its compressed form.
|
public byte[] getPubKeyHash() {
|
||||||
*/
|
if (pubKeyHash == null)
|
||||||
public static boolean isPubKeyCompressed(byte[] encoded) {
|
pubKeyHash = Utils.sha256hash160(this.pub.getEncoded());
|
||||||
if (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03))
|
return pubKeyHash;
|
||||||
return true;
|
|
||||||
else if (encoded.length == 65 && encoded[0] == 0x04)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
throw new IllegalArgumentException(Hex.toHexString(encoded));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given bytes represent a public key.
|
* Gets the raw public key value. This appears in transaction scriptSigs. Note that this is <b>not</b> the same
|
||||||
|
* as the pubKeyHash/address.
|
||||||
*/
|
*/
|
||||||
public static boolean isPubKey(byte[] encoded) {
|
public byte[] getPubKey() {
|
||||||
if (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03))
|
return pub.getEncoded();
|
||||||
return true;
|
}
|
||||||
else if (encoded.length == 65 && encoded[0] == 0x04)
|
|
||||||
return true;
|
/** Gets the public key in the form of an elliptic curve point object from Bouncy Castle. */
|
||||||
else
|
public ECPoint getPubKeyPoint() {
|
||||||
return false;
|
return pub.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given encoded point.
|
* Gets the private key in the form of an integer field element. The public key is derived by performing EC
|
||||||
* The compression state of pub will be preserved.
|
* point addition this number of times (i.e. point multiplying).
|
||||||
*/
|
|
||||||
public static ECKey fromPublicOnly(byte[] pub) {
|
|
||||||
return new ECKey(new LazyECPoint(CURVE.getCurve(), pub));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the given R/S pair (signature) against a hash using the public key.
|
|
||||||
*/
|
|
||||||
public boolean verify(Sha256Hash sigHash, ECDSASignature signature) {
|
|
||||||
return ECKey.verify(sigHash.getBytes(), signature, getPubKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Verifies the given ECDSA signature against the message bytes using the public key bytes.</p>
|
|
||||||
*
|
*
|
||||||
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be
|
* @throws java.lang.IllegalStateException if the private key bytes are not available.
|
||||||
* larger than 520 bytes.</p>
|
|
||||||
*
|
|
||||||
* @param data Hash of the data to verify.
|
|
||||||
* @param signature ASN.1 encoded signature.
|
|
||||||
* @param pub The public key bytes to use.
|
|
||||||
*/
|
*/
|
||||||
public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
|
public BigInteger getPrivKey() {
|
||||||
ECDSASigner signer = new ECDSASigner();
|
if (priv == null)
|
||||||
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
|
throw new MissingPrivateKeyException();
|
||||||
signer.init(false, params);
|
return priv;
|
||||||
try {
|
}
|
||||||
return signer.verifySignature(data, signature.r, signature.s);
|
|
||||||
} catch (NullPointerException e) {
|
/**
|
||||||
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
|
* Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 bytes, not 64.
|
||||||
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
|
*/
|
||||||
log.error("Caught NPE inside bouncy castle", e);
|
public boolean isCompressed() {
|
||||||
return false;
|
return pub.isCompressed();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -284,21 +433,199 @@ public class ECKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String toString() {
|
* Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are
|
||||||
return pub.toString();
|
* usually encoded using ASN.1 format, so you want {@link ECKey.ECDSASignature#toASN1()}
|
||||||
|
* instead. However sometimes the independent components can be useful, for instance, if you're going to do
|
||||||
|
* further EC maths on them.
|
||||||
|
*/
|
||||||
|
public ECDSASignature sign(Sha256Hash input) {
|
||||||
|
return doSign(input, priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) {
|
||||||
|
if(privateKeyForSigning == null) {
|
||||||
|
throw new IllegalArgumentException("Private key cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
|
||||||
|
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
|
||||||
|
signer.init(true, privKey);
|
||||||
|
BigInteger[] components = signer.generateSignature(input.getBytes());
|
||||||
|
return new ECDSASignature(components[0], components[1]).toCanonicalised();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Verifies the given ECDSA signature against the message bytes using the public key bytes.</p>
|
||||||
|
*
|
||||||
|
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be
|
||||||
|
* larger than 520 bytes.</p>
|
||||||
|
*
|
||||||
|
* @param data Hash of the data to verify.
|
||||||
|
* @param signature ASN.1 encoded signature.
|
||||||
|
* @param pub The public key bytes to use.
|
||||||
|
*/
|
||||||
|
public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
|
||||||
|
ECDSASigner signer = new ECDSASigner();
|
||||||
|
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
|
||||||
|
signer.init(false, params);
|
||||||
|
try {
|
||||||
|
return signer.verifySignature(data, signature.r, signature.s);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
|
||||||
|
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
|
||||||
|
log.error("Caught NPE inside bouncy castle", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
|
||||||
|
*
|
||||||
|
* @param data Hash of the data to verify.
|
||||||
|
* @param signature ASN.1 encoded signature.
|
||||||
|
* @param pub The public key bytes to use.
|
||||||
|
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||||
|
*/
|
||||||
|
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws SignatureDecodeException {
|
||||||
|
return verify(data, ECDSASignature.decodeFromDER(signature), pub);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
|
||||||
|
*
|
||||||
|
* @param hash Hash of the data to verify.
|
||||||
|
* @param signature ASN.1 encoded signature.
|
||||||
|
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||||
|
*/
|
||||||
|
public boolean verify(byte[] hash, byte[] signature) throws SignatureDecodeException {
|
||||||
|
return ECKey.verify(hash, signature, getPubKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the given R/S pair (signature) against a hash using the public key.
|
||||||
|
*/
|
||||||
|
public boolean verify(Sha256Hash sigHash, ECDSASignature signature) {
|
||||||
|
return ECKey.verify(sigHash.getBytes(), signature, getPubKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given pubkey is canonical, i.e. the correct length taking into account compression.
|
||||||
|
*/
|
||||||
|
public static boolean isPubKeyCanonical(byte[] pubkey) {
|
||||||
|
if (pubkey.length < 33)
|
||||||
|
return false;
|
||||||
|
if (pubkey[0] == 0x04) {
|
||||||
|
// Uncompressed pubkey
|
||||||
|
if (pubkey.length != 65)
|
||||||
|
return false;
|
||||||
|
} else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) {
|
||||||
|
// Compressed pubkey
|
||||||
|
if (pubkey.length != 33)
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given pubkey is in its compressed form.
|
||||||
|
*/
|
||||||
|
public static boolean isPubKeyCompressed(byte[] encoded) {
|
||||||
|
if (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03))
|
||||||
|
return true;
|
||||||
|
else if (encoded.length == 65 && encoded[0] == 0x04)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException(Hex.toHexString(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ECKey extractKeyFromASN1(byte[] asn1privkey) {
|
||||||
|
// To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source
|
||||||
|
// code in ec_asn1.c:
|
||||||
|
//
|
||||||
|
// ASN1_SEQUENCE(EC_PRIVATEKEY) = {
|
||||||
|
// ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
|
||||||
|
// ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
|
||||||
|
// ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
|
||||||
|
// ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
|
||||||
|
// } ASN1_SEQUENCE_END(EC_PRIVATEKEY)
|
||||||
|
//
|
||||||
|
try {
|
||||||
|
ASN1InputStream decoder = new ASN1InputStream(asn1privkey);
|
||||||
|
DLSequence seq = (DLSequence)decoder.readObject();
|
||||||
|
if(decoder.readObject() != null) {
|
||||||
|
throw new IllegalArgumentException("Input contains extra bytes");
|
||||||
|
}
|
||||||
|
decoder.close();
|
||||||
|
|
||||||
|
if(seq.size() != 4) {
|
||||||
|
throw new IllegalArgumentException("Input does not appear to be an ASN.1 OpenSSL EC private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!((ASN1Integer) seq.getObjectAt(0)).getValue().equals(BigInteger.ONE)) {
|
||||||
|
throw new IllegalArgumentException("Input is of wrong version");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] privbits = ((ASN1OctetString) seq.getObjectAt(1)).getOctets();
|
||||||
|
BigInteger privkey = new BigInteger(1, privbits);
|
||||||
|
|
||||||
|
ASN1TaggedObject pubkey = (ASN1TaggedObject) seq.getObjectAt(3);
|
||||||
|
if(pubkey.getTagNo() != 1) {
|
||||||
|
throw new IllegalArgumentException("Input has 'publicKey' with bad tag number");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pubbits = ((DERBitString)pubkey.getObject()).getBytes();
|
||||||
|
if(pubbits.length != 33 && pubbits.length != 65) {
|
||||||
|
throw new IllegalArgumentException("Input has 'publicKey' with invalid length");
|
||||||
|
};
|
||||||
|
|
||||||
|
int encoding = pubbits[0] & 0xFF;
|
||||||
|
// Only allow compressed(2,3) and uncompressed(4), not infinity(0) or hybrid(6,7)
|
||||||
|
if(encoding < 2 || encoding > 4) {
|
||||||
|
throw new IllegalArgumentException("Input has 'publicKey' with invalid encoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now sanity check to ensure the pubkey bytes match the privkey.
|
||||||
|
boolean compressed = isPubKeyCompressed(pubbits);
|
||||||
|
ECKey key = new ECKey(privkey, null, compressed);
|
||||||
|
if(!Arrays.equals(key.getPubKey(), pubbits)) {
|
||||||
|
throw new IllegalArgumentException("Public key in ASN.1 structure does not match private key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e); // Cannot happen, reading from memory stream.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a 32 byte array containing the private key.
|
||||||
|
* @throws MissingPrivateKeyException if the private key bytes are missing/encrypted.
|
||||||
|
*/
|
||||||
|
public byte[] getPrivKeyBytes() {
|
||||||
|
return Utils.bigIntegerToBytes(getPrivKey(), 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (!(o instanceof ECKey)) return false;
|
if (o == null || !(o instanceof ECKey)) return false;
|
||||||
ECKey other = (ECKey) o;
|
ECKey other = (ECKey) o;
|
||||||
return Objects.equals(this.pub, other.pub);
|
return Objects.equals(this.priv, other.priv)
|
||||||
|
&& Objects.equals(this.pub, other.pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return pub.hashCode();
|
return pub.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return pub.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MissingPrivateKeyException extends RuntimeException {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ public class ScriptChunk {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ECKey.isPubKey(data);
|
return ECKey.isPubKeyCanonical(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECKey getPubKey() {
|
public ECKey getPubKey() {
|
||||||
|
|
Loading…
Reference in a new issue