Skip to content

Commit

Permalink
init walle cli
Browse files Browse the repository at this point in the history
  • Loading branch information
iskyd committed Oct 4, 2024
1 parent 5d31569 commit a299fa0
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/bip44.zig
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,19 @@ pub fn generateAccountPrivate(extended_privkey: bip32.ExtendedPrivateKey, purpos
return index_extended_privkey;
}

// Purpose, cointype, and account use hardened derivation
// 2147483648 is added to the passed values for this fields.
pub fn generateDescriptorPrivate(extended_privkey: bip32.ExtendedPrivateKey, purpose: u32, cointype: u32, account: u32) !bip32.ExtendedPrivateKey {
const purpose_extended_privkey = try bip32.deriveHardenedChild(extended_privkey, purpose + 2147483648);
const cointype_extended_privkey = try bip32.deriveHardenedChild(purpose_extended_privkey, cointype + 2147483648);

// Add check that avoid creation of this account if previous account has no transaction associated
// as specified in bip44 https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account
const account_extended_privkey = try bip32.deriveHardenedChild(cointype_extended_privkey, account + 2147483648);

return account_extended_privkey;
}

pub fn generatePublicFromAccountPublicKey(extended_pubkey: bip32.ExtendedPublicKey, change: u32, index: u32) !bip32.PublicKey {
const change_extended_pubkey = try bip32.deriveChildFromExtendedPublicKey(extended_pubkey, change);
const index_extended_pubkey = try bip32.deriveChildFromExtendedPublicKey(change_extended_pubkey, index);
Expand Down
18 changes: 18 additions & 0 deletions src/db/db.zig
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,24 @@ pub fn getDescriptor(allocator: std.mem.Allocator, db: *sqlite.Db, path: []u8, p
return null;
}

pub fn saveDescriptor(allocator: std.mem.Allocator, db: *sqlite.Db, descriptor: Descriptor) !void {
const sql = "INSERT INTO descriptors(extended_key, path, private) VALUES(?,?,?);";
const path = try descriptor.keypath.toStr(allocator, null);
defer allocator.free(path);

var stmt = try db.prepare(sql);
defer stmt.deinit();
try stmt.exec(.{}, .{ .extended_key = descriptor.extended_key, .path = path, .private = descriptor.private });
}

pub fn countDescriptors(db: *sqlite.Db) !usize {
const sql = "SELECT COUNT(*) as total FROM descriptors;";
var stmt = try db.prepare(sql);
defer stmt.deinit();
const row = try stmt.one(struct { total: usize }, .{}, .{});
return row.?.total;
}

pub fn getUsedKeyPaths(allocator: std.mem.Allocator, db: *sqlite.Db) ![]KeyPath(5) {
const sql = "SELECT DISTINCT(path) AS path FROM outputs;";
var stmt = try db.prepare(sql);
Expand Down
92 changes: 92 additions & 0 deletions src/walle.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const std = @import("std");
const db = @import("db/db.zig");
const bip39 = @import("bip39.zig");
const bip32 = @import("bip32.zig");
const bip44 = @import("bip44.zig");
const Network = @import("const.zig").Network;
const utils = @import("utils.zig");

fn showHelp() void {
std.debug.print("Valid commands: walletcreate, walletimport\nFor more information use walle <cmd> help", .{});
}

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

const Commands = enum {
walletcreate,
};

const args = std.process.argsAlloc(allocator) catch {
std.debug.print("Error while allocating memory for args\n", .{});
return;
};
defer std.process.argsFree(allocator, args);
if (args.len < 2) {
showHelp();
return;
}

const cmd = std.meta.stringToEnum(Commands, args[1]);
if (cmd == null) {
showHelp();
return;
}

var database = try db.openDB();
defer db.closeDB(database);
try db.initDB(&database);

switch (cmd.?) {
.walletcreate => {
if (args.len < 3 or std.mem.eql(u8, args[2], "help")) {
std.debug.print("Create new wallet\nwalle walletcreate <mainnet/testnet>\n", .{});
return;
}

const network: Network = if (std.mem.eql(u8, args[2], "mainnet")) .mainnet else .testnet;

const total_descriptors = try db.countDescriptors(&database);
if (total_descriptors > 0) {
std.debug.print("Wallet already exists. Delete it before creating a new one.\n", .{});
return;
}

const wordlist = try bip39.WordList.init(allocator, "wordlist/english.txt");
// defer wordlist.deinit();
var entropy: [16]u8 = undefined;
bip39.generateEntropy(&entropy, 128);
var mnemonic: [12][]u8 = undefined;
try bip39.generateMnemonic(allocator, &entropy, wordlist, &mnemonic);
std.debug.print("This is your mnemonic. Save it offline and dont forget it, otherwise you will loose your funds\n", .{});
for (mnemonic) |word| {
std.debug.print("{s} ", .{word});
}
std.debug.print("\n", .{});

var seed: [64]u8 = undefined;
try bip39.mnemonicToSeed(allocator, &mnemonic, "", &seed);
const master_extended_privkey: bip32.ExtendedPrivateKey = bip32.generateExtendedMasterPrivateKey(&seed);

const cointype: u32 = if (network == .mainnet) bip44.bitcoin_coin_type else bip44.bitcoin_testnet_coin_type;
const descriptor_privkey = try bip44.generateDescriptorPrivate(master_extended_privkey, bip44.bip_84_purpose, cointype, 0);
const pubkey = bip32.generatePublicKey(descriptor_privkey.privatekey);
const pubkey_compressed = try pubkey.compress();

const privkey_version: bip32.SerializedPrivateKeyVersion = if (network == .mainnet) .segwit_mainnet else .segwit_testnet;
const pubkey_version: bip32.SerializedPublicKeyVersion = if (network == .mainnet) .segwit_mainnet else .segwit_testnet;
const fingerprint = utils.hash160(&pubkey_compressed)[0..4].*;
const addr_privkey = try descriptor_privkey.address(privkey_version, 3, fingerprint, 2147483648);
const descriptor_priv = bip44.Descriptor{ .extended_key = addr_privkey, .keypath = bip44.KeyPath(3){ .path = [3]u32{ bip44.bip_84_purpose, cointype, 0 } }, .private = true };
try db.saveDescriptor(allocator, &database, descriptor_priv);

const extended_pubkey = bip32.ExtendedPublicKey{ .key = pubkey, .chaincode = descriptor_privkey.chaincode };
const addr_pubkey = try extended_pubkey.address(pubkey_version, 3, fingerprint, 2147483648);
const descriptor_pub = bip44.Descriptor{ .extended_key = addr_pubkey, .keypath = bip44.KeyPath(3){ .path = [3]u32{ bip44.bip_84_purpose, cointype, 0 } }, .private = false };
try db.saveDescriptor(allocator, &database, descriptor_pub);

std.debug.print("Wallet initialized\n", .{});
},
}
}

0 comments on commit a299fa0

Please sign in to comment.