Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addresses generator (#1) #825

Merged
merged 20 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 42 additions & 32 deletions Sources/Web3Core/KeystoreManager/BIP32Keystore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,71 +149,81 @@ public class BIP32Keystore: AbstractKeystore {
} else {
newIndex = UInt32.zero
}

guard let newNode = parentNode.derive(index: newIndex, derivePrivateKey: true, hardened: false) else {
throw AbstractKeystoreError.keyDerivationError
}
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
throw AbstractKeystoreError.keyDerivationError
}
let prefixPath = self.rootPrefix
var newPath: String
if newNode.isHardened {
newPath = prefixPath + "/" + String(newNode.index % HDNode.hardenedIndexPrefix) + "'"
} else {
newPath = prefixPath + "/" + String(newNode.index)
}
JeneaVranceanu marked this conversation as resolved.
Show resolved Hide resolved
let newPath = rootPrefix + "/" + String(newNode.index)
addressStorage.add(address: newAddress, for: newPath)
}

public func createNewCustomChildAccount(password: String, path: String) throws {
guard let decryptedRootNode = try getPrefixNodeData(password) else {
guard let decryptedRootNode = try getPrefixNodeData(password),
let keystoreParams else {
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
}
guard let rootNode = HDNode(decryptedRootNode) else {
throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node")
}
let prefixPath = self.rootPrefix
var pathAppendix: String?

let prefixPath = rootPrefix
var pathAppendix = path

if path.hasPrefix(prefixPath) {
let upperIndex = (path.range(of: prefixPath)?.upperBound)!
if upperIndex < path.endIndex {
pathAppendix = String(path[path.index(after: upperIndex)])
if let upperIndex = (path.range(of: prefixPath)?.upperBound), upperIndex < path.endIndex {
pathAppendix = String(path[path.index(after: upperIndex)..<path.endIndex])
} else {
throw AbstractKeystoreError.encryptionError("out of bounds")
}

guard pathAppendix != nil else {
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
}
if pathAppendix!.hasPrefix("/") {
pathAppendix = pathAppendix?.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
}
} else {
if path.hasPrefix("/") {
pathAppendix = path.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
}
}
guard pathAppendix != nil else {
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
if pathAppendix.hasPrefix("/") {
pathAppendix = pathAppendix.trimmingCharacters(in: .init(charactersIn: "/"))
}
guard rootNode.depth == prefixPath.components(separatedBy: "/").count - 1 else {
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
}
guard let newNode = rootNode.derive(path: pathAppendix!, derivePrivateKey: true) else {
guard let newNode = rootNode.derive(path: pathAppendix, derivePrivateKey: true) else {
throw AbstractKeystoreError.keyDerivationError
}
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
throw AbstractKeystoreError.keyDerivationError
}
var newPath: String

let newPath: String
if newNode.isHardened {
newPath = prefixPath + "/" + pathAppendix!.trimmingCharacters(in: CharacterSet.init(charactersIn: "'")) + "'"
newPath = prefixPath + "/" + pathAppendix.trimmingCharacters(in: .init(charactersIn: "'")) + "'"
} else {
newPath = prefixPath + "/" + pathAppendix!
newPath = prefixPath + "/" + pathAppendix
}

addressStorage.add(address: newAddress, for: newPath)
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {throw AbstractKeystoreError.keyDerivationError}
try encryptDataToStorage(password, data: serializedRootNode, aesMode: self.keystoreParams!.crypto.cipher)
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {
throw AbstractKeystoreError.keyDerivationError
}
try encryptDataToStorage(password, data: serializedRootNode, aesMode: keystoreParams.crypto.cipher)
}

/// Fast generation addresses for current account
JeneaVranceanu marked this conversation as resolved.
Show resolved Hide resolved
/// used to show which addresses the user can get for indices from `0` to `number-1`
/// - Parameters:
/// - password: password of seed storage
/// - number: number of wallets addresses needed to generate from `0` to `number-1`
/// - Returns: Array of addresses generated from `0` to number bound
public func getAddressForAccount(password: String, number: UInt) throws -> [EthereumAddress] {
guard let decryptedRootNode = try? getPrefixNodeData(password),
let rootNode = HDNode(decryptedRootNode) else {
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
}
return try [UInt](0..<number).compactMap() { number in
guard rootNode.depth == rootPrefix.components(separatedBy: "/").count - 1,
let newNode = rootNode.derive(path: "\(number)", derivePrivateKey: true) else {
throw AbstractKeystoreError.keyDerivationError
}
return Utilities.publicToAddress(newNode.publicKey)
}
}

fileprivate func encryptDataToStorage(_ password: String, data: Data, dkLen: Int = 32, N: Int = 4096, R: Int = 6, P: Int = 1, aesMode: String = "aes-128-cbc") throws {
Expand Down
62 changes: 62 additions & 0 deletions Tests/web3swiftTests/localTests/BIP32KeystoreTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// BIP32KeystoreTests.swift
// localTests
//
// Created by 6od9i on 29.06.2023.
//

import Foundation
import XCTest
import Web3Core

@testable import web3swift

class BIP32KeystoreTests: XCTestCase {
func testAddressGeneration() throws {
/// Arrange
/// Seed randomly generated for this test
let mnemonic = "resource beyond merit enemy foot piece reveal eagle nothing luggage goose spot"
let password = "test_password"

let addressesCount: UInt = 101

guard let keystore = try BIP32Keystore(
mnemonics: mnemonic,
password: password,
mnemonicsPassword: "",
language: .english,
prefixPath: HDNode.defaultPathMetamaskPrefix) else {
XCTFail("Keystore has not generated")
throw NSError(domain: "0", code: 0)
}

/// Act
let addresses = try keystore.getAddressForAccount(password: password,
number: addressesCount)

guard let sameKeystore = try BIP32Keystore(
mnemonics: mnemonic,
password: password,
mnemonicsPassword: "",
language: .english,
prefixPath: HDNode.defaultPathMetamaskPrefix) else {
XCTFail("Keystore has not generated")
throw NSError(domain: "0", code: 0)
}

let walletNumber = addressesCount - 1
try sameKeystore.createNewCustomChildAccount(password: password,
path: HDNode.defaultPathMetamaskPrefix + "/\(walletNumber)")
let address = sameKeystore.addresses?.last?.address

/// Assert
XCTAssertEqual(UInt(addresses.count), addressesCount)
XCTAssertNotEqual(addresses[11], addresses[1])
XCTAssertEqual(addresses.last?.address, address)
XCTAssertEqual("0xEF22ebb8Bb5CDa4EaCc98b280c94Cbaa3828566F", addresses.last?.address)
XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", addresses.first?.address)
XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", sameKeystore.addresses?.first?.address)
XCTAssertEqual("0x971CF293b46162CD03DD9Cc39E89B592988DD6C4", addresses[Int(addressesCount / 2)].address)
XCTAssertEqual("0x3B565482a93CE4adA9dE0fD3c118bd41E24CC23C", addresses[10].address)
}
}