Skip to content

Commit 9c78e98

Browse files
authored
feat: support recovering wart-wallet addresses
1 parent 18c8a8f commit 9c78e98

File tree

1 file changed

+102
-8
lines changed

1 file changed

+102
-8
lines changed

src/main/backend/wallet.ts

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,109 @@
11
import { secp256k1 } from "ethereum-cryptography/secp256k1";
22
import { sha256 } from "ethereum-cryptography/sha256";
33
import { ripemd160 } from "ethereum-cryptography/ripemd160";
4-
import { toHex, utf8ToBytes } from "ethereum-cryptography/utils";
4+
import { toHex } from "ethereum-cryptography/utils";
55

6+
// Custom imports
7+
const elliptic = require('elliptic');
8+
const ec = new elliptic.ec('secp256k1');
9+
const crypto = require('crypto-browserify');
10+
11+
// PRNG
12+
function* block_generator(seed) {
13+
let counter = 0;
14+
while (true) {
15+
for (const byte of crypto.createHash('sha256').update("prng-" + counter.toString() + "-" + seed).digest()) {
16+
yield byte;
17+
}
18+
counter++;
19+
}
20+
}
21+
22+
class PRNG {
23+
generator;
24+
constructor(seed) {
25+
this.generator = block_generator(seed);
26+
}
27+
28+
get_bytes(n) {
29+
let a = new Uint8Array(n);
30+
for (let i = 0; i < n; i++) {
31+
a[i] = this.generator.next().value;
32+
}
33+
return a;
34+
}
35+
}
36+
37+
// workaround
38+
function uint8_to_byte_literal(uint8) {
39+
const bytes = ["\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06",
40+
"\\x07", "\\x08", "\\t", "\\n", "\\x0b", "\\x0c", "\\r", "\\x0e", "\\x0f", "\\x10",
41+
"\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19",
42+
"\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f", " ", "!", "\"", "#", "$",
43+
"%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3",
44+
"4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B",
45+
"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
46+
"R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\\\", "]", "^", "_", "`",
47+
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
48+
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~",
49+
"\\x7f", "\\x80", "\\x81", "\\x82", "\\x83", "\\x84", "\\x85", "\\x86", "\\x87",
50+
"\\x88", "\\x89", "\\x8a", "\\x8b", "\\x8c", "\\x8d", "\\x8e", "\\x8f", "\\x90",
51+
"\\x91", "\\x92", "\\x93", "\\x94", "\\x95", "\\x96", "\\x97", "\\x98", "\\x99",
52+
"\\x9a", "\\x9b", "\\x9c", "\\x9d", "\\x9e", "\\x9f", "\\xa0", "\\xa1", "\\xa2",
53+
"\\xa3", "\\xa4", "\\xa5", "\\xa6", "\\xa7", "\\xa8", "\\xa9", "\\xaa", "\\xab",
54+
"\\xac", "\\xad", "\\xae", "\\xaf", "\\xb0", "\\xb1", "\\xb2", "\\xb3", "\\xb4",
55+
"\\xb5", "\\xb6", "\\xb7", "\\xb8", "\\xb9", "\\xba", "\\xbb", "\\xbc", "\\xbd",
56+
"\\xbe", "\\xbf", "\\xc0", "\\xc1", "\\xc2", "\\xc3", "\\xc4", "\\xc5", "\\xc6",
57+
"\\xc7", "\\xc8", "\\xc9", "\\xca", "\\xcb", "\\xcc", "\\xcd", "\\xce", "\\xcf",
58+
"\\xd0", "\\xd1", "\\xd2", "\\xd3", "\\xd4", "\\xd5", "\\xd6", "\\xd7", "\\xd8",
59+
"\\xd9", "\\xda", "\\xdb", "\\xdc", "\\xdd", "\\xde", "\\xdf", "\\xe0", "\\xe1",
60+
"\\xe2", "\\xe3", "\\xe4", "\\xe5", "\\xe6", "\\xe7", "\\xe8", "\\xe9", "\\xea",
61+
"\\xeb", "\\xec", "\\xed", "\\xee", "\\xef", "\\xf0", "\\xf1", "\\xf2", "\\xf3",
62+
"\\xf4", "\\xf5", "\\xf6", "\\xf7", "\\xf8", "\\xf9", "\\xfa", "\\xfb", "\\xfc",
63+
"\\xfd", "\\xfe", "\\xff"]
64+
return bytes[uint8];
65+
}
66+
67+
// expects mnemonic phrase as string in format: "word1 word2 word3 word4 word5..."
68+
// returns private key as integer (point on curve)
69+
function warthog_mnemo_to_pk(mnemo){
70+
let order = BigInt(115792089237316195423570985008687907852837564279074904382605163141518161494337);
71+
let bytes = 32;
72+
73+
let mnemo_hash = crypto.pbkdf2Sync(mnemo, 'mnemonic', 2048, 64, 'sha512');
74+
75+
let seed = "";
76+
for (const byte of mnemo_hash) {
77+
seed += uint8_to_byte_literal(byte);
78+
}
79+
if (seed.includes("'")) {
80+
seed = "b\"" + seed + "\"";
81+
}else{
82+
seed = "b'" + seed + "'";
83+
}
84+
85+
let prng = new PRNG(seed);
86+
while (true) {
87+
let extrabyte = new Uint8Array(1);
88+
extrabyte[0] = prng.get_bytes(1)[0] & 1;
89+
90+
let guess = new Uint8Array(33);
91+
guess.set(extrabyte);
92+
guess.set(prng.get_bytes(bytes), 1);
93+
let guess_int = BigInt("0x" + Buffer.from(guess).toString('hex'));
94+
95+
// Compare to both variables to fix the type comparision issue
96+
if (BigInt(1) <= guess_int && guess_int < order){
97+
return guess_int + BigInt(1);
98+
}
99+
}
100+
}
101+
102+
103+
// Wartlock implementations
6104
export async function walletFromSeed(seed: string): Promise<{ address: string; privateKey: string }> {
7-
const seedBytes = utf8ToBytes(seed);
8-
const privateKey = derivePrivateKey(seedBytes);
105+
const privateKeyHex = ec.keyFromPrivate(warthog_mnemo_to_pk(seed), 10).getPrivate().toString("hex");
106+
const privateKey = new Uint8Array(Buffer.from(privateKeyHex, "hex"));
9107
const publicKey = secp256k1.getPublicKey(privateKey, true);
10108
const address = addressFromPublicKey(publicKey);
11109

@@ -21,15 +119,11 @@ export async function walletFromPkHex(pkHex) {
21119
};
22120
}
23121

24-
function derivePrivateKey(seed) {
25-
const hash = sha256(seed);
26-
return hash.slice(0, 32);
27-
}
28-
29122
function addressFromPublicKey(publicKey) {
30123
const sha256Hash = sha256(publicKey);
31124
const ripemd160Hash = ripemd160(sha256Hash);
32125
const checksum = sha256(ripemd160Hash).slice(0, 4);
33126
const addressBytes = new Uint8Array([...ripemd160Hash, ...checksum]);
34127
return toHex(addressBytes);
35128
}
129+

0 commit comments

Comments
 (0)