Skip to content

Commit

Permalink
Merge pull request #219 from yenom/Sajjon/expose-ec-methods
Browse files Browse the repository at this point in the history
Adding EC point multiplication method
  • Loading branch information
usatie authored Sep 12, 2019
2 parents 56992b4 + e006212 commit 739aaa2
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 2 deletions.
16 changes: 16 additions & 0 deletions BitcoinKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@
CFA290702101CDCA001A1BAB /* ScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA2906F2101CDCA001A1BAB /* ScriptTests.swift */; };
CFA290722102B635001A1BAB /* ScriptMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA290712102B635001A1BAB /* ScriptMachine.swift */; };
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 */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -473,6 +477,10 @@
CFA2906F2101CDCA001A1BAB /* ScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptTests.swift; sourceTree = "<group>"; };
CFA290712102B635001A1BAB /* ScriptMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptMachine.swift; sourceTree = "<group>"; };
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 */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -599,6 +607,8 @@
147494D7201F9A29006D1CF8 /* Info.plist */,
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 @@ -665,6 +675,8 @@
147494EB201F9E4F006D1CF8 /* Network.swift */,
1463E6B52025E99C0033DAAE /* BlockChain.swift */,
1463E6B32025E9480033DAAE /* BlockStore.swift */,
E6A64305224418EB00CD4BFC /* PointOnCurve.swift */,
E6A643072244190400CD4BFC /* Scalar32Bytes.swift */,
1482B5E12026B0680098B612 /* Mnemonic.swift */,
1482B5E32026F2580098B612 /* WordList.swift */,
14839A7B202F79F900A6CB34 /* PaymentURI.swift */,
Expand Down Expand Up @@ -1087,6 +1099,7 @@
1482B5E42026F2590098B612 /* WordList.swift in Sources */,
14839A93202FE6AC00A6CB34 /* FilterLoadMessage.swift in Sources */,
14839A9D202FE72600A6CB34 /* TransactionOutPoint.swift in Sources */,
E6A643082244190400CD4BFC /* Scalar32Bytes.swift in Sources */,
2933014D214FCE150028946B /* TransactionHistoryProvider.swift in Sources */,
0C1DE155211D787100FE8E43 /* OP_TOTALSTACK.swift in Sources */,
2949920220F228B400D078B6 /* UnspentTransaction.swift in Sources */,
Expand Down Expand Up @@ -1150,6 +1163,7 @@
0C1DD41021181AF3004BA8A8 /* OP_NUMEQUAL.swift in Sources */,
A2613884211E8E12003A3B29 /* UInt256+Bitcoin.swift in Sources */,
14839AA9202FE7DD00A6CB34 /* VarString.swift in Sources */,
E6A64306224418EB00CD4BFC /* PointOnCurve.swift in Sources */,
14F37A3E2020A02000D34748 /* SighashType.swift in Sources */,
A2F06FBA2126785200DFF652 /* UInt32+Utility.swift in Sources */,
14839A8F202FE68000A6CB34 /* PongMessage.swift in Sources */,
Expand Down Expand Up @@ -1278,12 +1292,14 @@
147494D6201F9A29006D1CF8 /* BitcoinKitTests.swift in Sources */,
A2DE5110212AFCF200F54EA0 /* UInt256+BitcoinTests.swift in Sources */,
CFA290742102B650001A1BAB /* ScriptMachineTests.swift in Sources */,
E6C9FB9D2243FF7A000AAE12 /* PointMultiplicationTests.swift in Sources */,
6E20AECB2112C434008A9810 /* HDPrivateKeyTests.swift in Sources */,
6E20AED52112C8F9008A9810 /* BloomFilterTests.swift in Sources */,
CF432AFA20F222B400AD4020 /* AddressFactoryTests.swift in Sources */,
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
6 changes: 5 additions & 1 deletion BitcoinKit/BitcoinKitPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ NS_ASSUME_NONNULL_BEGIN
@end

@interface _Key : NSObject

+ (NSData *)deriveKey:(NSData *)password salt:(NSData *)salt iterations:(NSInteger)iterations keyLength:(NSInteger)keyLength;

@end

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

@interface _Crypto : NSObject
+ (NSData *)signMessage:(NSData *)message withPrivateKey:(NSData *)privateKey;
+ (BOOL)verifySignature:(NSData *)signature message:(NSData *)message publicKey:(NSData *)publicKey;
Expand Down
59 changes: 59 additions & 0 deletions BitcoinKit/BitcoinKitPrivate.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,65 @@ + (NSData *)hmacsha512:(NSData *)data key:(NSData *)key {
return result;
}

@end

@implementation _EllipticCurve
+ (NSData *)multiplyECPointX:(NSData *)ecPointX andECPointY:(NSData *)ecPointY withScalar:(NSData *)scalar {
BN_CTX *ctx = BN_CTX_new();
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);

BIGNUM *point_x = BN_new();
BN_bin2bn(ecPointX.bytes, (int)ecPointX.length, point_x);

BIGNUM *point_y = BN_new();
BN_bin2bn(ecPointY.bytes, (int)ecPointY.length, point_y);

EC_POINT *point = EC_POINT_new(group);
EC_POINT_set_affine_coordinates_GFp(group, point, point_x, point_y, ctx);

EC_POINT *point_result_of_ec_multiplication = EC_POINT_new(group);
EC_POINT_mul(group, point_result_of_ec_multiplication, nil, point, multiplication_factor, ctx);

NSMutableData *newPointXAndYPrefixedWithByte = [NSMutableData dataWithLength:65];
BIGNUM *new_point_x_and_y_as_single_bn = BN_new();
EC_POINT_point2bn(group, point_result_of_ec_multiplication, 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_result_of_ec_multiplication);
EC_POINT_free(point);
BN_free(multiplication_factor);
BN_free(point_x);
BN_free(point_y);
BN_CTX_free(ctx);
EC_GROUP_free(group);

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
13 changes: 12 additions & 1 deletion Sources/BitcoinKit/Core/Keys/PrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public struct PrivateKey {
public let network: Network
public let isPublicKeyCompressed: Bool

// QUESTION: これランダムに生成する場合かな?
public init(network: Network = .testnet, isPublicKeyCompressed: Bool = true) {
self.network = network
self.isPublicKeyCompressed = isPublicKeyCompressed
Expand Down Expand Up @@ -123,6 +122,18 @@ public struct PrivateKey {
return _SwiftKey.computePublicKey(fromPrivateKey: data, compression: isPublicKeyCompressed)
}

public func publicKeyPoint() throws -> PointOnCurve {
let xAndY: Data = _SwiftKey.computePublicKey(fromPrivateKey: data, compression: false)
let expectedLengthOfScalar = Scalar32Bytes.expectedByteCount
let expectedLengthOfKey = expectedLengthOfScalar * 2
guard xAndY.count == expectedLengthOfKey else {
fatalError("expected length of key is \(expectedLengthOfKey) bytes, but got: \(xAndY.count)")
}
let x = xAndY.prefix(expectedLengthOfScalar)
let y = xAndY.suffix(expectedLengthOfScalar)
return try PointOnCurve(x: x, y: y)
}

public func publicKey() -> PublicKey {
return PublicKey(bytes: computePublicKeyData(), network: network)
}
Expand Down
79 changes: 79 additions & 0 deletions Sources/BitcoinKit/Core/PointOnCurve.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// PointOnCurve.swift
// BitcoinKit
//
// Created by Alexander Cyon on 2019-03-21.
// Copyright © 2019 BitcoinKit developers. All rights reserved.
//

import Foundation
#if BitcoinKitXcode
import BitcoinKit.Private
#else
import BitcoinKitPrivate
#endif

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)
}
}

public extension PointOnCurve {
enum Error: Swift.Error {
case multiplicationResultedInTooFewBytes(expected: Int, butGot: Int)
case expectedUncompressedPoint
case publicKeyContainsTooFewBytes(expected: Int, butGot: Int)
}

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)
}

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)
}

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 {
return try multiplyBy(scalar: privateKey.data)
}

func multiplyBy(scalar scalarData: Data) throws -> PointOnCurve {
let scalar = try Scalar32Bytes(data: scalarData)
return try multiplyBy(scalar: scalar)
}
}
24 changes: 24 additions & 0 deletions Sources/BitcoinKit/Core/Scalar32Bytes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Scalar32Bytes.swift
// BitcoinKit
//
// Created by Alexander Cyon on 2019-03-21.
// Copyright © 2019 BitcoinKit developers. All rights reserved.
//

import Foundation

public struct Scalar32Bytes {
public enum Error: Swift.Error {
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.tooManyBytes(expectedCount: Scalar32Bytes.expectedByteCount, butGot: byteCount)
}
self.data = data
}
}
74 changes: 74 additions & 0 deletions Sources/BitcoinKitPrivate/BitcoinKit.Private.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,77 @@ public class _Crypto {
case publicKeyParseFailed
}
}

public class _EllipticCurve {
public static func multiplyECPointX(_ ecPointX: Data, andECPointY ecPointY: Data, withScalar scalar: Data) -> Data {
let ctx = BN_CTX_new()
defer { BN_CTX_free(ctx) }
let group = EC_GROUP_new_by_curve_name(NID_secp256k1)
defer { EC_GROUP_free(group) }

let multiplication_factor = BN_new()
defer { BN_free(multiplication_factor) }
scalar.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
BN_bin2bn(ptr, Int32(scalar.count), multiplication_factor)
return
}

let point_x = BN_new()
defer { BN_free(point_x) }
ecPointX.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
BN_bin2bn(ptr, Int32(ecPointX.count), point_x)
return
}

let point_y = BN_new();
defer { BN_free(point_y) }
ecPointY.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
BN_bin2bn(ptr, Int32(ecPointY.count), point_y)
return
}

let point = EC_POINT_new(group);
defer { EC_POINT_free(point) }
EC_POINT_set_affine_coordinates_GFp(group, point, point_x, point_y, ctx)

let point_result_of_ec_multiplication = EC_POINT_new(group)
defer { EC_POINT_free(point_result_of_ec_multiplication) }
EC_POINT_mul(group, point_result_of_ec_multiplication, nil, point, multiplication_factor, ctx)

var newPointXAndYPrefixedWithByte = [UInt8](repeating: 0, count: 65)
let new_point_x_and_y_as_single_bn = BN_new()
defer { BN_free(new_point_x_and_y_as_single_bn) }

EC_POINT_point2bn(group, point_result_of_ec_multiplication, POINT_CONVERSION_UNCOMPRESSED, new_point_x_and_y_as_single_bn, ctx)

BN_bn2bin(new_point_x_and_y_as_single_bn, &newPointXAndYPrefixedWithByte)

return Data(newPointXAndYPrefixedWithByte)
}

public static func decodePointOnCurve(forCompressedPublicKey publicKeyCompressed: Data) -> Data {
let ctx = BN_CTX_new()
defer { BN_CTX_free(ctx) }

let group = EC_GROUP_new_by_curve_name(NID_secp256k1)
defer { EC_GROUP_free(group) }

EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_COMPRESSED)
let point = EC_POINT_new(group)
defer { EC_POINT_free(point) }

publicKeyCompressed.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
EC_POINT_oct2point(group, point, ptr, Int(publicKeyCompressed.count), ctx)
return
}

var newPointXAndYPrefixedWithByte = [UInt8](repeating: 0, count: 65)
let new_point_x_and_y_as_single_bn = BN_new()
defer { BN_free(new_point_x_and_y_as_single_bn) }

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)

return Data(newPointXAndYPrefixedWithByte)
}
}
Loading

0 comments on commit 739aaa2

Please sign in to comment.