From a299fa0c7e19dab00fa427e9259174b50292c9dc Mon Sep 17 00:00:00 2001 From: iskyd Date: Fri, 4 Oct 2024 22:18:32 +0200 Subject: [PATCH] init walle cli --- src/bip44.zig | 13 ++++++++ src/db/db.zig | 18 ++++++++++ src/walle.zig | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/walle.zig diff --git a/src/bip44.zig b/src/bip44.zig index 651ec1c..1bac316 100644 --- a/src/bip44.zig +++ b/src/bip44.zig @@ -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); diff --git a/src/db/db.zig b/src/db/db.zig index dce454c..3b179a1 100644 --- a/src/db/db.zig +++ b/src/db/db.zig @@ -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); diff --git a/src/walle.zig b/src/walle.zig new file mode 100644 index 0000000..e7a53a7 --- /dev/null +++ b/src/walle.zig @@ -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 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 \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", .{}); + }, + } +}