3
0
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:
Mike Hearn 2013-10-23 12:50:52 +02:00
parent 32a823804c
commit f315125bf5
3 changed files with 77 additions and 15 deletions

View File

@ -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();

View File

@ -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);
}

View File

@ -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