Skip to content

Conversation

@fanatid
Copy link
Member

@fanatid fanatid commented Mar 15, 2016

I want to use this package in cryptocoinjs/secp256k1-node
auto-padding would be nice feature
@dcousens thoughts ?

I'll fix tests after feedback

@dcousens
Copy link
Contributor

The entire point of this module is to be in coherence (with side-by-side readability) with BIP66... I'd rather not.

@fanatid
Copy link
Member Author

fanatid commented Mar 16, 2016

@dcousens BIP66 says nothing about encode, why not include auto-padding in this case?
What you think about check as try-catch?
I checked BIP66 more precisely, it's strange that IsValidSignatureEncoding has no conditions that lenR or lenS more than 33 bytes...

@dcousens
Copy link
Contributor

Indeed, it really should have been a 64-byte fixed format, maybe with segwit...

@dcousens
Copy link
Contributor

@dcousens BIP66 says nothing about encode, why not include auto-padding in this case?

Can you outline where it would be useful?

@fanatid
Copy link
Member Author

fanatid commented Mar 16, 2016

not a 64-bytes, 66-bytes (2 x 33 bytes)
right now I see that lenR + lenS should be less than 72 - 6, but in this case if lenR is 35 and lenS is 31, IsValidSignatureEncoding will still return true, although it is invalid secp256k1 DER signature

@dcousens BIP66 says nothing about encode, why not include auto-padding in this case?

Can you outline where it would be useful?

https://github.com/cryptocoinjs/secp256k1-node/blob/880a9ebb9e972f24803c1d86eaf851701c60ebd9/lib/index.js#L322

@dcousens
Copy link
Contributor

not a 64-bytes, 66-bytes (2 x 33 bytes)

No, I meant 64 bytes. The 33 byte case is basically just a padding byte (for the twos complement) anyway.

@dcousens
Copy link
Contributor

right now I see that lenR + lenS should be less than 72 - 6, but in this case if lenR is 35 and lenS is 31, IsValidSignatureEncoding will still return true, although it is invalid secp256k1 DER signature

Can you write a test case for that?

@fanatid
Copy link
Member Author

fanatid commented Mar 17, 2016

var data = '3044021458a2f39bd87f0000000506030000000000050603022c402dde9afe7f0000010000000100000004000000040000000000000000000000000000000a00000000000000'
require('bip66').check(new Buffer(data, 'hex')) // true

@dcousens
Copy link
Contributor

@fanatid PR it! 👍
Ping @sipa, I guess it was just assumed that this level of error checking would be handled by the underlying ecdsa library? (namely, libsecp256k1?)

@fanatid
Copy link
Member Author

fanatid commented Mar 27, 2016

@dcousens what you final thoughts about auto-padding in bip66 instead secp256k1 (and other libraries)?

@sipa
Copy link

sipa commented Mar 27, 2016

If you pass a DER-valid encoding of an invalid signature to libsecp256k1, its parsing will succeed, but validation will fail.

So typically the steps are:

  • Validate according to BIP66; if that fails, the script is invalid
  • Parse with libsecp256k1. This should always succeed for BIP66 transactions. There is a piece of C code in the libsecp256k1 contrib directory to do parsing more lax, which will accept at least all valid signatures in the blockchain. You could translate it to JS if it's necessary for parsing old signatures.
  • Call secp256k1_ecdsa_signature_normalize if you want to support highS signatures (this is not part of parsing, as it's technically changing the contents of the signature).
  • Call secp256k1_ecdsa_signature_verify. If this fails, the signature is invalid, but the script itself may still be valid (e.g. in a CHECKMULTISIG, or more obscurely, when you would have the nonsensical script CHECKSIG NOT).

@sipa
Copy link

sipa commented Mar 27, 2016

On the reason why BIP66 does not enforce a maximum length on R and S: it didn't need to.

All BIP66 try to do was to give valid signatures a unique and easy-to-determine encoding. Having an overlength R or S automatically make the transaction invalid, so it was not within scope.

Another way of looking at it is, if you're trying to duplicate part of the validation rules in BIP66 (which is about its encoding, not validity), why stop there?

  • You could check whether R and S are within the range [1...secp256k1_order-1], as other signatures are always invalid.
  • You could even go check whether R is a valid X coordinate on the curve modulo the secp256k1 field size.

In short, there was no need to add a rule limiting the size of R or S, so we didn't. There is a rule that checks the total length, because if that was over 255 bytes, you'd need a multi-byte-length decoder to check whether the signature is DER.

If we'd want to go a step further, I think we should add a rule that a signature either has to be valid (as in, completely valid) or empty. That would remove malleability of invalid signatures. Unfortunately that isn't possible with how CHECKMULTISIG works now.

Regarding 64-byte signatures: yes, absolutely. Once segwit is in (which introduces script versioning, making such changes much easier), I do plan on proposing a Schnorr-based scheme with 64-byte signatures. @gmaxwell recently came up with a scheme that would allow reducing all signatures in a transaction into a single one even.

@fanatid
Copy link
Member Author

fanatid commented Mar 31, 2016

@dcousens please reopen, if you decide inline auto-padding to encode:

var r = Buffer.concat([new Buffer([0]), sigObj.r])
for (var lenR = 33, posR = 0; lenR > 1 && r[posR] === 0x00 && !(r[posR + 1] & 0x80); --lenR, ++posR);

var s = Buffer.concat([new Buffer([0]), sigObj.s])
for (var lenS = 33, posS = 0; lenS > 1 && s[posS] === 0x00 && !(s[posS + 1] & 0x80); --lenS, ++posS);

@dcousens
Copy link
Contributor

@fanatid would all existing test fixtures still be valid?
What wouldn't be valid?
When does this 'auto'-padding kick in?

@fanatid fanatid force-pushed the feature/next branch 2 times, most recently from 10c67ca to 537e84e Compare April 22, 2016 05:46
@fanatid fanatid changed the title auto-padding add/remove leading zero on encode/decode Apr 22, 2016
@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

@dcousens not all existed fixtures will be valid (I fixed them in new commit)
since BIP66 not check that r/s can be longer than 32 bytes, I changed PR to adding/removing leading zero for DER (i.e. new commit not expand r/s to 32 bytes)

@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

you can pass 0 to 32 bytes r/s buffer to encode
decode return 1 to 63(?) bytes r/s buffer

index.js Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous recreation of a new Buffer([0]) every time this is called.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, updated!

index.js Outdated
Copy link
Contributor

@dcousens dcousens Apr 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we un-inline this? For readability? With a comment explaining what/where/why.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

index.js Outdated
Copy link
Contributor

@dcousens dcousens Apr 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment to explain what this does? (and note it isn't part of BIP66)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

@dcousens
Copy link
Contributor

Hmmm, I understand why this is the best place to put this... but that doesn't make this feel a little out of place... but seeing as only encode is having a change in behaviour, not decode, I'm not against this as much as I was.

@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

you do not want remove excessive leading zero byte on decode?

* -62300 => 0xff0ca4
*/
function encode (r, s) {
r = Buffer.concat([zeroBuffer1, r])
Copy link
Contributor

@dcousens dcousens Apr 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infact, to avoid superfluous copies everywhere, can you just allocate this in the final signature buffer?

6 + lenR + lenS where lenS = s.length + 1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this is possible..

@dcousens
Copy link
Contributor

dcousens commented Apr 22, 2016

you do not want remove excessive leading zero byte on decode?

If the r value encoded is 0x01, I want 0x01 returned from decode. Not 0x0001.
If this change is merged, indeed, I'd want to remove the added zero byte.

@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

you do not want remove excessive leading zero byte on decode?

If the r value encoded is 0x01, I want 0x01 returned from decode. Not 0x0001.
If this change is merged, indeed, I'd want to remove the added zero byte.

ok, first 0x01 can be encoded only as 0x01, but
0x80 encoded as 0x0080 and current master return 0x0080
with this PR result will be 0x80

PR:

bip66.encode(new Buffer([1]), new Buffer([128]))
<Buffer 30 07 02 01 01 02 02 00 80>
bip66.decode(bip66.encode(new Buffer([1]), new Buffer([128])))
{ r: <Buffer 01>, s: <Buffer 80> }

master:

bip66.decode(bip66.encode(new Buffer([1]), new Buffer([128])))
Error: S value is negative
bip66.encode(new Buffer([1]), new Buffer([0, 128]))
<Buffer 30 07 02 01 01 02 02 00 80>
bip66.decode(bip66.encode(new Buffer([1]), new Buffer([0, 128])))
{ r: <Buffer 01>, s: <Buffer 00 80> }

@dcousens dcousens merged commit 79f06eb into master Apr 22, 2016
@dcousens dcousens deleted the feature/next branch April 22, 2016 07:51
@dcousens
Copy link
Contributor

Right. Then this was all my fault. Thanks @fanatid

@dcousens
Copy link
Contributor

dcousens commented Apr 22, 2016

I'll see if I can't clean up a few things, PR it, merge then release patch.

@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

Thanks, I'll create PR with tape/nyc in nearest time.

@dcousens
Copy link
Contributor

dcousens commented Apr 22, 2016

@fanatid so turns out this will probably break everything that depends on this package.
It appears everything was expecting the encoding/decoding of DER integers.

If we revert, we should really make that clear.

Thoughts on a way forward?

@dcousens
Copy link
Contributor

@fanatid basically we've "optimised" the package to return positive only integers, makes sense, but, the question is do we make this 2.0.0? Or do we revert back to DER two's complement integers which is currently the standard (ugh).

@fanatid
Copy link
Member Author

fanatid commented Apr 22, 2016

Yes, this PR require major version bump :(
with PR decode return positive/negative, before PR it could be only positive
IDK, maybe will better revert changes and let developer pass r/s as two's complement? (but we should write about this in readme!)

@dcousens
Copy link
Contributor

but we should write about this in readme!

Indeed.

fanatid added a commit that referenced this pull request Apr 25, 2016
dcousens added a commit that referenced this pull request Apr 26, 2016
Revert #2 and #4, update encode/tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants