Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Improve ECDSA Signing on iOS #22

Merged
merged 10 commits into from
Nov 21, 2018
Merged

Conversation

pelle
Copy link
Contributor

@pelle pelle commented Nov 17, 2018

I've rewritten and refactored the signing code.

There is a testing function in hubSaga.js that can be enabled by uncommenting line 300

This now works 100% of the time. At least I've tested it up to 10,000 signatures.

}


NSDictionary *genericSignature(BTCKey *keypair, NSData *hash, BOOL lowS) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@christianlundkvist @mirceanis This code occasionally creates an invalid signature. It's something like 1 in a 1000 (unscientifically).

Can you guys please look at the maths if there is something wrong I'm doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should just comment that the previous code was seemingly worse than this.

Copy link
Contributor

Choose a reason for hiding this comment

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

@pelle do you have an example of invalid signature?

#include <openssl/obj_mac.h>

static int BTCRegenerateKey(EC_KEY *eckey, BIGNUM *priv_key) {
BN_CTX *ctx = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest using 4-space indentation (like all other iOS code), not 2.

@"recoveryParam": @(recoveryParam)
};
}
return @{};
Copy link
Contributor

Choose a reason for hiding this comment

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

Redundant.

Just saving work. This still doesn't fix it
I've gone through and compared it with other implementations.

There still seems to be a few very occasional cases where it breaks.
@pelle pelle force-pushed the feature/#162028218/force-recovery-ios branch from b8389c0 to f1c240d Compare November 20, 2018 14:20
#include <openssl/obj_mac.h>

NSMutableData *compressedPublicKey(EC_KEY *key) {
if (!key) return nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please indent all changed/added code by 4 spaces.


NSDictionary *signatureDictionary = @{ @"v": @(base + [sig[@"recoveryParam"] intValue]),
@"r": [rData base64EncodedStringWithOptions:0],
@"s":[sData base64EncodedStringWithOptions:0]};
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Add spaces before & after the : and before the closing } =>

NSDictionary *signatureDictionary = @{ @"v" : @(base + [sig[@"recoveryParam"] intValue]),
                                       @"r" : [rData base64EncodedStringWithOptions:0],
                                       @"s" : [sData base64EncodedStringWithOptions:0] };

// K was infinity and does not work, redo it
if ([K isInfinity]) return NULL;
BTCMutableBigNumber* Kx = [K.x mutableCopy];
[Kx mod: n];
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space before n => [Kx mod:n];

BTCBigNumber* hashBN = [[BTCBigNumber alloc] initWithUnsignedBigEndian:hash];

// Compute s = (k^-1)*(h + Kx*privkey)

Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming above comment belongs to line below: Remove empty line.

@"r": rData,
@"s": sData,
@"recoveryParam": @(recoveryParam)
};
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Format as other literal =>

return @{ @"r" : rData,
          @"s" : sData,
          @"recoveryParam" : @(recoveryParam) };

Would also fit on one line, but it's nice/more-clear like this.


// Compute s = (k^-1)*(h + Kx*privkey)

BTCMutableBigNumber* signatureBN = [[[[privkeyBN mutableCopy] multiply:Kx mod:n] add:hashBN mod: n] multiply:[k inverseMod:n] mod:n];
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space between : and n => [[[[privkeyBN mutableCopy] multiply:Kx mod:n] add:hashBN mod:n] multiply:[k inverseMod:n] mod:n];

[K clear];
[Kx clear];

BTCBigNumber *twiceS = [[signatureBN mutableCopy] add: signatureBN];
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space after : => [[signatureBN mutableCopy] add:signatureBN];

BTCBigNumber *s;
if (lowS && BN_cmp(twiceS.BIGNUM, n.BIGNUM) > 0) {
// enforce low S values, by negating the value (modulo the order) if above order/2.
s = [[n mutableCopy] subtract: signatureBN]; // Maybe this should be swapped
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space after :

memset(sigBytes, 0, 64);

memcpy(sigBytes, rData.bytes, 32);
memcpy(sigBytes+32, sData.bytes, 32);
Copy link
Contributor

Choose a reason for hiding this comment

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

Add spaces around +

int n = 0;
int i = recid / 2;

const EC_GROUP *group = EC_KEY_get0_group(eckey);
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

In lines below, add spaces around ='s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll leave this bit as is, because it's copied verbatim from openssl source. So it's easier to update. Actually, while it's ugly I'll comment it out. It's not needed here, but may be needed by the sdk.

if (signature) {
result(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTError" code:UPTSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": @"signing failed due to invalid values"}];
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

I think it's good to keep lines shorter than 120 characters. Please break up this line, and use same dictionary literal format as commented on above =>

NSError *signingError;
signingError = [[NSError alloc] initWithDomain:@"UPTError" 
                                          code:UPTSignerErrorCodeLevelSigningFailed.integerValue
                                      userInfo:@{ @"message" : @"signing failed due to invalid values" }];

if (signature) {
result(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTError" code:UPTSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": @"signing failed due to invalid values"}];
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Break up in 4 lines, as above, to keep lines < 120 chars.

And again use same dictionary literal format => @{ @"message" : @"signing failed due to invalid values" }

callback(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTHDSignerError" code:UPTHDSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": [NSString stringWithFormat:@"signing failed due to invalid values for eth address: signJWT %@", rootAddress]}];
callback( nil, signingError);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space after (

@@ -0,0 +1,25 @@
// Copyright (C) 2018 ConsenSys AG
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Add file name to header, to stick with iOS/Xcode standard =>

//
//  EthereumSigner.h
//  uPort Mobile App
//
//  Copyright (C) 2018 ConsenSys AG

@@ -0,0 +1,25 @@
// Copyright (C) 2018 ConsenSys AG
//
// This file is part of uPort Mobile App.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be deleted because it's now mentioned above?

//
// This file is part of uPort Mobile App.
//
// uPort Mobile App is free software: you can redistribute it and/or modify
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it correct to call this uPort Mobile App, since it's not an app but SDK instead, and I'd expect iOS in the project/module name.

//
// This file is part of uPort Mobile App.
//
// uPort Mobile App is free software: you can redistribute it and/or modify
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be nice to indent all lines below as the standard Xcode header, so adding one space before each line.

@@ -0,0 +1,203 @@
// Copyright (C) 2018 ConsenSys AG
Copy link
Contributor

Choose a reason for hiding this comment

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

Update file header; see .h comment above.

#include <openssl/obj_mac.h>

NSMutableData *compressedPublicKey(EC_KEY *key) {
if (!key) return nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it's good practice to always have the if/else/while/... code blocks as a real block between { }. Placing code on the same line makes that they can be missed easily (because it's a second way to format these statements; best is to have as little ways to format code as possible making it easier to scan by a human).

return data;
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove line?

return signatureDictionary;
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove line?

if (!sig) return NULL;
NSData *rData = (NSData *)sig[@"r"];
NSData *sData = (NSData *)sig[@"s"];
///////
Copy link
Contributor

Choose a reason for hiding this comment

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

Delete


NSData *simpleSignature(BTCKey *keypair, NSData *hash) {
NSDictionary *sig = genericSignature(keypair, hash, NO);
if (!sig) return NULL;
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Although it's correct in C, what you're actually testing if sig is nil. So to keep close to that meaning, I think it's 'better' to write if (sig == nil) Meaning matters ;-)

int i = recid / 2;

const EC_GROUP *group = EC_KEY_get0_group(eckey);
if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; }
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

It may help remove some visual clutter here to use a good old CPP macro:

#define CHECK(condition, error) { if (!(condition)) { ret = (error); goto err; }

    CHECK(EC_GROUP_get_order(group, order, ctx)), -1)
    x = BN_CTX_get(ctx);
    CHECK(BN_copy(x, order)), -1)
    CHECK(BN_mul_word(x, i)), -1)

Just an idea eh; probably best to leave code as it is.

result(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTError" code:UPTSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": @"signing failed due to invalid values"}];
result( nil, signingError);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space before nil.

result(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTError" code:UPTSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": @"signing failed due to invalid values"}];
result( nil, signingError);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove space before nil.

if (signature) {
callback(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTHDSignerError" code:UPTHDSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": [NSString stringWithFormat:@"signing failed due to invalid values for eth address: signJWT %@", rootAddress]}];
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, please break up for line < 120

if (signature) {
callback(signature, nil);
} else {
NSError *signingError = [[NSError alloc] initWithDomain:@"UPTHDSignerError" code:UPTHDSignerErrorCodeLevelSigningFailed.integerValue userInfo:@{@"message": [NSString stringWithFormat:@"signing failed due to invalid values for eth address: signJWT %@", rootAddress]}];
Copy link
Contributor

Choose a reason for hiding this comment

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

Break up in multiple lines to keep line length below 120; possibly by introducing local variable(s).

Copy link
Contributor

@meaning-matters meaning-matters left a comment

Choose a reason for hiding this comment

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

All I could do (at the moment, without knowing yet how to run this) is comments about formatting. I'm glad you managed to get it working. I'm curious what changes fixed things.


NSDictionary *signatureDictionary = @{ @"v" : @(base + [sig[@"recoveryParam"] intValue]),
@"r" : [rData base64EncodedStringWithOptions:0],
@"s" :[sData base64EncodedStringWithOptions:0] };
Copy link
Contributor

@meaning-matters meaning-matters Nov 21, 2018

Choose a reason for hiding this comment

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

Space missing after : => @"s" : [sData base64EncodedStringWithOptions:0] };

Copy link
Contributor

@meaning-matters meaning-matters left a comment

Choose a reason for hiding this comment

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

Great!

(One space missing somewhere.)

@pelle pelle merged commit 08f4b1a into develop Nov 21, 2018
@mirceanis mirceanis deleted the feature/#162028218/force-recovery-ios branch January 22, 2020 11:10
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants