Skip to content

Commit

Permalink
Merge pull request #4 from Sajjon/expose_ec_methods
Browse files Browse the repository at this point in the history
Expose Decode Point from Public Key method
  • Loading branch information
Sajjon authored Mar 22, 2019
2 parents b5dc019 + bc0e367 commit c068926
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 28 deletions.
4 changes: 4 additions & 0 deletions BitcoinKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
CFA290742102B650001A1BAB /* ScriptMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA290732102B650001A1BAB /* ScriptMachineTests.swift */; };
E6A64306224418EB00CD4BFC /* PointOnCurve.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A64305224418EB00CD4BFC /* PointOnCurve.swift */; };
E6A643082244190400CD4BFC /* Scalar32Bytes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A643072244190400CD4BFC /* Scalar32Bytes.swift */; };
E6A643102244EA3B00CD4BFC /* DecodePointFromCompressedKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A6430F2244EA3B00CD4BFC /* DecodePointFromCompressedKeyTests.swift */; };
E6C9FB9D2243FF7A000AAE12 /* PointMultiplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C9FB9C2243FF7A000AAE12 /* PointMultiplicationTests.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -448,6 +449,7 @@
CFA290732102B650001A1BAB /* ScriptMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptMachineTests.swift; sourceTree = "<group>"; };
E6A64305224418EB00CD4BFC /* PointOnCurve.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOnCurve.swift; sourceTree = "<group>"; };
E6A643072244190400CD4BFC /* Scalar32Bytes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scalar32Bytes.swift; sourceTree = "<group>"; };
E6A6430F2244EA3B00CD4BFC /* DecodePointFromCompressedKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodePointFromCompressedKeyTests.swift; sourceTree = "<group>"; };
E6C9FB9C2243FF7A000AAE12 /* PointMultiplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointMultiplicationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -573,6 +575,7 @@
6EE789DA2112C1E500EAB620 /* CryptoTests.swift */,
6E20AEC62112C290008A9810 /* PrivateKeyTests.swift */,
E6C9FB9C2243FF7A000AAE12 /* PointMultiplicationTests.swift */,
E6A6430F2244EA3B00CD4BFC /* DecodePointFromCompressedKeyTests.swift */,
6E20AEC82112C31A008A9810 /* LegacyAddressTests.swift */,
6E20AECA2112C434008A9810 /* HDPrivateKeyTests.swift */,
6E20AECC2112C559008A9810 /* CashAddrTests.swift */,
Expand Down Expand Up @@ -1233,6 +1236,7 @@
297C408121100810003AF4EF /* MnemonicTests.swift in Sources */,
6EE789DB2112C1E500EAB620 /* CryptoTests.swift in Sources */,
6E20AEC72112C290008A9810 /* PrivateKeyTests.swift in Sources */,
E6A643102244EA3B00CD4BFC /* DecodePointFromCompressedKeyTests.swift in Sources */,
CF432AF620F0ED4500AD4020 /* AddressTests.swift in Sources */,
6E20AED32112C7DA008A9810 /* TransactionTests.swift in Sources */,
6E20AED72112D417008A9810 /* MurmurHashTests.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions BitcoinKit/BitcoinKitPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface _EllipticCurve : NSObject
+ (NSData *)multiplyECPointX:(NSData *)ecPointX andECPointY:(NSData *)ecPointY withScalar:(NSData *)scalar;
+ (NSData *)decodePointOnCurveForCompressedPublicKey:(NSData *)publicKeyCompressed;
@end

@interface _HDKey : NSObject
Expand Down
25 changes: 23 additions & 2 deletions BitcoinKit/BitcoinKitPrivate.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ + (NSData *)hmacsha512:(NSData *)data key:(NSData *)key {
@implementation _EllipticCurve
+ (NSData *)multiplyECPointX:(NSData *)ecPointX andECPointY:(NSData *)ecPointY withScalar:(NSData *)scalar {
BN_CTX *ctx = BN_CTX_new();
const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1);
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1);

BIGNUM *multiplication_factor = BN_new();
BN_bin2bn(scalar.bytes, (int)scalar.length, multiplication_factor);
Expand Down Expand Up @@ -100,10 +100,31 @@ + (NSData *)multiplyECPointX:(NSData *)ecPointX andECPointY:(NSData *)ecPointY w
BN_free(point_x);
BN_free(point_y);
BN_CTX_free(ctx);
EC_GROUP_free(group);

return [newPointXAndYPrefixedWithByte subdataWithRange:NSMakeRange(1, 64)];
return newPointXAndYPrefixedWithByte;
}

+ (NSData *)decodePointOnCurveForCompressedPublicKey:(NSData *)publicKeyCompressed {
BN_CTX *ctx = BN_CTX_new();
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1);
EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_COMPRESSED);
EC_POINT *point = EC_POINT_new(group);
EC_POINT_oct2point(group, point, publicKeyCompressed.bytes, (int)publicKeyCompressed.length, ctx);

NSMutableData *newPointXAndYPrefixedWithByte = [NSMutableData dataWithLength:65];
BIGNUM *new_point_x_and_y_as_single_bn = BN_new();
EC_POINT_point2bn(group, point, POINT_CONVERSION_UNCOMPRESSED, new_point_x_and_y_as_single_bn, ctx);
BN_bn2bin(new_point_x_and_y_as_single_bn, newPointXAndYPrefixedWithByte.mutableBytes);

BN_free(new_point_x_and_y_as_single_bn);
EC_POINT_free(point);
BN_CTX_free(ctx);
EC_GROUP_free(group);
return newPointXAndYPrefixedWithByte;
}


@end

@implementation _Key
Expand Down
46 changes: 30 additions & 16 deletions Sources/BitcoinKit/Core/PointOnCurve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,59 @@ import Foundation

public struct PointOnCurve {

private static let byteForUncompressed: UInt8 = 0x04
public let x: Scalar32Bytes
public let y: Scalar32Bytes

public init(x: Scalar32Bytes, y: Scalar32Bytes) {
self.x = x
self.y = y
}

init(x xData: Data, y yData: Data) throws {
let x = try Scalar32Bytes(data: xData)
let y = try Scalar32Bytes(data: yData)
self.init(x: x, y: y)
}
}

#if BitcoinKitXcode
public extension PointOnCurve {

#if BitcoinKitXcode
public enum Error: Swift.Error {
case multiplicationResultedInTooFewBytes(expected: Int, butGot: Int)
case expectedUncompressedPoint
case publicKeyContainsTooFewBytes(expected: Int, butGot: Int)
}
#else
public enum Error: Swift.Error {
case pointMultiplicationNotSuported
}
#endif

init(x xData: Data, y yData: Data) throws {
let x = try Scalar32Bytes(data: xData)
let y = try Scalar32Bytes(data: yData)
self.init(x: x, y: y)
static func decodePointFromPublicKey(_ publicKey: PublicKey) throws -> PointOnCurve {
let data: Data
if publicKey.isCompressed {
data = _EllipticCurve.decodePointOnCurve(forCompressedPublicKey: publicKey.data)
} else {
data = publicKey.data
}
return try PointOnCurve.decodePointFrom(xAndYPrefixedWithCompressionType: data)
}

func multiplyBy(scalar: Scalar32Bytes) throws -> PointOnCurve {
#if BitcoinKitXcode
let xAndY = _EllipticCurve.multiplyECPointX(x.data, andECPointY: y.data, withScalar: scalar.data)
private static func decodePointFrom(xAndYPrefixedWithCompressionType data: Data) throws -> PointOnCurve {
var xAndY = data
guard xAndY[0] == PointOnCurve.byteForUncompressed else {
throw Error.expectedUncompressedPoint
}
xAndY = Data(xAndY.dropFirst())
let expectedByteCount = Scalar32Bytes.expectedByteCount * 2
guard xAndY.count == expectedByteCount else {
throw Error.multiplicationResultedInTooFewBytes(expected: expectedByteCount, butGot: xAndY.count)
}
let resultX = xAndY.prefix(Scalar32Bytes.expectedByteCount)
let resultY = xAndY.suffix(Scalar32Bytes.expectedByteCount)
return try PointOnCurve(x: resultX, y: resultY)
#else
throw Error.pointMultiplicationNotSuported
#endif
}

func multiplyBy(scalar: Scalar32Bytes) throws -> PointOnCurve {
let xAndY = _EllipticCurve.multiplyECPointX(x.data, andECPointY: y.data, withScalar: scalar.data)
return try PointOnCurve.decodePointFrom(xAndYPrefixedWithCompressionType: xAndY)
}

func multiplyBy(privateKey: PrivateKey) throws -> PointOnCurve {
Expand All @@ -61,3 +74,4 @@ public extension PointOnCurve {
return try multiplyBy(scalar: scalar)
}
}
#endif
4 changes: 0 additions & 4 deletions Sources/BitcoinKit/Core/Scalar32Bytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ import Foundation

public struct Scalar32Bytes {
public enum Error: Swift.Error {
case tooFewBytes(expectedCount: Int, butGot: Int)
case tooManyBytes(expectedCount: Int, butGot: Int)
}
public static let expectedByteCount = 32
public let data: Data
public init(data: Data) throws {
let byteCount = data.count
if byteCount < Scalar32Bytes.expectedByteCount {
throw Error.tooFewBytes(expectedCount: Scalar32Bytes.expectedByteCount, butGot: byteCount)
}
if byteCount > Scalar32Bytes.expectedByteCount {
throw Error.tooManyBytes(expectedCount: Scalar32Bytes.expectedByteCount, butGot: byteCount)
}
Expand Down
40 changes: 40 additions & 0 deletions Tests/BitcoinKitTests/DecodePointFromCompressedKeyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// DecodePointFromCompressedKeyTests.swift
// BitcoinKitTests
//
// Created by Alexander Cyon on 2019-03-22.
// Copyright © 2019 BitcoinKit developers. All rights reserved.
//

#if BitcoinKitXcode
import Foundation
@testable import BitcoinKit
import XCTest

class DecodePointTests: XCTestCase {
func testPointDecoding() {
do {
let wifUncompressed = "5K6EwEiKWKNnWGYwbNtrXjA8KKNntvxNKvepNqNeeLpfW7FSG1v"
let wifCompresssed = "L2r8WPXNgQ79rBdyxjdscd5HHr7BaD9P8Xov7NWZ9pVNw12TFSDZ"
let privateKeyFromUncompressed = try PrivateKey(wif: wifUncompressed)
let publicKeyUncompressed = privateKeyFromUncompressed.publicKey()
XCTAssertFalse(publicKeyUncompressed.isCompressed)

let decodedFromUncompressed: PointOnCurve = try PointOnCurve.decodePointFromPublicKey(publicKeyUncompressed)
let expectedY = "ccfca71eff2101ad68238112e7585110e0f2c32d345225985356dc7cab8fdcc9"
XCTAssertEqual(decodedFromUncompressed.y.data.hex, expectedY)

let privateKeyFromCompressed = try PrivateKey(wif: wifCompresssed)
let publicKeyCompressed = privateKeyFromCompressed.publicKey()
XCTAssertTrue(publicKeyCompressed.isCompressed)

let decodedFromCompressed: PointOnCurve = try PointOnCurve.decodePointFromPublicKey(publicKeyCompressed)
XCTAssertEqual(decodedFromCompressed.y.data.hex, expectedY)
XCTAssertEqual(decodedFromCompressed.y.data.hex, decodedFromUncompressed.y.data.hex)

} catch {
XCTFail("Error: \(error)")
}
}
}
#endif
9 changes: 3 additions & 6 deletions Tests/BitcoinKitTests/PointMultiplicationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// Copyright © 2019 BitcoinKit developers. All rights reserved.
//

#if BitcoinKitXcode
import Foundation

import XCTest
@testable import BitcoinKit

Expand Down Expand Up @@ -69,12 +69,9 @@ class PointMultiplicationTests: XCTestCase {
XCTAssertEqual(pointOrderPlus1.x.data.hex, pointOne.x.data.hex)
XCTAssertEqual(pointOrderPlus1.y.data.hex, pointOne.y.data.hex)
} catch {
#if BitcoinKitXcode
XCTFail("error: \(error)")
#else
XCTAssertTrue(true, "Cannot do point multiplication if `BitcoinKitXcode` is not defined")
#endif
}
}

}
#endif

0 comments on commit c068926

Please sign in to comment.