mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-11 17:55:53 +00:00
ECKey: Always use the canonical form of the S component.
This is a part of the general Bitcoin protocol anti-tx malleability work.
This commit is contained in:
parent
32a823804c
commit
f315125bf5
@ -66,7 +66,14 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
public class ECKey implements Serializable {
|
||||
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
|
||||
|
||||
private static final ECDomainParameters ecParams;
|
||||
/** The parameters of the secp256k1 curve that Bitcoin uses. */
|
||||
public static final ECDomainParameters CURVE;
|
||||
|
||||
/**
|
||||
* Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. If you aren't
|
||||
* sure what this is about, you can ignore it.
|
||||
*/
|
||||
public static final BigInteger HALF_CURVE_ORDER;
|
||||
|
||||
private static final SecureRandom secureRandom;
|
||||
private static final long serialVersionUID = -728224901792295832L;
|
||||
@ -74,7 +81,8 @@ public class ECKey implements Serializable {
|
||||
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());
|
||||
CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
|
||||
HALF_CURVE_ORDER = params.getN().shiftRight(1);
|
||||
secureRandom = new SecureRandom();
|
||||
}
|
||||
|
||||
@ -106,7 +114,7 @@ public class ECKey implements Serializable {
|
||||
*/
|
||||
public ECKey() {
|
||||
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||
ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(ecParams, secureRandom);
|
||||
ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(CURVE, secureRandom);
|
||||
generator.init(keygenParams);
|
||||
AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
|
||||
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
|
||||
@ -122,7 +130,7 @@ public class ECKey implements Serializable {
|
||||
}
|
||||
|
||||
private static ECPoint compressPoint(ECPoint uncompressed) {
|
||||
return new ECPoint.Fp(ecParams.getCurve(), uncompressed.getX(), uncompressed.getY(), true);
|
||||
return new ECPoint.Fp(CURVE.getCurve(), uncompressed.getX(), uncompressed.getY(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -236,7 +244,7 @@ public class ECKey implements Serializable {
|
||||
* new BigInteger(1, bytes);</tt>
|
||||
*/
|
||||
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) {
|
||||
ECPoint point = ecParams.getG().multiply(privKey);
|
||||
ECPoint point = CURVE.getG().multiply(privKey);
|
||||
if (compressed)
|
||||
point = compressPoint(point);
|
||||
return point.getEncoded();
|
||||
@ -320,12 +328,32 @@ public class ECKey implements Serializable {
|
||||
/** The two components of the signature. */
|
||||
public BigInteger r, s;
|
||||
|
||||
/** Constructs a signature with the given components. */
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void ensureCanonical() {
|
||||
if (s.compareTo(HALF_CURVE_ORDER) > 0) {
|
||||
// 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.
|
||||
s = CURVE.getN().subtract(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -418,10 +446,12 @@ public class ECKey implements Serializable {
|
||||
}
|
||||
|
||||
ECDSASigner signer = new ECDSASigner();
|
||||
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, ecParams);
|
||||
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
|
||||
signer.init(true, privKey);
|
||||
BigInteger[] sigs = signer.generateSignature(input.getBytes());
|
||||
return new ECDSASignature(sigs[0], sigs[1]);
|
||||
BigInteger[] components = signer.generateSignature(input.getBytes());
|
||||
final ECDSASignature signature = new ECDSASignature(components[0], components[1]);
|
||||
signature.ensureCanonical();
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,7 +469,7 @@ public class ECKey implements Serializable {
|
||||
return NativeSecp256k1.verify(data, signature.encodeToDER(), pub);
|
||||
|
||||
ECDSASigner signer = new ECDSASigner();
|
||||
ECPublicKeyParameters params = new ECPublicKeyParameters(ecParams.getCurve().decodePoint(pub), ecParams);
|
||||
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
|
||||
signer.init(false, params);
|
||||
try {
|
||||
return signer.verifySignature(data, signature.r, signature.s);
|
||||
@ -660,7 +690,7 @@ public class ECKey implements Serializable {
|
||||
Preconditions.checkNotNull(message);
|
||||
// 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 n = CURVE.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
|
||||
@ -670,7 +700,7 @@ public class ECKey implements Serializable {
|
||||
// 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();
|
||||
ECCurve.Fp curve = (ECCurve.Fp) CURVE.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.
|
||||
@ -699,7 +729,7 @@ public class ECKey implements Serializable {
|
||||
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 p1 = CURVE.getG().multiply(eInvrInv);
|
||||
ECPoint p2 = R.multiply(srInv);
|
||||
ECPoint.Fp q = (ECPoint.Fp) p2.add(p1);
|
||||
if (compressed) {
|
||||
@ -712,7 +742,7 @@ public class ECKey implements Serializable {
|
||||
/** 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();
|
||||
ECCurve.Fp curve = (ECCurve.Fp) CURVE.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();
|
||||
|
@ -55,7 +55,7 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
||||
* real signature later.
|
||||
*/
|
||||
public static TransactionSignature dummy() {
|
||||
BigInteger val = BigInteger.ONE.shiftLeft(32 * 8); // 32 byte components.
|
||||
BigInteger val = ECKey.HALF_CURVE_ORDER;
|
||||
return new TransactionSignature(val, val);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,11 @@ import com.google.bitcoin.params.MainNetParams;
|
||||
import com.google.bitcoin.params.TestNet3Params;
|
||||
import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Protos.ScryptParameters;
|
||||
@ -38,8 +43,12 @@ import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.google.bitcoin.core.Utils.reverseBytes;
|
||||
import static org.junit.Assert.*;
|
||||
@ -67,6 +76,29 @@ public class ECKeyTest {
|
||||
BriefLogFormatter.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sValue() throws Exception {
|
||||
// Check that we never generate an S value that is larger than half the curve order. This avoids a malleability
|
||||
// issue that can allow someone to change a transaction [hash] without invalidating the signature.
|
||||
final int ITERATIONS = 10;
|
||||
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(ITERATIONS));
|
||||
List<ListenableFuture<ECKey.ECDSASignature>> sigFutures = Lists.newArrayList();
|
||||
final ECKey key = new ECKey();
|
||||
for (byte i = 0; i < ITERATIONS; i++) {
|
||||
final Sha256Hash hash = Sha256Hash.create(new byte[]{i});
|
||||
sigFutures.add(executor.submit(new Callable<ECKey.ECDSASignature>() {
|
||||
@Override
|
||||
public ECKey.ECDSASignature call() throws Exception {
|
||||
return key.sign(hash);
|
||||
}
|
||||
}));
|
||||
}
|
||||
List<ECKey.ECDSASignature> sigs = Futures.allAsList(sigFutures).get();
|
||||
for (ECKey.ECDSASignature signature : sigs) {
|
||||
assertTrue(signature.s.compareTo(ECKey.HALF_CURVE_ORDER) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatures() throws Exception {
|
||||
// Test that we can construct an ECKey from a private key (deriving the public from the private), then signing
|
||||
|
Loading…
x
Reference in New Issue
Block a user