Skip to content

Commit 7cb0fb6

Browse files
committed
added string extension function to obtain a new path with new account, change and addressIndex from a valid bip44 path. code cleanup
1 parent d31fb83 commit 7cb0fb6

File tree

2 files changed

+53
-51
lines changed

2 files changed

+53
-51
lines changed

Sources/Web3Core/KeystoreManager/BIP44.swift

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ extension HDNode: BIP44 {
4040
}
4141

4242
extension String {
43+
/// Verifies if current value matches BIP44 path standard
44+
var isBip44Path: Bool {
45+
do {
46+
let pattern = "^m/44'/\\d+'/\\d+'/[0-1]/\\d+$"
47+
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
48+
let matches = regex.numberOfMatches(in: self, range: NSRange(location: 0, length: utf16.count))
49+
return matches == 1
50+
} catch {
51+
return false
52+
}
53+
}
54+
4355
/// Returns the account from the path if the string contains a well formed BIP44 path
4456
var accountFromPath: Int? {
4557
guard isBip44Path else {
@@ -54,37 +66,25 @@ extension String {
5466
return account
5567
}
5668

57-
/// Returns a new BIP32 path that uses an external change, if the path is invalid returns nil
58-
var externalChangePath: String? {
59-
do {
60-
guard isBip44Path else {
61-
return nil
62-
}
63-
let changePathPattern = "'/[0-1]/"
64-
let regex = try NSRegularExpression(pattern: changePathPattern, options: [.caseInsensitive])
65-
let range = NSRange(location: 0, length: utf16.count)
66-
let matches = regex.numberOfMatches(in: self, range: range)
67-
if matches == 1 {
68-
let result = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "'/0/")
69-
return result
70-
} else {
71-
return nil
72-
}
73-
} catch {
69+
/**
70+
Transforms a bip44 path into a new one changing `account` & `index`. The resulting one will have the change value equal to `0` to represent external chain. Format `m/44'/coin_type'/account'/change/address_index`
71+
- Parameter account: the new `account` to use
72+
- Parameter addressIndex: the new `addressIndex` to use
73+
- Returns: a valid bip44 path or nil otherwise
74+
*/
75+
func newPath(account: Int, addressIndex: Int) -> String? {
76+
guard isBip44Path else {
7477
return nil
7578
}
76-
}
77-
78-
/// Verifies if matches BIP44 path standard
79-
var isBip44Path: Bool {
80-
do {
81-
let pattern = "^m/44'/\\d+'/\\d+'/[0-1]/\\d+$"
82-
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
83-
let matches = regex.numberOfMatches(in: self, range: NSRange(location: 0, length: utf16.count))
84-
return matches == 1
85-
} catch {
86-
return false
87-
}
79+
var components = components(separatedBy: "/")
80+
let accountPosition = 3
81+
components[accountPosition] = "\(account)'"
82+
let changePosition = 4
83+
components[changePosition] = "0"
84+
let addressIndexPosition = 5
85+
components[addressIndexPosition] = "\(addressIndex)"
86+
let result = components.joined(separator: "/")
87+
return result
8888
}
8989
}
9090

Tests/web3swiftTests/localTests/StringBIP44Tests.swift

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,28 +68,6 @@ final class StringBIP44Tests: XCTestCase {
6868
}
6969
}
7070

71-
// MARK: - externalChangePath
72-
73-
func testInvalidChangesReturnNil() throws {
74-
let invalidPaths = ["m/44'/60'/0'/-1/0",
75-
"m/44'/60'/0'/2/0"]
76-
invalidPaths.forEach { invalidPath in
77-
XCTAssertNil(invalidPath.externalChangePath)
78-
}
79-
}
80-
81-
func testInternalChangeReturnsExternalChangePath() throws {
82-
let path = "m/44'/60'/0'/1/0"
83-
let result = path.externalChangePath
84-
XCTAssertEqual(result, "m/44'/60'/0'/0/0")
85-
}
86-
87-
func testExternalChangeReturnsExternalChangePath() throws {
88-
let path = "m/44'/60'/0'/0/0"
89-
let result = path.externalChangePath
90-
XCTAssertEqual(result, path)
91-
}
92-
9371
// MARK: - isBip44Path
9472

9573
func testVerifyBip44Paths() {
@@ -108,6 +86,30 @@ final class StringBIP44Tests: XCTestCase {
10886
XCTAssertFalse(invalidPath.isBip44Path)
10987
}
11088
}
89+
90+
// MARK: - newPath
91+
92+
func testNotBip44PathNewPathReturnsNil() {
93+
invalidPaths.forEach { invalidPath in
94+
XCTAssertNil(invalidPath.newPath(account: Int.random(in: 0...Int.max), addressIndex: Int.random(in: 0...Int.max)))
95+
}
96+
}
97+
98+
func testNewPathGeneratesExternalChainAsZero() {
99+
let path = "m/44'/60'/0'/1/0"
100+
101+
let result = path.newPath(account: 0, addressIndex: 0)
102+
103+
XCTAssertEqual(result, "m/44'/60'/0'/0/0")
104+
}
105+
106+
func testNewPathGeneratesNewAccountAndAddressIndex() {
107+
let path = "m/44'/60'/1'/0/2"
108+
109+
let result = path.newPath(account: 4, addressIndex: 3)
110+
111+
XCTAssertEqual(result, "m/44'/60'/4'/0/3")
112+
}
111113
}
112114

113115
struct TestScenario<I, E> {

0 commit comments

Comments
 (0)