diff --git a/GMEllipticCurveCrypto.h b/GMEllipticCurveCrypto.h index 912462e..3e78a70 100644 --- a/GMEllipticCurveCrypto.h +++ b/GMEllipticCurveCrypto.h @@ -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; diff --git a/GMEllipticCurveCrypto.m b/GMEllipticCurveCrypto.m index 1f1fbb2..1439752 100644 --- a/GMEllipticCurveCrypto.m +++ b/GMEllipticCurveCrypto.m @@ -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 @@ -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; } @@ -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; @@ -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]; } @@ -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; } @@ -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; } diff --git a/test.m b/test.m index c5cf7a3..33ea2d5 100644 --- a/test.m +++ b/test.m @@ -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; @@ -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=="; @@ -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"; @@ -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="; @@ -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"; @@ -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