We started this journey at the beginning of June 2018. We were a small, talented and very passionate team of IT specialist. We evolved and grew up since them but we are still very compact. We were about to engage into the crypto world and start building a completely new product called LOCALLY Reward.
LOCALLY Reward is an engagement platform build on the top of the NEM Blockchain. It rewards people with NEM cryptocurrency based on the data they share on our systems. We have on the behind scenes a couple of layers of Machine Learning, A.I and Data processing algorithms to help us to build quality data.
We are the mobile team and our main goal on this project is to build a framework for iOS and Android, that allows people to share data and monitor their rewards.
The NEM Account
When you use LOCALLY Reward for the first time, we create a NEM account for you so you can carry on and withdraw your funds. A NEM account is like a bank account on the crypto world, it has an address and a public and private key. The address and public key are used as your identifier to receive and send funds on the blockchain. The private key is like your password to access your account.
NEM has a public API called NIS and at the time we started this project, we thought most of the features related to account management would be handled through API calls and network services. That was not the case and we ended up having to implement all features related to NEM accounts by ourselves (more about that in a second).
The NEM account lives on the heart of LOCALLY Reward, it's a vital piece of infrastructure and it was by far one of the biggest we faced on this project. The main reason behind that is because to create a NEM account we had to also deal with a couple of crypto libraries, algorithms, hashes and data structures that had to be implemented in a very specific order to be in conformance with the NEM specifications.
In addition to that, by the time we started this project, there was a lack of documentation and solid implementations you could follow. Fortunately, NEM has a very active and supportive community and they came up to the rescue when the hard time hit. We wouldn't be able to overcome this issue without them but even with all the support we had, it still cost us a lot of time coordinating in different time zones, communicating, etc. We only reached the bottom of this problem when we had a chance to put our hands on a decent piece of code implemented in C# (Thanks Kailin).
We are porting this implementation to the Mobile Platforms (Swift and Kotlin). We started with Swift because we thought it would be more challenging due to the language volatility and the lack of reference we had at that time.
The NEM account generation is all based on the concept of KeyPairs (Private and Public Keys) where the private key is the pivot of the other elements of creation.
Here is a quick hint of the private key generation:
var privateKeyBytes = [UInt8](repeating: 0, count: keySize)
createPrivateKey(&privateKeyBytes)
As you can see the private key is generated by a 32 bytes array shuffled randomly and converted to hexadecimal.
In Swift we use the private key to generate the public one and then we create a KeyPair.
static func generatePublicKey(fromPrivateKey privateKey: String) -> String {
var publicKeyBytes = ['UInt8'](repeating: 0, count: keySize)
var privateKeyBytesEndian = privateKey.toEndianArray(privateKey.bytes.count)
createPublicKey(&publicKeyBytes, &privateKeyBytesEndian)
return Data(bytes: publicKeyBytes).toHexString()
}
With the public key and a network selected, we then generate the account address using Sha3 and RipeMd160.
static func address(withPublicKey publicKey: String, network: Network = .mainnet) -> String {
// First Step
let sha3 = publicKey.toByteArray().sha3(.keccak256)
// Second Step
let sha3data = NSData(bytes: sha3, length: sha3.count)
let RIPEMD160 = RIPEMD.digest(sha3data) as NSData
// Third Step
let networkRIPEMD160Bytes = [network.rawValue] + (RIPEMD160 as Data).bytes
// Fourth Step
let networkRIPEMD160BytesSha256 = networkRIPEMD160Bytes.sha3(.keccak256)
let checksumArray = [networkRIPEMD160BytesSha256[0],
networkRIPEMD160BytesSha256[1],
networkRIPEMD160BytesSha256[2],
networkRIPEMD160BytesSha256[3]]
let checksumBuffer = networkRIPEMD160Bytes + checksumArray
let checksumData = Data(bytes: checksumBuffer, count: checksumBuffer.count)
return Base32Encode(checksumData)
}
With a public, private key and address in hands we can now create a NEM Account.
In Swift we do that in a single line of code:
Account(network: network)
The NEM Wallet
The NEM wallet is basically a place where we store NEM accounts. We use a public account, a selected network and password to generate these digital wallets using crypto algorithms such as AES256 and Keccak. The great thing about this wallet is that it’s fully compatible with any other NEM application. For instance, once you have an account on LOCALLY Reward you can export your wallet and download and access your account details in any other desktop client (Mac, Linux and Windows) [https://nem.io/downloads/].
Here a snippet of the wallet generation:
static func generate(withAccount account: PublicAccount, password: String, network: Int = Network.mainnet.intValue) -> Wallet? {
guard let encryptedKey = Crypto.encryptPrivateKey(privateKey: account.privateKey, withPassword: password) else { return nil }
return Wallet(address: account.address, encrypted: encryptedKey.encrypted, iv: encryptedKey.encryptedIV, network: network)
}
The idea here is to create an encrypted private key that will be stored securely on the digital wallet. Let’s have a look inside the encryptPrivateKey method.
At first, we give 20 rounds of derived Sha3 to the password, then we convert the derived Sha3 to hexadecimals and send it with the password to an AES256Encrypt.
static func encryptPrivateKey(privateKey: String, withPassword password: String) -> EncryptedKey? {
guard privateKey.isHexadecimal else { return nil }
let derivedSha3 = derivedPassSha(string: password, count: 20)
return AES256Encrypt(inputString: privateKey, hashKey: derivedSha3.toHexString())
}
The AES256Encrypt receives the private key, the hash generated with the password and a random IV (Initialization Vector).
static func AES256Encrypt(inputString: String, hashKey: String, iv: [UInt8] = AES.randomIV(16)) -> EncryptedKey? {
let inputStringBytes = inputString.toByteArray()
let inputStringData = Data(bytes: inputStringBytes, count: inputStringBytes.count)
guard let encryptedData = inputStringData.aesEncrypt(key: hashKey.toByteArray(), iv: iv) else { return nil }
return EncryptedKey(encrypted: encryptedData.toHexString(), encryptedIV: iv.toHexString())
}
The algorithm generates a byte array with the private key and another one with the hash that was passed. Then, it sends both the random IV to another AES encryption algorithm to create a byte array. When the byte array returns we generate two hexadecimal strings, one with the returned byte array and another one with the random IV.
Conclusion
In this chapter, we covered some of the challenges we faced in building NEM accounts and digital wallets on the iOS platform. In the next chapter, we will talk about signed transactions (mosaics, signed transactions, messages, etc) and how we built a network layer capable of communicate with the NEM blockchain.
|
Eduardo Dias - Lead Mobile Platform Developer I have been working on the software development industry for almost two decades. With a solid understanding of all software development life cycle, I worked in a wide range of projects delivering results that provide great user experience and reach beyond clients expectations.
|
Top comments (0)