Skip to content

Commit 43f339d

Browse files
committed
added string utils to extract the account from a bip44 path
1 parent 08ddc4f commit 43f339d

File tree

2 files changed

+87
-25
lines changed

2 files changed

+87
-25
lines changed

Sources/Web3Core/KeystoreManager/BIP44.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public enum BIP44Error: Equatable {
2525
extension HDNode: BIP44 {
2626
public func derive(path: String, warns: Bool = true) async throws -> HDNode? {
2727
if warns {
28-
if let externalPath = path.externalChangePath {
29-
28+
var accountIndex = 0
29+
guard let account = path.accountFromPath else {
30+
return nil
3031
}
32+
3133
return nil
3234
} else {
3335
return derive(path: path, derivePrivateKey: true)
@@ -36,6 +38,20 @@ extension HDNode: BIP44 {
3638
}
3739

3840
extension String {
41+
/// Returns the account from the path if the string contains a well formed BIP44 path
42+
var accountFromPath: Int? {
43+
guard isBip44Path else {
44+
return nil
45+
}
46+
let components = components(separatedBy: "/")
47+
let accountIndex = 3
48+
let rawAccount = components[accountIndex].trimmingCharacters(in: CharacterSet(charactersIn: "'"))
49+
guard let account = Int(rawAccount) else {
50+
return nil
51+
}
52+
return account
53+
}
54+
3955
/// Returns a new BIP32 path that uses an external change, if the path is invalid returns nil
4056
var externalChangePath: String? {
4157
do {

Tests/web3swiftTests/localTests/StringBIP44Tests.swift

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,68 @@ import XCTest
77
@testable import Web3Core
88

99
final class StringBIP44Tests: XCTestCase {
10+
private var invalidPaths: [String]!
1011

11-
//MARK: - externalChangePath
12+
override func setUpWithError() throws {
13+
try super.setUpWithError()
14+
invalidPaths = ["",
15+
"m",
16+
"m/",
17+
"m/44",
18+
"m/44'",
19+
"m/44'/",
20+
"m/44'/60",
21+
"m/44'/60'",
22+
"m/44'/60'/",
23+
"m/44'/60'/0",
24+
"m/44'/60'/0'",
25+
"m/44'/60'/0'/",
26+
"m/44'/60'/0'/0",
27+
"m/44'/60'/0'/0/",
28+
"/44'/60'/0'/0/0",
29+
"m44'/60'/0'/0/0",
30+
"m0'/60'/0'/0/0",
31+
"m/'/60'/0'/0/0",
32+
"m/60'/0'/0/0",
33+
"m/44'/60/0'/0/0",
34+
"m/44'/'/0'/0/0",
35+
"m/44'/60'/0/0/0",
36+
"m/44'/60'/'/0/0",
37+
"m/44'/60'/0'/0",
38+
"m/44'/60'/0'/0/",
39+
"m/44'/60'/0'/-1/0",
40+
"m/44'/60'/0'/2/0",
41+
"m/44'/60.0'/0'/0/0",
42+
"m/44'/60'/0.0'/0/0",
43+
"m/44'/60'/0'/0/0.0"]
44+
}
45+
46+
override func tearDownWithError() throws {
47+
try super.tearDownWithError()
48+
invalidPaths = nil
49+
}
50+
51+
// MARK: - accountFromPath
52+
53+
func testInvalidPathReturnNilAccount() throws {
54+
invalidPaths.forEach { invalidPath in
55+
XCTAssertNil(invalidPath.accountFromPath)
56+
}
57+
}
58+
59+
func testValidPathReturnAccount() {
60+
let scenarios: [TestScenario<String, Int>] = [
61+
.init(input: "m/44'/60'/0'/1/4", expected: 0),
62+
.init(input: "m/44'/60'/1'/0/3", expected: 1),
63+
.init(input: "m/44'/60'/2'/0/2", expected: 2),
64+
.init(input: "m/44'/60'/3'/0/1", expected: 3),
65+
.init(input: "m/44'/60'/4'/0/0", expected: 4)]
66+
scenarios.forEach { scenario in
67+
XCTAssertEqual(scenario.input.accountFromPath, scenario.expected)
68+
}
69+
}
70+
71+
// MARK: - externalChangePath
1272

1373
func testInvalidChangesReturnNil() throws {
1474
let invalidPaths = ["m/44'/60'/0'/-1/0",
@@ -30,7 +90,7 @@ final class StringBIP44Tests: XCTestCase {
3090
XCTAssertEqual(result, path)
3191
}
3292

33-
//MARK: - isBip44Path
93+
// MARK: - isBip44Path
3494

3595
func testVerifyBip44Paths() {
3696
let validPaths = ["m/44'/0'/0'/0/0",
@@ -39,32 +99,18 @@ final class StringBIP44Tests: XCTestCase {
3999
"m/44'/0'/0'/1/0",
40100
"m/44'/0'/0'/0/1"]
41101
validPaths.forEach { validPath in
42-
let result = validPath.isBip44Path
43-
XCTAssertTrue(result)
102+
XCTAssertTrue(validPath.isBip44Path)
44103
}
45104
}
46105

47106
func testVerifyNotBip44Paths() {
48-
let invalidPaths = ["",
49-
"/44'/60'/0'/0/0",
50-
"m44'/60'/0'/0/0",
51-
"m0'/60'/0'/0/0",
52-
"m/'/60'/0'/0/0",
53-
"m/60'/0'/0/0",
54-
"m/44'/60/0'/0/0",
55-
"m/44'/'/0'/0/0",
56-
"m/44'/60'/0/0/0",
57-
"m/44'/60'/'/0/0",
58-
"m/44'/60'/0'/0",
59-
"m/44'/60'/0'/0/",
60-
"m/44'/60'/0'/-1/0",
61-
"m/44'/60'/0'/2/0",
62-
"m/44'/60.0'/0'/0/0",
63-
"m/44'/60'/0.0'/0/0",
64-
"m/44'/60'/0'/0/0.0"]
65107
invalidPaths.forEach { invalidPath in
66-
let result = invalidPath.isBip44Path
67-
XCTAssertFalse(result)
108+
XCTAssertFalse(invalidPath.isBip44Path)
68109
}
69110
}
70111
}
112+
113+
struct TestScenario<I, E> {
114+
let input: I
115+
let expected: E
116+
}

0 commit comments

Comments
 (0)