Skip to content

Commit

Permalink
Add EOS ecrecover function
Browse files Browse the repository at this point in the history
  • Loading branch information
XuNeal committed Mar 13, 2019
1 parent 03817d4 commit 3600c0b
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 1 deletion.
43 changes: 43 additions & 0 deletions Sources/Encryptor/Secp256k1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,49 @@ extension Encryptor {

return data.toHexString()
}

/// Recover public key from signature and message.
/// - Parameter signature: Signature.
/// - Parameter message: Raw message before signing.
/// - Parameter recid: recid.
/// - Returns: Recoverd public key.
func eosRecover(signature: Data, message: Data, recid: Int32) -> String? {
// guard let signBytes = signature.tk_dataFromHexString()?.bytes,
// let messageBytes = message.tk_dataFromHexString()?.bytes else {
// return nil
// }
let signBytes = signature.bytes
let messageBytes = message.bytes

let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY))!
defer {
secp256k1_context_destroy(context)
}

var sig = secp256k1_ecdsa_recoverable_signature()
secp256k1_ecdsa_recoverable_signature_parse_compact(context, &sig, signBytes, recid)

var publicKey = secp256k1_pubkey()
var result: Int32 = 0
result = secp256k1_ecdsa_recover(context, &publicKey, &sig, messageBytes)

if result == 0 {
return nil
}

var length = 65
var data = Data(count: length)
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) in
result = secp256k1_ec_pubkey_serialize(context, bytes, &length, &publicKey, UInt32(SECP256K1_EC_UNCOMPRESSED))
}

if result == 0 {
return nil
}

return data.toHexString()
}


/// Verify a key.
/// - Parameter key: Key in hex format.
Expand Down
12 changes: 12 additions & 0 deletions Sources/Foundation/EOS/EOSKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class EOSKey {
init(privateKey: [UInt8]) {
btcKey = BTCKey(privateKey: Data(bytes: privateKey))!
}

init(key: BTCKey) {
btcKey = key
}

convenience init(wif: String) {
self.init(privateKey: EOSKey.privateKey(from: wif))
Expand All @@ -34,6 +38,14 @@ class EOSKey {
func sign(data: Data) -> Data {
return btcKey.eosCompactSignature(forHash: data)
}


public static func ecRecover(data: Data, signature: Data) throws -> String {
guard let key = BTCKey.eosEcRecover(signature, forHash: data) else {
throw "Not found a workable private key"
}
return EOSKey(key: key).publicKey
}

static func privateKey(from wif: String) -> [UInt8] {
let wifBytes = (BTCDataFromBase58(wif) as Data).bytes
Expand Down
13 changes: 13 additions & 0 deletions Sources/Foundation/EOS/EOSTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,17 @@ public final class EOSTransaction {

return "SIG_K1_\(BTCBase58StringWithData(ret as Data)!)"
}

static func deserializeSignature(sig: String) throws -> Data {
guard sig.starts(with: "SIG_K1_") else {
throw "Signature must begin with SIG_K1_"
}
let base58Str = sig.tk_substring(from: "SIG_K1_".count)
let decodedData = BTCDataFromBase58(base58Str)! as Data
let rsvData = Data(bytes: decodedData.bytes[0..<65])
if EOSTransaction.signatureBase58(data: rsvData) != sig {
throw "The Checksum of eos signature is invalid"
}
return rsvData
}
}
5 changes: 5 additions & 0 deletions Sources/Keystore/EOS/EOSKeystore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ struct EOSKeystore: Keystore, EncMnemonicKeystore {
}

func decryptPrivateKey(from publicKey: String, password: String) throws -> [UInt8] {

guard verify(password: password) else {
throw PasswordError.incorrect
}

guard let keyPath = keyPathPrivates.first(where: { keyPathPrivate -> Bool in
return publicKey == keyPathPrivate.publicKey
}) else {
Expand Down
10 changes: 10 additions & 0 deletions Sources/Utils/Hex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,14 @@ public extension String {
func tk_isHex() -> Bool {
return Hex.isHex(self)
}

func tk_data() -> Data? {
var dataBytes: Data?
if self.tk_isHex() {
dataBytes = self.tk_dataFromHexString()
} else {
dataBytes = self.data(using: .utf8)
}
return dataBytes
}
}
6 changes: 5 additions & 1 deletion Sources/Wallet/Identity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public extension Identity {
extension Identity {
func append(_ newKeystore: Keystore) throws -> BasicWallet {
let wallet = BasicWallet(newKeystore)


if findWalletByAddress(wallet.address.removePrefix0xIfNeeded(), on: newKeystore.meta.chain!) != nil {
throw AddressError.alreadyExist
}

keystore.wallets.append(wallet)
keystore.walletIds.append(wallet.walletID)
if Identity.storage.flushWallet(wallet.keystore) && Identity.storage.flushIdentity(keystore) {
Expand Down
34 changes: 34 additions & 0 deletions Sources/Wallet/WalletManager+EOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,38 @@ public extension WalletManager {

return try EOSTransactionSigner(txs: txs, keystore: wallet.keystore, password: password).sign()
}

public static func eosEcSign(walletID: String, data: String, publicKey: String?, password: String) throws -> String {
guard let wallet = Identity.currentIdentity?.findWalletByWalletID(walletID) else {
throw GenericError.walletNotFound
}

let eosKey: EOSKey
if wallet.keystore is EOSLegacyKeystore {
let wif = try wallet.privateKey(password: password)
eosKey = EOSKey(wif: wif)
} else if wallet.keystore is EOSKeystore {
let prvKey = try (wallet.keystore as! EOSKeystore).decryptPrivateKey(from: publicKey!, password: password)
eosKey = EOSKey(privateKey: prvKey)
} else {
throw "Only EOS wallet can invoke the eosEcSign"
}

guard let hashedData = data.tk_data()?.sha256() else {
throw "Data shoud be string or hex"
}

return EOSTransaction.signatureBase58(data: eosKey.sign(data: hashedData))
}

public static func eosEcRecover(data: String, signature: String) throws -> String {
guard let hashedData = data.tk_data()?.sha256() else {
throw "Data shoud be string or hex"
}

let sig = try EOSTransaction.deserializeSignature(sig: signature)

return try EOSKey.ecRecover(data: hashedData, signature: sig)
}

}
18 changes: 18 additions & 0 deletions Tests/Foundation/EOS/EOSKeyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,22 @@ class EOSKeyTests: TestCase {
let eosKey = EOSKey(privateKey: privateKey)
XCTAssertEqual(TestData.eosPublicKey, eosKey.publicKey)
}

func testEcSignTest() {
do {
let meta = WalletMeta(chain: .eos, source: .wif)
let eosWallet = try WalletManager.importFromPrivateKey(TestData.eosPrivateKey, encryptedBy: TestData.password, metadata: meta, accountName: "imtoken1")
let signedData = try WalletManager.eosEcSign(walletID: eosWallet.walletID, data: "imToken2017", publicKey: TestData.eosPublicKey, password: TestData.password)
XCTAssertEqual("SIG_K1_JuVsfsNmB3JgvsnxUcmuw5m27gH9xTGuU4yN9BMoRLeLVYhA4Bfypdm8DDg5cUTXSLArDLc3gtRFkFHMm3rmZyZxD5FE7k", signedData)
let rightPubKey = try WalletManager.eosEcRecover(data: "imToken2017", signature: signedData)
XCTAssertEqual(TestData.eosPublicKey, rightPubKey)

let wrongPubKey = try WalletManager.eosEcRecover(data: "imToken2016", signature: signedData)
XCTAssertNotEqual(TestData.eosPublicKey, wrongPubKey)
} catch {
XCTFail(error.localizedDescription)
}

}

}
6 changes: 6 additions & 0 deletions Vendor/CoreBitcoin/BTCCurvePoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
// Initializes point with OpenSSL EC_POINT.
- (id) initWithEC_POINT:(const EC_POINT*)ecpoint;

- (id) initWithHex:(NSString*)hex;

- (id) initWithX:(BTCBigNumber*)x yBit:(NSInteger)yBit;
- (id) initWithSumOfTwoMultiplies:(BTCBigNumber*)n m:(BTCBigNumber*)m;
- (instancetype) multiplyTwo:(BTCBigNumber*)j x:(BTCCurvePoint*)x k:(BTCBigNumber*)k;

// These modify the receiver and return self (or nil in case of error). To create another point use -copy: [[point copy] multiply:number]
- (instancetype) multiply:(BTCBigNumber*)number;
- (instancetype) add:(BTCCurvePoint*)point;
Expand Down
101 changes: 101 additions & 0 deletions Vendor/CoreBitcoin/BTCCurvePoint.m
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ - (id) init {
return self;
}

- (id) initInfinity {
if (self = [self initEmpty]) {
if (!EC_POINT_copy(_point, EC_GROUP_get0_generator(_group))) {
return nil;
}
if (!EC_POINT_set_to_infinity(_group, _point)) {
return nil;
}
}
return self;
}

// Initializes point with its binary representation (corresponds to -data).
- (id) initWithData:(NSData*)data {
if (self = [self initEmpty]) {
Expand Down Expand Up @@ -112,6 +124,48 @@ - (id) initWithEC_POINT:(const EC_POINT*)ecpoint {
return self;
}

- (id) initWithHex:(NSString *)hex {
if (self = [self initEmpty]) {

// BIGNUM* bn = BN_bin2bn(data.bytes, (int)data.length, NULL);
// if (!bn) {
// return nil;
// }
if (!EC_POINT_hex2point(_group, hex.cString, _point, _bnctx)) {
// if (!EC_POINT_bn2point(_group, bn, _point, _bnctx)) {
// if (bn) BN_clear_free(bn);
return nil;
}

// Point is imported, only need to cleanup an intermediate BIGNUM structure.
// if (bn) BN_clear_free(bn);
}
return self;
}

- (id) initWithX:(BTCBigNumber*)x yBit:(NSInteger)yBit {
if (self = [self initEmpty]) {
// BTCBigNumber *y = [[BTCBigNumber alloc] initWithInt32: 0];
if (!EC_POINT_set_compressed_coordinates_GF2m(_group, _point, x.BIGNUM, yBit, _bnctx)) {
return nil;
}

}
return self;
}

// EC_POINT_mul calculates the value generator * n + q * m return the result
- (id) initWithSumOfTwoMultiplies:(BTCBigNumber*)n m:(BTCBigNumber*)m {

if (self = [self initEmpty]) {
if (!EC_POINT_mul(_group, _point, n.BIGNUM, _point, m.BIGNUM, _bnctx)) {
return nil;
}
}

return self;
}

- (NSData*) data {
NSMutableData* data = [NSMutableData dataWithLength:33];

Expand Down Expand Up @@ -169,10 +223,21 @@ - (instancetype) addGeneratorMultipliedBy:(BTCBigNumber*)number {
return self;
}



- (BOOL) isInfinity {
return 1 == EC_POINT_is_at_infinity(_group, _point);
}

- (instancetype) twice {

if (!EC_POINT_dbl(_group, _point, _point, _bnctx)) {
return nil;
}

return self;
}

- (BTCBigNumber*) x {
BN_CTX_start(_bnctx);
BIGNUM* bn = BN_CTX_get(_bnctx);
Expand Down Expand Up @@ -230,5 +295,41 @@ - (NSString*) description {
return [NSString stringWithFormat:@"<BTCCurvePoint:0x%p %@>", self, BTCHexFromData(self.data)];
}

- (instancetype) multiplyTwo:(BTCBigNumber*)j x:(BTCCurvePoint*)x k:(BTCBigNumber*)k {
NSLog(@"j bitlength %d", BN_num_bits(j.BIGNUM));
NSLog(@"k bitlength %d", BN_num_bits(k.BIGNUM));
NSLog(@"k %@", k.decimalString);
NSInteger i = MAX(BN_num_bits(j.BIGNUM), BN_num_bits(k.BIGNUM)) - 1;
NSLog(@"i lenght: %ld", i);
BTCCurvePoint *R = [[BTCCurvePoint alloc] initInfinity];
BTCCurvePoint *both = [[[BTCCurvePoint alloc] initWithEC_POINT:self.EC_POINT] add:x];
NSInteger z = 0;
while (i >= 0) {
BOOL jBit = BN_is_bit_set(j.BIGNUM, i);
BOOL kBit = BN_is_bit_set(k.BIGNUM, i);

R = [R twice];
if (z <= 10) {
NSLog(@"R Twice: (%@, %@)", [R.x stringInBase:10], [R.y stringInBase:10]);
z++;
}

if (jBit) {
if (kBit) {
R = [R add:both];
NSLog(@"R = [R add:both];");
} else {
NSLog(@"self (%@, %@)", self.x.decimalString, self.y.decimalString);
R = [R add:self];
NSLog(@"R = [R add:self];");
}
} else if (kBit) {
R = [R add:x];
NSLog(@"R = [R add:x];");
}
--i;
}
return R;
}

@end
1 change: 1 addition & 0 deletions Vendor/CoreBitcoin/BTCKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@

// just for eos
- (NSData*) eosCompactSignatureForHash:(NSData*)hash;
+ (instancetype) eosEcRecover:(NSData*)signature forHash:(NSData*)hash;

// [RFC6979 implementation](https://tools.ietf.org/html/rfc6979).
// Returns 32-byte `k` nonce generated deterministically from the `hash` and the private key.
Expand Down
18 changes: 18 additions & 0 deletions Vendor/CoreBitcoin/BTCKey.m
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,24 @@ - (NSData*) eosCompactSignatureForHash:(NSData*)hash {
return sigdata;
}

+ (instancetype) eosEcRecover:(NSData *)signature forHash:(NSData *)hash {
BTCKey *key = [[BTCKey alloc] initWithNewKeyPair:NO];
ECDSA_SIG *sig = ECDSA_SIG_new();
int recId = (int)[signature subdataWithRange:NSMakeRange(0, 1)];
BTCBigNumber *r = [[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:[signature subdataWithRange:NSMakeRange(1, 32)]];
BTCBigNumber *s = [[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:[signature subdataWithRange:NSMakeRange(33, 32)]];

sig->r = r.BIGNUM;
sig->s = s.BIGNUM;
recId = (recId - 27) & 3;
for (int i=0; i< recId; i++) {
if (ECDSA_SIG_recover_key_GFp(key->_key, sig, hash.bytes, (int)hash.length, i, 1)) {
return key;
}
}
return nil;
}

// Verifies digest against given compact signature. On success returns a public key.
// Reconstruct public key from a compact signature
// This is only slightly more CPU intensive than just verifying it.
Expand Down

0 comments on commit 3600c0b

Please sign in to comment.