Skip to content

Commit

Permalink
Merge commit 'dab2667578b9d053fc82dadb89337f5cbdd94f38' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangliugang committed Aug 22, 2023
2 parents 1fc6cd6 + dab2667 commit d098b87
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
- [x]**Literally following the standards** (BIP, EIP, etc):
- [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)**
- [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)
- [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
- [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
- [x] **RLP encoding**
- [x] Base58 encoding scheme
- [x] Formatting to and from Ethereum Units
Expand Down
75 changes: 40 additions & 35 deletions Sources/Web3Core/KeystoreManager/BIP32Keystore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,71 +149,76 @@ 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)
}
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
if newNode.isHardened {
newPath = prefixPath + "/" + pathAppendix!.trimmingCharacters(in: CharacterSet.init(charactersIn: "'")) + "'"
} else {
newPath = prefixPath + "/" + pathAppendix!
}

let 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
/// 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
11 changes: 9 additions & 2 deletions Sources/web3swift/Web3/Web3+Signing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ public struct Web3Signer {
keystore: T,
account: EthereumAddress,
password: String,
useHash: Bool = true,
useExtraEntropy: Bool = false) throws -> Data? {
var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account)
defer { Data.zero(&privateKey) }
guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil }
let (compressedSignature, _) = SECP256K1.signForRecovery(hash: hash,
var data: Data
if useHash {
guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil }
data = hash
} else {
data = personalMessage
}
let (compressedSignature, _) = SECP256K1.signForRecovery(hash: data,
privateKey: privateKey,
useExtraEntropy: useExtraEntropy)
return compressedSignature
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)
}
}

0 comments on commit d098b87

Please sign in to comment.