Skip to content

Commit

Permalink
Allow forced low r-value signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Sep 5, 2023
1 parent f828898 commit 79b8e0d
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 57 deletions.
19 changes: 16 additions & 3 deletions coinlib/lib/src/bindings/secp256k1_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ abstract class Secp256k1Base<
static const uncompressedFlags = 2;
static const privkeySize = 32;
static const hashSize = 32;
static const entropySize = 32;
static const pubkeySize = 64;
static const compressedPubkeySize = 33;
static const uncompressedPubkeySize = 65;
Expand Down Expand Up @@ -80,6 +81,7 @@ abstract class Secp256k1Base<
late HeapArrayBase scalarArray;
late HeapArrayBase serializedPubKeyArray;
late HeapArrayBase hashArray;
late HeapArrayBase entropyArray;
late HeapArrayBase serializedSigArray;
late HeapArrayBase derSigArray;

Expand Down Expand Up @@ -259,17 +261,28 @@ abstract class Secp256k1Base<
/// Constructs a signature in the compact format using a 32-byte message
/// [hash] and 32-byte [privKey] scalar. The signature contains a 32-byte
/// big-endian R value followed by a 32-byte big-endian low-S value.
/// Signatures are deterministic according to RFC6979.
Uint8List ecdsaSign(Uint8List hash, Uint8List privKey) {
/// Signatures are deterministic according to RFC6979. Additional entropy may
/// be added as 32 bytes with [extraEntropy].
Uint8List ecdsaSign(
Uint8List hash, Uint8List privKey, [Uint8List? extraEntropy,]
) {
_requireLoad();

privKeyArray.load(privKey);
hashArray.load(hash);
if (extraEntropy != null) entropyArray.load(extraEntropy);

// Sign
if (
extEcdsaSign(
ctxPtr, sigPtr, hashArray.ptr, privKeyArray.ptr, nullPtr, nullPtr,
ctxPtr, sigPtr, hashArray.ptr, privKeyArray.ptr,
// Passing null will give secp256k1_nonce_function_rfc6979. If secp256k1
// changes this default function in the future,
// secp256k1_nonce_function_rfc6979 should be used directly.
// Using null as it doesn't require passing an additional constant from
// the web and io implementations.
nullPtr,
extraEntropy == null ? nullPtr : entropyArray.ptr,
) != 1
) {
throw Secp256k1Exception("Cannot sign message with private key");
Expand Down
1 change: 1 addition & 0 deletions coinlib/lib/src/bindings/secp256k1_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Secp256k1 extends Secp256k1Base<
scalarArray = HeapArrayFfi(Secp256k1Base.privkeySize);
serializedPubKeyArray = HeapArrayFfi(Secp256k1Base.uncompressedPubkeySize);
hashArray = HeapArrayFfi(Secp256k1Base.hashSize);
entropyArray = HeapArrayFfi(Secp256k1Base.entropySize);
serializedSigArray = HeapArrayFfi(Secp256k1Base.sigSize);
derSigArray = HeapArrayFfi(Secp256k1Base.derSigSize);

Expand Down
1 change: 1 addition & 0 deletions coinlib/lib/src/bindings/secp256k1_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Secp256k1 extends Secp256k1Base<int, int, int, int,int, int, int, int> {
privKeyArray = arrayFactory.create(Secp256k1Base.privkeySize);
scalarArray = arrayFactory.create(Secp256k1Base.privkeySize);
hashArray = arrayFactory.create(Secp256k1Base.hashSize);
entropyArray = arrayFactory.create(Secp256k1Base.entropySize);
serializedPubKeyArray = arrayFactory.create(
Secp256k1Base.uncompressedPubkeySize,
);
Expand Down
25 changes: 21 additions & 4 deletions coinlib/lib/src/crypto/ecdsa_signature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,29 @@ class ECDSASignature {
/// Creates a signature using a private key ([privkey]) for a given 32-byte
/// [hash]. The signature will be generated deterministically and shall be the
/// same for a given hash and key.
factory ECDSASignature.sign(ECPrivateKey privkey, Uint8List hash) {
/// If [forceLowR] is true (default), then signatures with high r-values will
/// be skipped until a signature with a low r-value is found.
factory ECDSASignature.sign(
ECPrivateKey privkey,
Uint8List hash,
{ bool forceLowR = true, }
) {
checkBytes(hash, 32);

final sig = ECDSASignature.fromCompact(
secp256k1.ecdsaSign(hash, privkey.data),
);
Uint8List compact;

if (forceLowR) {
// Loop through incrementing entropy until a low r-value is found
Uint8List extraEntropy = Uint8List(32);
do {
compact = secp256k1.ecdsaSign(hash, privkey.data, extraEntropy);
for (int i = 0; extraEntropy[i]++ == 255; i++) {}
} while (compact[0] >= 0x80);
} else {
compact = secp256k1.ecdsaSign(hash, privkey.data);
}

final sig = ECDSASignature.fromCompact(compact);

// Verify signature to protect against computation errors. Cosmic rays etc.
if (!sig.verify(privkey.pubkey, hash)) throw InvalidECDSASignature();
Expand Down
30 changes: 20 additions & 10 deletions coinlib/test/crypto/ecdsa_signature_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,27 @@ void main() {
});

test("provides a correct signature", () {
// This signature has been determined to be correct. secp256k1 has more
// exhaustive tests and this method is a wrapper around that.
// Should be the same each time
for (int x = 0; x < 2; x++) {
final sig = ECDSASignature.sign(key, msgHash);
expect(
bytesToHex(sig.compact),
"a951b0cf98bd51c614c802a65a418fa42482dc5c45c9394e39c0d98773c51cd530104fdc36d91582b5757e1de73d982e803cc14d75e82c65daf924e38d27d834",
);
expect(sig.verify(key.pubkey, msgHash), true);
// The low and high r-value signatures have been determined to be
// correct. secp256k1 has more exhaustive tests and this method is a
// wrapper around that.

expectWithR(bool lowR, String hex) {
// Do twice and should be the same both times
for (int x = 0; x < 2; x++) {
final sig = ECDSASignature.sign(key, msgHash, forceLowR: lowR);
expect(bytesToHex(sig.compact), hex);
expect(sig.verify(key.pubkey, msgHash), true);
}
}
expectWithR(
false,
"a951b0cf98bd51c614c802a65a418fa42482dc5c45c9394e39c0d98773c51cd530104fdc36d91582b5757e1de73d982e803cc14d75e82c65daf924e38d27d834",
);
expectWithR(
true,
"0eda3be316b7d47901bc6a2bfc1b95cf6c408129a564d6b75963451e16fa155025077334a5bf937fcf3242cec81506deea34e26e65892bda896533c4e50e9360",
);

});

test("slight change in hash gives different signatures", () {
Expand Down
Loading

0 comments on commit 79b8e0d

Please sign in to comment.