Skip to content

Commit c0e06d5

Browse files
authored
Merge branch 'web3swift-team:develop' into Mnemonic-Data
2 parents ab8aaba + dab2667 commit c0e06d5

File tree

4 files changed

+112
-38
lines changed

4 files changed

+112
-38
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
- [x]**Literally following the standards** (BIP, EIP, etc):
5959
- [x] **[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (HD Wallets), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) (Seed phrases), [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) (Key generation prefixes)**
6060
- [x] **[EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)** (Standard interface for tokens - ERC-20), **[EIP-67](https://github.com/ethereum/EIPs/issues/67)** (Standard URI scheme), **[EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)** (Replay attacks protection), **[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md)** (Typed Transaction Envelope), **[EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)** (Gas Fee market change)
61-
- [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-681, EIP-721, EIP-165, EIP-777, EIP-820, EIP-888, EIP-1400, EIP-1410, EIP-1594, EIP-1643, EIP-1644, EIP-1633, EIP-721, EIP-1155, EIP-1376, ST-20
61+
- [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-165, EIP-681, EIP-721, EIP-777, EIP-820, EIP-888, EIP-1155, EIP-1376, EIP-1400, EIP-1410, EIP-1594, EIP-1633, EIP-1643, EIP-1644, EIP-4361 ([SIWE](https://eips.ethereum.org/EIPS/eip-4361)), ST-20
6262
- [x] **RLP encoding**
6363
- [x] Base58 encoding scheme
6464
- [x] Formatting to and from Ethereum Units

Sources/Web3Core/KeystoreManager/BIP32Keystore.swift

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -149,71 +149,76 @@ public class BIP32Keystore: AbstractKeystore {
149149
} else {
150150
newIndex = UInt32.zero
151151
}
152+
152153
guard let newNode = parentNode.derive(index: newIndex, derivePrivateKey: true, hardened: false) else {
153154
throw AbstractKeystoreError.keyDerivationError
154155
}
155156
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
156157
throw AbstractKeystoreError.keyDerivationError
157158
}
158-
let prefixPath = self.rootPrefix
159-
var newPath: String
160-
if newNode.isHardened {
161-
newPath = prefixPath + "/" + String(newNode.index % HDNode.hardenedIndexPrefix) + "'"
162-
} else {
163-
newPath = prefixPath + "/" + String(newNode.index)
164-
}
159+
let newPath = rootPrefix + "/" + String(newNode.index)
165160
addressStorage.add(address: newAddress, for: newPath)
166161
}
167162

168163
public func createNewCustomChildAccount(password: String, path: String) throws {
169-
guard let decryptedRootNode = try getPrefixNodeData(password) else {
164+
guard let decryptedRootNode = try getPrefixNodeData(password),
165+
let keystoreParams else {
170166
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
171167
}
172168
guard let rootNode = HDNode(decryptedRootNode) else {
173169
throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node")
174170
}
175-
let prefixPath = self.rootPrefix
176-
var pathAppendix: String?
171+
172+
let prefixPath = rootPrefix
173+
var pathAppendix = path
174+
177175
if path.hasPrefix(prefixPath) {
178-
let upperIndex = (path.range(of: prefixPath)?.upperBound)!
179-
if upperIndex < path.endIndex {
180-
pathAppendix = String(path[path.index(after: upperIndex)])
176+
if let upperIndex = (path.range(of: prefixPath)?.upperBound), upperIndex < path.endIndex {
177+
pathAppendix = String(path[path.index(after: upperIndex)..<path.endIndex])
181178
} else {
182179
throw AbstractKeystoreError.encryptionError("out of bounds")
183180
}
184-
185-
guard pathAppendix != nil else {
186-
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
187-
}
188-
if pathAppendix!.hasPrefix("/") {
189-
pathAppendix = pathAppendix?.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
190-
}
191-
} else {
192-
if path.hasPrefix("/") {
193-
pathAppendix = path.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
194-
}
195181
}
196-
guard pathAppendix != nil else {
197-
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
182+
if pathAppendix.hasPrefix("/") {
183+
pathAppendix = pathAppendix.trimmingCharacters(in: .init(charactersIn: "/"))
198184
}
199185
guard rootNode.depth == prefixPath.components(separatedBy: "/").count - 1 else {
200186
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
201187
}
202-
guard let newNode = rootNode.derive(path: pathAppendix!, derivePrivateKey: true) else {
188+
guard let newNode = rootNode.derive(path: pathAppendix, derivePrivateKey: true) else {
203189
throw AbstractKeystoreError.keyDerivationError
204190
}
205191
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
206192
throw AbstractKeystoreError.keyDerivationError
207193
}
208-
var newPath: String
209-
if newNode.isHardened {
210-
newPath = prefixPath + "/" + pathAppendix!.trimmingCharacters(in: CharacterSet.init(charactersIn: "'")) + "'"
211-
} else {
212-
newPath = prefixPath + "/" + pathAppendix!
213-
}
194+
195+
let newPath = prefixPath + "/" + pathAppendix
196+
214197
addressStorage.add(address: newAddress, for: newPath)
215-
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {throw AbstractKeystoreError.keyDerivationError}
216-
try encryptDataToStorage(password, data: serializedRootNode, aesMode: self.keystoreParams!.crypto.cipher)
198+
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {
199+
throw AbstractKeystoreError.keyDerivationError
200+
}
201+
try encryptDataToStorage(password, data: serializedRootNode, aesMode: keystoreParams.crypto.cipher)
202+
}
203+
204+
/// Fast generation addresses for current account
205+
/// used to show which addresses the user can get for indices from `0` to `number-1`
206+
/// - Parameters:
207+
/// - password: password of seed storage
208+
/// - number: number of wallets addresses needed to generate from `0` to `number-1`
209+
/// - Returns: Array of addresses generated from `0` to number bound
210+
public func getAddressForAccount(password: String, number: UInt) throws -> [EthereumAddress] {
211+
guard let decryptedRootNode = try? getPrefixNodeData(password),
212+
let rootNode = HDNode(decryptedRootNode) else {
213+
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
214+
}
215+
return try [UInt](0..<number).compactMap { number in
216+
guard rootNode.depth == rootPrefix.components(separatedBy: "/").count - 1,
217+
let newNode = rootNode.derive(path: "\(number)", derivePrivateKey: true) else {
218+
throw AbstractKeystoreError.keyDerivationError
219+
}
220+
return Utilities.publicToAddress(newNode.publicKey)
221+
}
217222
}
218223

219224
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 {

Sources/web3swift/Web3/Web3+Signing.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ public struct Web3Signer {
2323
keystore: T,
2424
account: EthereumAddress,
2525
password: String,
26+
useHash: Bool = true,
2627
useExtraEntropy: Bool = false) throws -> Data? {
2728
var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account)
2829
defer { Data.zero(&privateKey) }
29-
guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil }
30-
let (compressedSignature, _) = SECP256K1.signForRecovery(hash: hash,
30+
var data: Data
31+
if useHash {
32+
guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil }
33+
data = hash
34+
} else {
35+
data = personalMessage
36+
}
37+
let (compressedSignature, _) = SECP256K1.signForRecovery(hash: data,
3138
privateKey: privateKey,
3239
useExtraEntropy: useExtraEntropy)
3340
return compressedSignature
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// BIP32KeystoreTests.swift
3+
// localTests
4+
//
5+
// Created by 6od9i on 29.06.2023.
6+
//
7+
8+
import Foundation
9+
import XCTest
10+
import Web3Core
11+
12+
@testable import web3swift
13+
14+
class BIP32KeystoreTests: XCTestCase {
15+
func testAddressGeneration() throws {
16+
/// Arrange
17+
/// Seed randomly generated for this test
18+
let mnemonic = "resource beyond merit enemy foot piece reveal eagle nothing luggage goose spot"
19+
let password = "test_password"
20+
21+
let addressesCount: UInt = 101
22+
23+
guard let keystore = try BIP32Keystore(
24+
mnemonics: mnemonic,
25+
password: password,
26+
mnemonicsPassword: "",
27+
language: .english,
28+
prefixPath: HDNode.defaultPathMetamaskPrefix) else {
29+
XCTFail("Keystore has not generated")
30+
throw NSError(domain: "0", code: 0)
31+
}
32+
33+
/// Act
34+
let addresses = try keystore.getAddressForAccount(password: password,
35+
number: addressesCount)
36+
37+
guard let sameKeystore = try BIP32Keystore(
38+
mnemonics: mnemonic,
39+
password: password,
40+
mnemonicsPassword: "",
41+
language: .english,
42+
prefixPath: HDNode.defaultPathMetamaskPrefix) else {
43+
XCTFail("Keystore has not generated")
44+
throw NSError(domain: "0", code: 0)
45+
}
46+
47+
let walletNumber = addressesCount - 1
48+
try sameKeystore.createNewCustomChildAccount(password: password,
49+
path: HDNode.defaultPathMetamaskPrefix + "/\(walletNumber)")
50+
let address = sameKeystore.addresses?.last?.address
51+
52+
/// Assert
53+
XCTAssertEqual(UInt(addresses.count), addressesCount)
54+
XCTAssertNotEqual(addresses[11], addresses[1])
55+
XCTAssertEqual(addresses.last?.address, address)
56+
XCTAssertEqual("0xEF22ebb8Bb5CDa4EaCc98b280c94Cbaa3828566F", addresses.last?.address)
57+
XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", addresses.first?.address)
58+
XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", sameKeystore.addresses?.first?.address)
59+
XCTAssertEqual("0x971CF293b46162CD03DD9Cc39E89B592988DD6C4", addresses[Int(addressesCount / 2)].address)
60+
XCTAssertEqual("0x3B565482a93CE4adA9dE0fD3c118bd41E24CC23C", addresses[10].address)
61+
}
62+
}

0 commit comments

Comments
 (0)