EOSIO / eosjs-ecc

Elliptic curve cryptography functions: Private Key, Public Key, Signature, AES, Encryption, Decryption
288 stars 119 forks source link

Why use hash concatenating more zero bytes when calculating K #17

Closed XuNeal closed 6 years ago

XuNeal commented 6 years ago

Hey everyone I want to known why EOS need the lenR and lenS are both 32 , It's not a requirement in ethereum.

while (true) {
        ecsignature = ecdsa.sign(curve, dataSha256, privateKey.d, nonce++);
        der = ecsignature.toDER();
        console.log("der: " + der.toString('hex'))
        lenR = der[3];
        lenS = der[5 + lenR];
        if (lenR === 32 && lenS === 32) {
            i = ecdsa.calcPubKeyRecoveryParam(curve, e, ecsignature, privateKey.toPublic().Q);
            i += 4; // compressed
            i += 27; // compact  //  24 or 27 :( forcing odd-y 2nd key candidate)
            break;
        }
        if (nonce % 10 === 0) {
            console.log("WARN: " + nonce + " attempts to find canonical signature");
        }
    }

and why to generate a new K by changing the hash.

function deterministicGenerateK(curve, hash, d, checkSig, nonce) {

  enforceType('Buffer', hash);
  enforceType(BigInteger, d);

  if (nonce) {
    hash = crypto.sha256(Buffer.concat([hash, new Buffer(nonce)]));
  }
  console.log('toSign: ' + hash.toString('hex'))

  // sanity check
  assert.equal(hash.length, 32, 'Hash must be 256 bit');

  var x = d.toBuffer(32);
  var k = new Buffer(32);
  var v = new Buffer(32);

  // Step B
  v.fill(1);

  // Step C
  k.fill(0);

  // Step D
  k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0]), x, hash]), k);

  // Step E
  v = crypto.HmacSHA256(v, k);

  // Step F
  k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([1]), x, hash]), k);

  // Step G
  v = crypto.HmacSHA256(v, k);

  // Step H1/H2a, ignored as tlen === qlen (256 bit)
  // Step H2b
  v = crypto.HmacSHA256(v, k);

  var T = BigInteger.fromBuffer(v);

  // Step H3, repeat until T is within the interval [1, n - 1]
  while (T.signum() <= 0 || T.compareTo(curve.n) >= 0 || !checkSig(T)) {
    k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k);
    v = crypto.HmacSHA256(v, k);

    // Step H1/H2a, again, ignored as tlen === qlen (256 bit)
    // Step H2b again
    v = crypto.HmacSHA256(v, k);

    T = BigInteger.fromBuffer(v);
  }

  return T;
}

Is it possible to get the correct K only with the following code?

  // Step H3, repeat until T is within the interval [1, n - 1]
  while (T.signum() <= 0 || T.compareTo(curve.n) >= 0 || !checkSig(T)) {
    k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k);
    v = crypto.HmacSHA256(v, k);

    // Step H1/H2a, again, ignored as tlen === qlen (256 bit)
    // Step H2b again
    v = crypto.HmacSHA256(v, k);

    T = BigInteger.fromBuffer(v);
  }

Thanks

jcalfee commented 6 years ago

Great questions.. I need find some time to review this it has been a while. Some people that might help us (they are probably very busy too with the launch coming up): @arhag @chris-allnutt @heifner

abourget commented 6 years ago

EOS uses the compact signature serialization format, and forces it to be so, to have consistent length. It'll pump up the nonce one by one to make sure it can have a compact format. The goal is to have, for a given payload, one and only one valid signature.

That nonce in the second code is there to tweak the content (it adds "\0x00", then "\0x00\0x00", etc..) when the top-level loop (which is a few calls above in the call stack) detects it didn't produce a "canonical" signature.

You can take a look at the Go implementation (been there too) here: https://github.com/eoscanada/eos-go/blob/master/btcsuite/btcd/btcec/privkey.go#L68 and around that codebase.. like https://github.com/eoscanada/eos-go/blob/master/ecc/privkey.go#L84

XuNeal commented 6 years ago

https://steemit.com/steem/@dantheman/steem-and-bitshares-cryptographic-security-update

The blog show why the eos require canonical signatures. I will close this issue, thanks @abourget and @jcalfee