Multi-threaded Arweave wallet mining with OpenSSL and Rust.
February 15, 2021
Recently, Tate had live streamed on the Verto discord server where he built a wallet miner. It worked well but it would take ages to get an ideal wallet address.
So I got inspired and re-wrote it with Rust using threadpool
and openssl
.
Arweave uses RSA-4096 for private key generation. It took an average of ~3 secs using Node.js crypto
module, so clearly we had to switch for something more faster.
Go’s crypto/rsa
I had heard a lot about the Golang crypto
package so I decided to try it out. I ended up implementing a complete JWK utility but I was not satisfied with the performance overall.
package main
import (
"crypto/rsa"
"crypto/rand"
"crypto"
"math/big"
"encoding/base64"
"encoding/json"
"strings"
"fmt"
)
func safeEncode(p []byte) string {
data := base64.URLEncoding.EncodeToString(p)
return strings.TrimRight(data, "=")
}
type Key struct {
Keys []*Key `json:"keys,omitempty"`
Kty string `json:"kty"`
Use string `json:"use,omitempty"`
Kid string `json:"kid,omitempty"`
Alg string `json:"alg,omitempty"`
Crv string `json:"crv,omitempty"`
X string `json:"x,omitempty"`
Y string `json:"y,omitempty"`
D string `json:"d,omitempty"`
N string `json:"n,omitempty"`
E string `json:"e,omitempty"`
K string `json:"k,omitempty"`
}
// Create a JWK from a public key
func PublicKey(key crypto.PublicKey) (*Key, error) {
switch key := key.(type) {
case *rsa.PublicKey:
jwt := &Key{
Kty: "RSA",
N: safeEncode(key.N.Bytes()),
E: safeEncode(big.NewInt(int64(key.E)).Bytes()),
}
return jwt, nil
case []byte:
jwt := &Key{
Kty: "oct",
K: safeEncode(key),
}
return jwt, nil
default:
return nil, fmt.Errorf("Unknown key type %T", key)
}
}
func PrivateKey(key crypto.PrivateKey) (*Key, error) {
switch key := key.(type) {
case *rsa.PrivateKey:
jwt, err := PublicKey(&key.PublicKey)
if err != nil {
return nil, err
}
jwt.D = safeEncode(key.D.Bytes())
return jwt, err
case []byte:
return PublicKey(key)
default:
return nil, fmt.Errorf("Unknown key type %T", key)
}
}
func main() {
reader := rand.Reader
key, _ := rsa.GenerateKey(reader, 4096)
jwk, _ := PrivateKey(key)
b, err := json.Marshal(jwk)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
It took an average of ~4 secs to generate one JWK. My next move was to try out OpenSSL with cgo
but I knew I could do this better with Rust.
And yeah, Rust.
Rust doesn’t have a standard crypto library. In fact, there are tons of alternatives to choose from.
OpenSSL’s state-of-the-art RSA implementation made me wanna use the openssl-rust
crate:
use openssl::bn::BigNum;
use openssl::rsa::Rsa;
#[derive(Debug)]
struct Key {
kty: String,
n: String,
e: String,
d: String,
}
// ...
let exponent = BigNum::from_u32(65537).unwrap();
let key = Rsa::generate_with_e(4096, &exponent).unwrap();
let n = key.n().to_vec();
let jwk = Key {
kty: "RSA".to_string(),
n: safe_encode!(n.clone()),
e: safe_encode!(key.e().to_vec()),
d: safe_encode!(key.d().to_vec()),
};
And tada! --release
builds took an average of ~500ms which was way faster than Go or Node.
We are not done yet. Let’s make mining parellal using threadpools
:D