Skip to content

Commit

Permalink
Added full (setting, getting, detecting and converting) uncompressed …
Browse files Browse the repository at this point in the history
…public key support.
  • Loading branch information
ricmoo committed Dec 12, 2014
1 parent f387bea commit c09a973
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 19 deletions.
17 changes: 14 additions & 3 deletions GMEllipticCurveCrypto.h
Expand Up @@ -100,18 +100,29 @@ typedef enum GMEllipticCurve {
@property (nonatomic, readonly) NSString *name;

/**
* The public key for an elliptic curve is provided as a compressed point.
* Determines whether the public key will be compressed or uncompressed.
*
* It is updated when a public key is assigned and can be changed anytime
* to select what the publicKey property emits.
*
* A compressed point stores only the x co-ordinate of the point as well as
* a leading byte to indicate the parity of the y co-ordinate, which can then
* be computed from x.
*
* A public key's length is ((curve_bits / 8) + 1) bytes.
* By default, keys are compressed.
*/
@property (nonatomic, assign) BOOL compressedPublicKey;

/**
* The public key for an elliptic curve.
*
* A compressed public key's length is ((curve_bits / 8) + 1) bytes.
* An uncompressed public key's length is (2 * (curve_bits / 8) + 1) bytes.
*/
@property (nonatomic, strong) NSData *publicKey;

/**
* The public key (compressed point) encoded in base64
* The public key encoded in base64
*/
@property (nonatomic, strong) NSString *publicKeyBase64;

Expand Down
152 changes: 137 additions & 15 deletions GMEllipticCurveCrypto.m
Expand Up @@ -1354,6 +1354,7 @@ int ecdsa_verify(const uint8_t *p_publicKey, const uint8_t *p_hash, const uint8_
@interface GMEllipticCurveCrypto () {
int _bytes, _numDigits;
uint64_t *_curve_p, *_curve_b, *_curve_Gx, *_curve_Gy, *_curve_n;
NSData *_publicKey;
}

@end
Expand All @@ -1369,18 +1370,42 @@ + (GMEllipticCurveCrypto*)generateKeyPairForCurve:(GMEllipticCurve)curve {

}


+ (GMEllipticCurve)curveForKey:(NSData *)privateOrPublicKey {
switch ([privateOrPublicKey length]) {
case 16: case 17:

NSInteger length = [privateOrPublicKey length];

// We need at least 1 byte
if (length == 0) {
return GMEllipticCurveNone;
}

const uint8_t *bytes = [privateOrPublicKey bytes];

// Odd-length, therefore a public key
if (length % 2) {
switch (bytes[0]) {
case 0x04:
length = (length - 1) / 2;
break;
case 0x02: case 0x03:
length--;
break;
default:
return GMEllipticCurveNone;
}
}

switch (length) {
case 16:
return GMEllipticCurveSecp128r1;
case 24: case 25:
case 24:
return GMEllipticCurveSecp192r1;
case 32: case 33:
case 32:
return GMEllipticCurveSecp256r1;
case 48: case 49:
case 48:
return GMEllipticCurveSecp384r1;
}

return GMEllipticCurveNone;
}

Expand Down Expand Up @@ -1413,6 +1438,8 @@ + (id)cryptoForCurve:(GMEllipticCurve)curve {
- (id)initWithCurve:(GMEllipticCurve)curve {
self = [super init];
if (self) {
_compressedPublicKey = YES;

_bits = curve;
_bytes = _bits / 8;
_numDigits = _bytes / 8;
Expand Down Expand Up @@ -1606,6 +1633,89 @@ - (NSData*)publicKeyForPrivateKey: (NSData*)privateKey {
return [NSData dataWithBytes:l_public length:_bytes + 1];
}

- (NSData*)compressPublicKey: (NSData*)publicKey {

NSInteger length = [publicKey length];

if (length == 0) {
return nil;
}

const uint8_t *bytes = [publicKey bytes];

switch (bytes[0]) {

// Already compressed
case 0x02: case 0x03:
if (length != (1 + _bytes)) {
return nil;
}

return publicKey;

// Compress!
case 0x04: {
if (length != (1 + 2 * _bytes)) {
return nil;
}

// Get the (x, y) point from the public key
uint64_t l_publicX[_numDigits], l_publicY[_numDigits];
ecc_bytes2native(l_publicX, &bytes[1], _numDigits);
ecc_bytes2native(l_publicY, &bytes[1 + _bytes], _numDigits);

// And compress
uint8_t l_public[_bytes + 1];
ecc_native2bytes(l_public + 1, l_publicX, _numDigits);
l_public[0] = 2 + (l_publicY[0] & 0x01);

return [NSData dataWithBytes:l_public length:_bytes + 1];
}
}

return nil;
}

- (NSData*)decompressPublicKey: (NSData*)publicKey {
NSInteger length = [publicKey length];

if (length == 0) {
return nil;
}

const uint8_t *bytes = [publicKey bytes];

switch (bytes[0]) {

// Already uncompressed
case 0x04:
if (length != (1 + 2 * _bytes)) {
return nil;
}
return publicKey;

case 0x02: case 0x03: {
if (length != (1 + _bytes)) {
return nil;
}

// Decompress to get the (x, y) point
uint64_t l_publicX[_numDigits], l_publicY[_numDigits];
ecc_point_decompress(l_publicX, l_publicY, [_publicKey bytes], _numDigits, _curve_p, _curve_b);

// Compose the public key (0x04 + x + y)
uint8_t l_public[2 * _bytes + 1];
l_public[0] = 0x04;
ecc_native2bytes(l_public + 1, l_publicX, _numDigits);
ecc_native2bytes(l_public + 1 + _bytes, l_publicY, _numDigits);

return [NSData dataWithBytes:l_public length:2 * _bytes + 1];
}
}

return nil;
}

- (NSString*)privateKeyBase64 {
return [_privateKey base64EncodedStringWithOptions:0];
}
Expand All @@ -1618,13 +1728,12 @@ - (void)setPrivateKey: (NSData*)privateKey {
}

NSData *checkPublicKey = [self publicKeyForPrivateKey:privateKey];
NSLog(@"FOO: %@ %@ %@", checkPublicKey, _publicKey, privateKey);
if (_publicKey && ![_publicKey isEqual:checkPublicKey]) {
[NSException raise:@"Key mismatch" format:@"Private key %@ does not match public key %@", privateKey, _publicKey];
}

_privateKey = privateKey;
_publicKey = checkPublicKey;
_privateKey = privateKey;
}


Expand All @@ -1634,22 +1743,35 @@ - (void)setPrivateKeyBase64:(NSString *)privateKeyBase64 {


- (NSString*)publicKeyBase64 {
return [_publicKey base64EncodedStringWithOptions:0];
return [self.publicKey base64EncodedStringWithOptions:0];
}


- (NSData*)publicKey {
if (_compressedPublicKey) {
return _publicKey;
}
return [self decompressPublicKey:_publicKey];
}

- (void)setPublicKey: (NSData*)publicKey {
int keyBits = [GMEllipticCurveCrypto curveForKey:publicKey];
if (keyBits != _bits) {
[NSException raise:@"Invalid Key" format:@"Public key %@ is %d bits; curve is %d bits", publicKey, keyBits, _bits];
[NSException raise:@"Invalid Key" format:@"Public key %@ is %d bits; curve is %d bits", publicKey, keyBits, _bits];
}

if (_privateKey) {
NSData *checkPublicKey = [self publicKeyForPrivateKey:_privateKey];
if (![publicKey isEqual:checkPublicKey]) {
[NSException raise:@"Key mismatch" format:@"Private key %@ does not match public key %@", _privateKey, publicKey];
}
const uint8_t *bytes = [publicKey bytes];
BOOL compressedPublicKey = (bytes[0] != (uint8_t)0x04);

// Ensure the key is compressed (we only store compressed keys internally)
publicKey = [self compressPublicKey:publicKey];

// If the private key has already been set, and it doesn't match, complain
if (_privateKey && ![publicKey isEqual:_publicKey]) {
[NSException raise:@"Key mismatch" format:@"Private key %@ does not match public key %@", _privateKey, publicKey];
}

_compressedPublicKey = compressedPublicKey;
_publicKey = publicKey;
}

Expand Down
30 changes: 29 additions & 1 deletion test.m
Expand Up @@ -22,7 +22,7 @@ int main(int argc, const char* argv[]) {
// Select the curve to test and some known correct test data
GMEllipticCurve curve = GMEllipticCurveNone;
NSString *curveName = nil;
NSString *alicePublicKey, *alicePrivateKey, *bobPublicKey, *bobPrivateKey;
NSString *alicePublicKey, *aliceUncompressedPublicKey, *alicePrivateKey, *bobPublicKey, *bobPrivateKey;

// Known correct signatures
char *testSignatureBytes;
Expand All @@ -38,6 +38,7 @@ int main(int argc, const char* argv[]) {
curveName = @"GMEllipticCurveSecp128r1";

alicePublicKey = @"A/DSn7VEbavr9BNXdkc9YaM=";
aliceUncompressedPublicKey = @"BPDSn7VEbavr9BNXdkc9YaO5G8Z+WQrkPDsfAuEMN2Bj";
alicePrivateKey = @"LRtdqpOXEmdZuG+kHdl7iw==";
bobPublicKey = @"Atogpx1pGlak5vVpOP6pwYw=";
bobPrivateKey = @"B3rloJxb2h1uX+Cin/0Big==";
Expand All @@ -51,6 +52,7 @@ int main(int argc, const char* argv[]) {
curveName = @"GMEllipticCurveSecp192r1";

alicePublicKey = @"AjKrRgg7c5t3JduLDuLL3n9Eap/JHo+32g==";
aliceUncompressedPublicKey = @"BDKrRgg7c5t3JduLDuLL3n9Eap/JHo+32i0q7Dr9+yeILhMirgHnqQIRAKC2j1gBqA==";
alicePrivateKey = @"bOr7KLf1mrqh3ZW1Zmu2rTwmv8GtjzOs";
bobPublicKey = @"A5WjHAPDpwdMn0CuCqW03gksQ29nO9OuTw==";
bobPrivateKey = @"3zA52Irsxvm549QMeHaJ1K6arJz4XGrn";
Expand All @@ -64,6 +66,7 @@ int main(int argc, const char* argv[]) {
curveName = @"GMEllipticCurveSecp256r1";

alicePublicKey = @"Aq4Qk0tQwU3zSfStH0ZTMKzC6ZfF3PBEqoGLWwJMYQVz";
aliceUncompressedPublicKey = @"BK4Qk0tQwU3zSfStH0ZTMKzC6ZfF3PBEqoGLWwJMYQVzvncvr8fv+S6POJ96oLZn0l4YS/OpqB19Of+l1qxwO9Q=";
alicePrivateKey = @"UhCeQEsqYcby7UfjKWLxGePlag/RUTIAwYypF0K3ERU=";
bobPublicKey = @"ApneAMFPGDIS6bXR7qOca7huHsiQD5grT1X+CBB1UX82";
bobPrivateKey = @"7mKV1nAZ6m61UMuGAeJ+eBYuAY4jfrnDEOf9K1aQixk=";
Expand All @@ -77,6 +80,7 @@ int main(int argc, const char* argv[]) {
curveName = @"GMEllipticCurveSecp384r1";

alicePublicKey = @"Amp3NAD5A3Eyg2TB5xF6GnKKFN1mreXkEGBNW+BO9jCx/rPgho7cwgTqZOE950TDeg==";
aliceUncompressedPublicKey = @"BGp3NAD5A3Eyg2TB5xF6GnKKFN1mreXkEGBNW+BO9jCx/rPgho7cwgTqZOE950TDemKqgjvi5Ozs6CHq/+1VLBm9lt+XKRfeeUJpoSxQmINYv1yFcBmWHPeNU2b2dGW2IA==";
alicePrivateKey = @"238XF1UdIU+UATgDvhlhYmTZKTrdtCXEkfSGscowTCHNjRWY7QPOJxW1CzpxJcqM";
bobPublicKey = @"AnpyZJlvppMp9JazUxONY83RIsn+sv/XqWPCcfRb1XJYYoUYGhU/udPhvwjJvkDnLA==";
bobPrivateKey = @"42ly4+r5t9cmUHxN6mK0cM2IlsNZePumSO/1G4W8UOYsnExiUoIAM33gkPBZGXcJ";
Expand Down Expand Up @@ -115,6 +119,30 @@ int main(int argc, const char* argv[]) {



NSLog(@" Test ECC decompressing public keys");

// Test with Alice's keys
crypto = [GMEllipticCurveCrypto cryptoForKeyBase64:alicePublicKey];
NSCAssert(crypto.compressedPublicKey, @"compressed key was not identified as compressed");
NSLog(@" Compressed: %@", crypto.publicKeyBase64);
crypto.compressedPublicKey = NO;
NSLog(@" Uncompressed: %@", crypto.publicKeyBase64);
NSCAssert([crypto.publicKeyBase64 isEqual:aliceUncompressedPublicKey], @"compressed key was not correctly decompressed");



NSLog(@" Test ECC compressing public keys");

// Test with Alice's keys
crypto = [GMEllipticCurveCrypto cryptoForKeyBase64:aliceUncompressedPublicKey];
NSCAssert(!crypto.compressedPublicKey, @"decompressed key was not identified as decompressed");
NSLog(@" Decompressed: %@", crypto.publicKeyBase64);
crypto.compressedPublicKey = YES;
NSLog(@" Compressed: %@", crypto.publicKeyBase64);
NSCAssert([crypto.publicKeyBase64 isEqual:alicePublicKey], @"uncompressed key was not correctly compressed");



NSLog(@" Test ECDSA signature and verification for known keys");

// Test with Alice's keys
Expand Down

0 comments on commit c09a973

Please sign in to comment.