/**
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.redPandaLib.crypt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.Arrays;
import org.redPandaLib.core.Test;
import org.spongycastle.asn1.*;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.ec.CustomNamedCurves;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.FixedPointUtil;
import org.spongycastle.util.encoders.Base64;
// TODO: This class is quite a mess by now. Once users are migrated away from Java serialization for the wallets,
// refactor this to have better internal layout and a more consistent API.
/**
* <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 constructors 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-Qt 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>
*/
public class ECKey implements Serializable {
private static final ECDomainParameters ecParams;
private static final SecureRandom secureRandom;
private static final long serialVersionUID = -728224901792295832L;
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
public static final ECDomainParameters CURVE;
public static final BigInteger HALF_CURVE_ORDER;
static {
// All clients must agree on the curve to use by agreement. Bitcoin uses secp256k1.
X9ECParameters params = SECNamedCurves.getByName("secp256k1");
ecParams = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
secureRandom = new SecureRandom();
FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12);
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(),
CURVE_PARAMS.getH());
HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
}
// The two parts of the key. If "priv" is set, "pub" can always be calculated. If "pub" is set but not "priv", we
// can only verify signatures not make them.
// TODO: Redesign this class to use consistent internals and more efficient serialization.
private BigInteger priv;
private byte[] 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.
private long creationTimeSeconds;
// Transient because it's calculated on demand.
transient private byte[] pubKeyHash;
public int database_id = -1;
/**
* 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() {
ECKeyPairGenerator generator = new ECKeyPairGenerator();
ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(ecParams, secureRandom);
generator.init(keygenParams);
AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
priv = privParams.getD();
// 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 = 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.
*/
public static ECKey fromASN1(byte[] asn1privkey) {
return new ECKey(extractPrivateKeyFromASN1(asn1privkey));
}
/**
* Output this ECKey as an ASN.1 encoded private key, as understood by
* OpenSSL or used by the Bitcoin reference implementation in its wallet
* storage format.
*/
public byte[] toASN1() {
try {
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(priv.toByteArray()));
seq.addObject(new DERTaggedObject(0, SECNamedCurves.getByName("secp256k1").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.
}
}
/**
* 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.
*
* @param compressed If set to true and pubKey is null, the derived public
* key will be in compressed form.
*/
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, 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.
// TODO: This is probably not a useful API and may be confusing.
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)
*/
public ECKey(BigInteger privKey) {
this(privKey, (byte[]) null);
}
/**
* A constructor variant with BigInteger pubkey. See
* {@link ECKey#ECKey(BigInteger, byte[])}.
*/
public ECKey(BigInteger privKey, BigInteger pubKey) {
this(privKey, Utils.bigIntegerToBytes(pubKey, 65));
}
/**
* Creates an ECKey given only the private key bytes. This is the same as
* using the BigInteger constructor, but is more convenient if you are
* importing a key from elsewhere. If not provided the public key will be
* automatically derived from the private key.
*/
public ECKey(byte[] privKeyBytes, byte[] pubKey) {
this(privKeyBytes == null ? null : new BigInteger(1, privKeyBytes), pubKey);
}
/**
* 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, 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).
*/
public byte[] getPubKeyHash() {
if (pubKeyHash == null) {
pubKeyHash = Utils.sha256hash160(this.pub);
}
return pubKeyHash;
}
/**
* 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 byte[] getPubKey() {
return pub;
}
/**
* Returns whether this key is using the compressed form or not. Compressed
* pubkeys are only 33 bytes, not 64.
*/
public boolean isCompressed() {
return pub.length == 33;
}
public String toString() {
StringBuffer b = new StringBuffer();
b.append("pub:").append(Utils.bytesToHexString(pub));
if (creationTimeSeconds != 0) {
b.append(" timestamp:" + creationTimeSeconds);
}
return b.toString();
}
public String toStringWithPrivate() {
StringBuffer b = new StringBuffer();
b.append(toString());
if (priv != null) {
b.append(" priv:").append(Utils.bytesToHexString(priv.toByteArray()));
}
return b.toString();
}
// /**
// * Returns the address that corresponds to the public part of this ECKey. Note that an address is derived from
// * the RIPEMD-160 hash of the public key and is not the public key itself (which is too large to be convenient).
// */
// public Address toAddress(NetworkParameters params) {
// byte[] hash160 = Utils.sha256hash160(pub);
// return new Address(params, hash160);
// }
/**
* Groups the two components that make up a signature, and provides a way to
* encode to DER form, which is how ECDSA signatures are represented when
* embedded in other data structures in the Bitcoin protocol. The raw
* components can be useful for doing further EC maths on them.
*/
public static class ECDSASignature {
/**
* The two components of the signature.
*/
public final BigInteger r, s;
/**
* Constructs a signature with the given components. Does NOT
* automatically canonicalise the signature.
*/
public ECDSASignature(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
/**
* Returns true if the S component is "low", that means it is below
* {@link ECKey#HALF_CURVE_ORDER}. See <a
* href="https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">BIP62</a>.
*/
public boolean isCanonical() {
return s.compareTo(HALF_CURVE_ORDER) <= 0;
}
/**
* Will automatically adjust the S component to be less than or equal to
* half the curve order, if necessary. This is required because for
* every signature (r,s) the signature (r, -s (mod N)) is a valid
* signature of the same message. However, we dislike the ability to
* modify the bits of a Bitcoin transaction after it's been signed, as
* that violates various assumed invariants. Thus in future only one of
* those forms will be considered legal and the other will be banned.
*/
public ECDSASignature toCanonicalised() {
if (!isCanonical()) {
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
return new ECDSASignature(r, CURVE.getN().subtract(s));
} else {
return this;
}
}
/**
* DER is an international standard for serializing data structures
* which is widely used in cryptography. It's somewhat like protocol
* buffers but less convenient. This method returns a standard DER
* encoding of the signature, as recognized by OpenSSL and other
* libraries.
*/
public byte[] encodeToDER() {
try {
return derByteStream().toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public static ECDSASignature decodeFromDER(byte[] bytes) {
ASN1InputStream decoder = null;
try {
decoder = new ASN1InputStream(bytes);
DLSequence seq = (DLSequence) decoder.readObject();
if (seq == null) {
throw new RuntimeException("Reached past end of ASN.1 stream.");
}
ASN1Integer r, s;
try {
r = (ASN1Integer) seq.getObjectAt(0);
s = (ASN1Integer) seq.getObjectAt(1);
} catch (ClassCastException e) {
throw new IllegalArgumentException(e);
}
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
// Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (decoder != null) {
try {
decoder.close();
} catch (IOException x) {
}
}
}
}
protected ByteArrayOutputStream derByteStream() throws IOException {
// Usually 70-72 bytes.
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ECDSASignature other = (ECDSASignature) o;
return r.equals(other.r)
&& s.equals(other.s);
}
@Override
public int hashCode() {
int result = r.hashCode();
result = 31 * result + s.hashCode();
return result;
}
}
/**
* Signs the given hash and returns the R and S components as BigIntegers.
* In the Bitcoin protocol, they are usually encoded using DER format, so
* you want {@link ECKey#signToDER(Sha256Hash)} instead. However sometimes
* the independent components can be useful, for instance, if you're doing
* to do further EC maths on them.
*
* @throws IllegalStateException if this ECKey doesn't have a private part.
*/
public ECDSASignature sign(Sha256Hash input) {
if (priv == null) {
throw new IllegalStateException("This ECKey does not have the private key necessary for signing.");
}
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, 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) {
if (NativeSecp256k1.enabled) {
return NativeSecp256k1.verify(data, signature.encodeToDER(), 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.
e.printStackTrace();
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.
*/
public static boolean verify(byte[] data, byte[] signature, byte[] pub) {
if (NativeSecp256k1.enabled) {
return NativeSecp256k1.verify(data, signature, pub);
}
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.
*/
public boolean verify(byte[] hash, byte[] signature) {
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());
}
/**
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the
* public key, and throws an exception if the signature doesn't match
*
* @throws java.security.SignatureException if the signature does not match.
*/
public void verifyOrThrow(byte[] hash, byte[] signature) throws SignatureException {
if (!verify(hash, signature)) {
throw new SignatureException();
}
}
/**
* Verifies the given R/S pair (signature) against a hash using the public
* key, and throws an exception if the signature doesn't match
*
* @throws java.security.SignatureException if the signature does not match.
*/
public void verifyOrThrow(Sha256Hash sigHash, ECDSASignature signature) throws SignatureException {
if (!ECKey.verify(sigHash.getBytes(), signature, getPubKey())) {
throw new SignatureException();
}
}
private static BigInteger extractPrivateKeyFromASN1(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();
checkArgument(seq.size() == 4, "Input does not appear to be an ASN.1 OpenSSL EC private key");
checkArgument(((DERInteger) seq.getObjectAt(0)).getValue().equals(BigInteger.ONE),
"Input is of wrong version");
Object obj = seq.getObjectAt(1);
byte[] bits = ((ASN1OctetString) obj).getOctets();
decoder.close();
return new BigInteger(1, bits);
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen, reading from memory stream.
}
}
/**
* Signs a text message using the standard Bitcoin messaging signing format
* and returns the signature as a base64 encoded string.
*
* @throws IllegalStateException if this ECKey does not have the private
* part.
*/
public String signMessage(String message) {
if (priv == null) {
throw new IllegalStateException("This ECKey does not have the private key necessary for signing.");
}
byte[] data = Utils.formatMessageForSigning(message);
//Sha256Hash hash = Sha256Hash.createDouble(data);
Sha256Hash hash = Sha256Hash.create(data); //ToDo: mark changed
ECDSASignature sig = sign(hash);
// Now we have to work backwards to figure out the recId needed to recover the signature.
int recId = -1;
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, hash, isCompressed());
if (k != null && Arrays.equals(k.pub, pub)) {
recId = i;
break;
}
}
if (recId == -1) {
throw new RuntimeException("Could not construct a recoverable key. This should never happen.");
}
int headerByte = recId + 27 + (isCompressed() ? 4 : 0);
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
sigData[0] = (byte) headerByte;
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
return new String(Base64.encode(sigData), Charset.forName("UTF-8"));
}
/**
* Given an arbitrary piece of text and a Bitcoin-format message signature
* encoded in base64, returns an ECKey containing the public key that was
* used to sign it. This can then be compared to the expected public key to
* determine if the signature was correct. These sorts of signatures are
* compatible with the Bitcoin-Qt/bitcoind format generated by
* signmessage/verifymessage RPCs and GUI menu options. They are intended
* for humans to verify their communications with each other, hence the
* base64 format and the fact that the input is text.
*
* @param message Some piece of human readable text.
* @param signatureBase64 The Bitcoin-format message signature in base64
* @throws SignatureException If the public key could not be recovered or if
* there was a signature format error.
*/
public static ECKey signedMessageToKey(String message, String signatureBase64) throws SignatureException {
byte[] signatureEncoded;
try {
signatureEncoded = Base64.decode(signatureBase64);
} catch (RuntimeException e) {
// This is what you get back from Bouncy Castle if base64 doesn't decode :(
throw new SignatureException("Could not decode base64", e);
}
// Parse the signature bytes into r/s and the selector value.
if (signatureEncoded.length < 65) {
throw new SignatureException("Signature truncated, expected 65 bytes and got " + signatureEncoded.length);
}
int header = signatureEncoded[0] & 0xFF;
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
// 0x1D = second key with even y, 0x1E = second key with odd y
if (header < 27 || header > 34) {
throw new SignatureException("Header byte out of range: " + header);
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 1, 33));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 33, 65));
ECDSASignature sig = new ECDSASignature(r, s);
byte[] messageBytes = Utils.formatMessageForSigning(message);
// Note that the C++ code doesn't actually seem to specify any character encoding. Presumably it's whatever
// JSON-SPIRIT hands back. Assume UTF-8 for now.
Sha256Hash messageHash = Sha256Hash.create(messageBytes);
boolean compressed = false;
if (header >= 31) {
compressed = true;
header -= 4;
}
int recId = header - 27;
ECKey key = ECKey.recoverFromSignature(recId, sig, messageHash, compressed);
if (key == null) {
throw new SignatureException("Could not recover public key from signature");
}
return key;
}
/**
* Convenience wrapper around
* {@link ECKey#signedMessageToKey(String, String)}. If the key derived from
* the signature is not the same as this one, throws a SignatureException.
*/
public void verifyMessage(String message, String signatureBase64) throws SignatureException {
ECKey key = ECKey.signedMessageToKey(message, signatureBase64);
if (!Arrays.equals(key.getPubKey(), pub)) {
throw new SignatureException("Signature did not match for message");
}
}
/**
* <p>
* Given the components of a signature and a selector value, recover and
* return the public key that generated the signature according to the
* algorithm in SEC1v2 section 4.1.6.</p>
*
* <p>
* The recId is an index from 0 to 3 which indicates which of the 4 possible
* keys is the correct one. Because the key recovery operation yields
* multiple potential keys, the correct key must either be stored alongside
* the signature, or you must be willing to try each recId in turn until you
* find one that outputs the key you are expecting.</p>
*
* <p>
* If this method returns null it means recovery was not possible and recId
* should be iterated.</p>
*
* <p>
* Given the above two points, a correct usage of this method is inside a
* for loop from 0 to 3, and if the output is null OR a key that is not the
* one you expect, you try again with the next recId.</p>
*
* @param recId Which possible key to recover.
* @param r The R component of the signature.
* @param s The S component of the signature.
* @param message Hash of the data that was signed.
* @param compressed Whether or not the original pubkey was compressed.
* @return An ECKey containing only the public part, or null if recovery
* wasn't possible.
*/
public static ECKey recoverFromSignature(int recId, ECDSASignature sig, Sha256Hash message, boolean compressed) {
checkArgument(recId >= 0, "recId must be positive");
checkArgument(sig.r.compareTo(BigInteger.ZERO) >= 0, "r must be positive");
checkArgument(sig.s.compareTo(BigInteger.ZERO) >= 0, "s must be positive");
if (message == null) {
throw new NullPointerException("argument null");
}
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
BigInteger n = ecParams.getN(); // Curve order.
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = sig.r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
// specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
// conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then
// do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
ECCurve.Fp curve = (ECCurve.Fp) ecParams.getCurve();
BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime.
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
// So it's encoded in the recId.
ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
if (!R.multiply(n).isInfinity()) {
return null;
}
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
BigInteger e = message.toBigInteger();
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
// ** is point multiplication and + is point addition (the EC group operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
// inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
BigInteger rInv = sig.r.modInverse(n);
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint p1 = ecParams.getG().multiply(eInvrInv);
ECPoint p2 = R.multiply(srInv);
ECPoint.Fp q = (ECPoint.Fp) p2.add(p1);
if (compressed) {
// We have to manually recompress the point as the compressed-ness gets lost when multiply() is used.
q = new ECPoint.Fp(curve, q.getX(), q.getY(), true);
}
return new ECKey((byte[]) null, q.getEncoded());
}
/**
* Decompress a compressed public key (x co-ord and low-bit of y-coord).
*/
private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
// This code is adapted from Bouncy Castle ECCurve.Fp.decodePoint(), but it wasn't easily re-used.
ECCurve.Fp curve = (ECCurve.Fp) ecParams.getCurve();
ECFieldElement x = new ECFieldElement.Fp(curve.getQ(), xBN);
ECFieldElement alpha = x.multiply(x.square().add(curve.getA())).add(curve.getB());
ECFieldElement beta = alpha.sqrt();
// If we can't find a sqrt we haven't got a point on the curve - invalid inputs.
if (beta == null) {
throw new IllegalArgumentException("Invalid point compression");
}
if (beta.toBigInteger().testBit(0) == yBit) {
return new ECPoint.Fp(curve, x, beta, true);
} else {
ECFieldElement.Fp y = new ECFieldElement.Fp(curve.getQ(), curve.getQ().subtract(beta.toBigInteger()));
return new ECPoint.Fp(curve, x, y, true);
}
}
/**
* Returns a 32 byte array containing the private key.
*/
public byte[] getPrivKeyBytes() {
return Utils.bigIntegerToBytes(priv, 32);
}
// /**
// * Exports the private key in the form used by the Satoshi client "dumpprivkey" and "importprivkey" commands. Use
// * the {@link com.google.bitcoin.core.DumpedPrivateKey#toString()} method to get the string.
// *
// * @param params The network this key is intended for use on.
// * @return Private key bytes as a {@link DumpedPrivateKey}.
// */
// public DumpedPrivateKey getPrivateKeyEncoded(NetworkParameters params) {
// return new DumpedPrivateKey(params, getPrivKeyBytes(), isCompressed());
// }
/**
* Returns the creation time of this key or zero if the key was deserialized
* from a version that did not store that data.
*/
public long getCreationTimeSeconds() {
return creationTimeSeconds;
}
/**
* Sets the creation time of this key. Zero is a convention to mean
* "unavailable". This method can be useful when you have a raw key you are
* importing from somewhere else.
*
* @param newCreationTimeSeconds
*/
public void setCreationTimeSeconds(long newCreationTimeSeconds) {
if (newCreationTimeSeconds < 0) {
throw new IllegalArgumentException("Cannot set creation time to negative value: " + newCreationTimeSeconds);
}
creationTimeSeconds = newCreationTimeSeconds;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ECKey ecKey = (ECKey) o;
if (!Arrays.equals(pub, ecKey.pub)) {
return false;
}
return true;
}
@Override
public int hashCode() {
// Public keys are random already so we can just use a part of them as the hashcode. Read from the start to
// avoid picking up the type code (compressed vs uncompressed) which is tacked on the end.
return (pub[0] & 0xFF) | ((pub[1] & 0xFF) << 8) | ((pub[2] & 0xFF) << 16) | ((pub[3] & 0xFF) << 24);
}
private static void checkArgument(boolean b, String a) {
if (!b) {
throw new IllegalArgumentException(a);
}
}
public BigInteger deffiehelman(byte[] otherPublicBytes) {
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
ECDHBasicAgreement agreementGenerator = new ECDHBasicAgreement();
agreementGenerator.init(privKey);
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(otherPublicBytes), CURVE);
BigInteger calculateAgreement = agreementGenerator.calculateAgreement(params);
//System.out.println("asd " + calculateAgreement);
return calculateAgreement;
}
}