Skip to content

Commit

Permalink
[DRAFT] ECKey: when signing, grind for low R values
Browse files Browse the repository at this point in the history
Our goal is to make transaction sizes more predictable, simplifying the fee calculation.
Also see bitcoin/bitcoin#13666

FIXME unit tests
  • Loading branch information
Andreas Schildbach authored and schildbach committed Feb 7, 2024
1 parent c52db25 commit 1cb896e
Showing 1 changed file with 30 additions and 3 deletions.
33 changes: 30 additions & 3 deletions core/src/main/java/org/bitcoinj/crypto/ECKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
Expand All @@ -70,6 +71,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.SignatureException;
Expand Down Expand Up @@ -475,6 +477,14 @@ public ECDSASignature toCanonicalised() {
}
}

/**
* Check that the sig has a low R component, and will thus be 71 bytes or less in DER encoding.
*/
public boolean hasLowR() {
final byte[] compact = Utils.bigIntegerToBytes(r, 32);
return compact[0] >= 0;
}

/**
* 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
Expand Down Expand Up @@ -584,11 +594,28 @@ public ECDSASignature sign(Sha256Hash input, @Nullable AesKey aesKey) throws Key

protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) {
Objects.requireNonNull(privateKeyForSigning);
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()) {
int counter = 0;

@Override
protected void initAdditionalInput0(HMac hMac) {
if (counter > 0)
hMac.update(ByteBuffer.allocate(32).putInt(28, counter).array(), 0, 32);
counter++;
if (counter >= Integer.MAX_VALUE)
throw new IllegalStateException();
}
});
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(input.getBytes());
return new ECDSASignature(components[0], components[1]).toCanonicalised();
ECDSASignature signature = null;
do {
// grind for low R values
// see discussion at https://github.com/bitcoin/bitcoin/pull/13666
BigInteger[] components = signer.generateSignature(input.getBytes());
signature = new ECDSASignature(components[0], components[1]);
} while (!signature.hasLowR());
return signature.toCanonicalised();
}

/**
Expand Down

0 comments on commit 1cb896e

Please sign in to comment.