c0mm4nd / dart-secp256k1

Fucking dart!!! Force me to write all deps!!!
https://pub.dev/packages/secp256k1
MIT License
12 stars 1 forks source link

cannot verify signature with secp256k1 javascript library number 2 #6

Open tibfox opened 3 years ago

tibfox commented 3 years ago

hey there! I encounter the same issue like mentioned here: #2 And I can not find any reason for that.

The created signature from dart is marked as invalid from the nodejs side randomly. I know it is probably a pretty silly bug on my coding side but I would really like you to verify that both libraries can work together since I can not change any server side code and I am slowly goign nuts here.

this is how I create the signature in dart (I use flutter_bloc if that's helpful)

import 'package:hex/hex.dart';
import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:secp256k1/secp256k1.dart';
import 'package:bs58/bs58.dart';
...
...
...
    Digest digest = sha256.convert(txBytes);
    String pkHex = HEX.encode(base58.decode(privKey));
    PrivateKey _pk = PrivateKey.fromHex(pkHex);
    Signature _sig = _pk.signature(digest.toString());
    String _sigHex = _sig.toString();
    Uint8List sigBytes = Uint8List.fromList(HEX.decode(_sigHex));
    String _sigB58 = base58.encode(sigBytes);
    txNew.signature = _sigB58;
    print(_sig.verify(_pk.publicKey, digest.toString())); // of course always returns true

...
...

And this is a short and dirty nodejs part I reproduce the server side code to verify the signature:

//positive example
var pub = "29DCvEf5vUjzsgHM4iLgFjsB4gcnVzYMakwVLPvr9jJQL"
var sig = "2D6BNWWXELcZ4sQ2dBLrwdhUqBCQAQiWDoLWqeBoaPfvE1HbJxVcwjfh7RTa8Gfn1pYwyjgtfpp7Tp2gcz9m7jp1"
var hash = "f1dfef3a531c011fc449c783fa995c277e2780745f579eddb8458c081375ed7e"

//negative example
// var pub = "29DCvEf5vUjzsgHM4iLgFjsB4gcnVzYMakwVLPvr9jJQL"
// var sig = "2VAe7dLahXSLgbkpQo7iqpEHMYhfd8MQzEMhcZM92c8qfWauQEhWvU9yR2oabuMoxSzj4e3UqvidFZEJ4DrmTFSy"
// var hash = "03b90c912a5fbc2a4e53e4e785c2784887e437f47b355292d9405a0f34e2c553

var bufferHash = Buffer.from(hash, 'hex')
var b58sign = bs58.decode(sig)
var b58pub = bs58.decode(pub)
if (secp256k1.ecdsaVerify(b58sign, bufferHash, b58pub)) {
    console.log("sig valid")
} else {
    console.log("sig invalid")
}

Like I've said, it is random - no real schema I can observe. From 5 tries - 2 or less are valid.

Please let me know if you need any further examples or codelines. I am very thankful for your work and response in advanced.

c0mm4nd commented 3 years ago

https://github.com/cryptocoinjs/secp256k1-node/blob/024fbdad3fb64499e8db8b35246c2f0a36afa8c8/lib/elliptic.js#L338

Reason: S value (the signature.s) sometimes larger than ec.nh (= ec.n.ushrn(1))

However, in require('elliptic').ec('secp256k1') there is no such limitation on signature.s.

The solution is to add this when signaturing, but does any document about why doing this?

tibfox commented 3 years ago

Oh thanks for the quick response! Unfortunately changing the server side code is not an option for me so the solution you provided is not executable in my case. And additionally to this I'm not able / knowledged enough to correctly execute the solution on the dart side. Would you be so kind to point out how I could implement this in dart? Sorry but I'm quiet new to cryptographics and was amazed to find your library bc it will make my life so much easier

c0mm4nd commented 3 years ago

found the reason -> the RFC6979 sign requires s is larger than half n (the nh)

c0mm4nd commented 3 years ago

I will write RFC6979 sign into package ecdsa first, then export RFC6979 sign on re-structuring this package

tibfox commented 3 years ago

Great thank you so much!

tibfox commented 3 years ago

@C0MM4ND do you have any time plan for implementing the changes? I am stucked a bit with my current project due to this error. No pressure - just asking

c0mm4nd commented 3 years ago

https://pub.dev/packages/ecdsa/versions/0.0.2

dart signer in test

import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:ecdsa/ecdsa.dart';
import 'package:elliptic/elliptic.dart';

Future<void> main() async {
  var ec = getS256();

  var random = Random.secure();
  var client = HttpClient();
  for (var i = 0; i < 100000; i++) {
    var msgHash = List<int>.generate(32, (i) => random.nextInt(256));
    //.join(); // random 32 bytes;
    var pk = ec.generatePrivateKey();
    var sig = deterministicSign(pk, msgHash);
    var pub = pk.publicKey;

    var ok = verify(pub, msgHash, sig);
    if (!ok) {
      throw Error();
    }

    // var rs = sig.toHexes();
    // print('testing: ' + pk.D.toRadixString(16));
    var r = sig.R.toRadixString(16).padLeft(64, '0'); // rs[0];
    var s = sig.S.toRadixString(16).padLeft(64, '0'); // rs[1];
    var req = await client.getUrl(Uri.parse('http://127.0.0.1:3000/' +
        [
          pub,
          List<String>.generate(32,
                  (index) => msgHash[index].toRadixString(16).padLeft(2, '0'))
              .join(),
          r,
          s
        ].join('/')));
    var res = await req.close();
    res.transform(Utf8Decoder()).listen(print);
  }
}

nodejs validator in test

const { exception } = require('console');
const express = require('express');
const { exit } = require('process');
const app = express()
const port = 3000

var ec = require('secp256k1/elliptic');
// var ec = new EC('secp256k1');

function hex2U8Array(hexString) {
    return Uint8Array.from(Buffer.from(hexString, 'hex'))
}

app.get('/:pub/:hash/:R/:S', (req, res) => {
    let pub = req.params['pub']
    let hash = req.params['hash']
    let r = req.params['R']
    let s = req.params['S']
    // console.log(r+':'+s)
    console.log(hash)

    // var key = ec.keyFromPublic(pub, 'hex')
    var key = Buffer.from(pub, 'hex')
    var sig = Buffer.from(r+s, 'hex')
    var msg = Buffer.from(hash, 'hex')

    try {
        let ok = ec.ecdsaVerify(sig, msg, key)
        if (!ok) {
            console.log(ok, r+':'+s)
            res.send('failed')
            exit()
        }
        res.send('ok')
    } catch (e) {
        console.log('failed', r+':'+s)
        res.send('failed'+ e.message)
        exit()
    }
})

app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
})

Passed more than 10,000 tests.

tibfox commented 3 years ago

unfortunately now the signature is not the correct length anymore. Before I implemented the change it always had the correct length but wasn't valid randomly. What am I doing wrong now?

Expected signature to be an Uint8Array with length 64 is what I get back from the server.

This is what I do to sign:

 Future<Transaction> sign(
      Transaction tx, String applicationUser, String privKey) async {
    var ec = getS256();
    Transaction txNew = Transaction(type: tx.type, data: tx.data);
    txNew.sender = applicationUser; // set sender of tx
    txNew.ts = DateTime.now().millisecondsSinceEpoch; // set timestamp
    var jsonString = jsonEncode(txNew.toJson());
    List<int> txBytes = utf8.encode(jsonString);
    Digest digest = sha256.convert(txBytes);
    var messageHash = digest.toString();
    txNew.hash = messageHash;
    String pkHex = HEX.encode(base58.decode(privKey));
    PrivateKey _pk = PrivateKey.fromHex(ec, pkHex);
    Signature _sig = deterministicSign(_pk, HEX.decode(messageHash));
    String _sigHex = _sig.toString();
    Uint8List sigBytes = Uint8List.fromList(HEX.decode(_sigHex));
    print(sigBytes.length);
    String _sigB58 = base58.encode(sigBytes);
    txNew.signature = _sigB58;
    return txNew;
  }
c0mm4nd commented 3 years ago

In ecdsa package _sig.toString() equals to _sig.toDER() (Distinguished Encoding Rules) To get the compacted hex like package secp256k1, you need to use

String _sigHex = _sig.R.toRadixString(16).padLeft(64, '0') + // r's hex 
    _sig.S.toRadixString(16).padLeft(64, '0'); // s's hex

toCompactHex will be add into ecdsa package, but the toString wont be changed because the toDER/toASN1 is a standard defined in spec https://www.secg.org/sec2-v2.pdf

using ASN.1 syntax and specifies ASN.1 object identifiers for the elliptic curve domain parameters recommended in this document

tibfox commented 3 years ago

Amazing thank you so much for the short and quick answer! It's working like a charm!

negu63 commented 2 years ago

It really helped a lot. Thank you guys so much!!