我有一个为 macOS 制作的简单 VanityGen Swift 代码。它只是简单地生成比特币地址,直到找到一个以指定(最多 5 个)字符开头的地址,例如 1Test...
问题是在 M1 Max 上这不会超过每秒 25,000 个地址(如果我使用多线程,则为 70,000 个)。
相比之下,著名的 VanityGen(使用 CPU,而不是 GPU)使用 Intel/AMD CPU 平均每秒生成超过 100 万!所以我必须在这里做错事。
这里只是负责生成地址的函数(完整代码如下):
private func generateRandomBitcoinAddress() -> (address: String, key: String)? {
// Private key
guard let privateKey = try? secp256k1.Signing.PrivateKey(format: .compressed) else { return nil }
// Public key
let publicKey = privateKey.publicKey.rawRepresentation
// SHA256 of PublicKey
let sha256 = SHA256.hash(data: publicKey)
// RIPEMD-160 of sha256
let ripemd160 = RIPEMD160.hash(Data(sha256))
// Add version byte
var versionByte = 0x00
var addingVersionBytesToRipemd160 = Data(bytes: &versionByte, count: 1)
addingVersionBytesToRipemd160.append(contentsOf: ripemd160)
// SHA256 of RIPEMD-160 with added version byte
let shaOfRipe = SHA256.hash(data: addingVersionBytesToRipemd160)
// Repeat sha256
let doubleSha = SHA256.hash(data: Data(shaOfRipe))
// Take first 4 bytes as checksum
let checksum = doubleSha.bytes.prefix(4)
// Add checksum to RIPEMD-160 with added version byte
addingVersionBytesToRipemd160.append(contentsOf: checksum)
let binaryBitcoinAddressByte = addingVersionBytesToRipemd160.bytes
// Base58Check encoding
let bitcoinAddress = Base58.base58Encode(binaryBitcoinAddressByte)
return (bitcoinAddress, String(bytes: privateKey.rawRepresentation.bytes))
}
这里是 complete 和工作代码(只需为 macOS 11+ 创建一个 SwiftUI 项目并将此代码与注释包一起添加到 ContentView.swift 就足够了):
import SwiftUI
import secp256k1 // https://github.com/GigaBitcoin/secp256k1.swift.git (just secp256k1, without zkp)
import Base58Swift // https://github.com/keefertaylor/Base58Swift.git
import ripemd160_Swift // https://github.com/MiclausCorp/ripemd160-Swift.git
struct ContentView: View {
@StateObject private var addressGenerator = AddressGenerator()
@State private var vanityChars = ""
@State private var started = false
var body: some View {
VStack {
if !started {
TextField("Looking for Address that starts with Chars (max 5 char):", text: $vanityChars)
Button("Start") {
addressGenerator.startVanityGen(lookingFor: vanityChars)
started = true
}
} else {
Text("Looking for: \(vanityChars)...").padding(.bottom)
Text("Addresses Per Second: \(addressGenerator.addressesPerSecond)")
Text("Total: \(addressGenerator.totalAddressesGenerated)")
if addressGenerator.found {
Button("Found") {
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path)
}
}
}
}
.padding()
}
}
class AddressGenerator: ObservableObject {
@Published private(set) var totalAddressesGenerated = 0
@Published private(set) var addressesPerSecond = 0
@Published private(set) var found = false
private var currentSecond: Int {
Calendar.current.component(.second, from: Date())
}
func startVanityGen(lookingFor: String) {
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self else { return }
var counter = 0
var second = self.currentSecond
while self.found == false {
self.generateBitcoinAddressAndCompare(to: lookingFor)
counter += 1
if second != self.currentSecond {
let totalGenerated = counter
DispatchQueue.main.async {
self.addressesPerSecond = totalGenerated
self.totalAddressesGenerated += totalGenerated
}
counter = 0
second = self.currentSecond
}
}
}
}
func generateBitcoinAddressAndCompare(to vanity: String) {
guard let generated = generateRandomBitcoinAddress() else { return }
let bitcoinAddress = generated.address
let privateKey = generated.key
if bitcoinAddress.prefix(vanity.count) == vanity && found == false {
DispatchQueue.main.async {
self.found = true
self.saveToFile(address: bitcoinAddress, privateKey: privateKey)
}
}
}
private func generateRandomBitcoinAddress() -> (address: String, key: String)? {
// Private key
guard let privateKey = try? secp256k1.Signing.PrivateKey(format: .compressed) else { return nil }
// Public key
let publicKey = privateKey.publicKey.rawRepresentation
// SHA256 of PublicKey
let sha256 = SHA256.hash(data: publicKey)
// RIPEMD-160 of sha256
let ripemd160 = RIPEMD160.hash(Data(sha256))
// Add version byte
var versionByte = 0x00
var addingVersionBytesToRipemd160 = Data(bytes: &versionByte, count: 1)
addingVersionBytesToRipemd160.append(contentsOf: ripemd160)
// SHA256 of RIPEMD-160 with added version byte
let shaOfRipe = SHA256.hash(data: addingVersionBytesToRipemd160)
// Repeat sha256
let doubleSha = SHA256.hash(data: Data(shaOfRipe))
// Take first 4 bytes as checksum
let checksum = doubleSha.bytes.prefix(4)
// Add checksum to RIPEMD-160 with added version byte
addingVersionBytesToRipemd160.append(contentsOf: checksum)
let binaryBitcoinAddressByte = addingVersionBytesToRipemd160.bytes
// Base58Check encoding
let bitcoinAddress = Base58.base58Encode(binaryBitcoinAddressByte)
return (bitcoinAddress, String(bytes: privateKey.rawRepresentation.bytes))
}
func saveToFile(address: String, privateKey: String) {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("\(address).txt")
let fileContent = "Address: \(address) - Private Key: \(privateKey)"
try? fileContent.write(to: url, atomically: true, encoding: String.Encoding.utf8)
}
}
请帮助我了解我做错了什么以及如何提高速度。