Skip to content

Commit

Permalink
send tx cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
iskyd committed Oct 8, 2024
1 parent b3a5dff commit 7bae32f
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 38 deletions.
7 changes: 7 additions & 0 deletions src/bip44.zig
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ pub fn generatePublicFromAccountPublicKey(extended_pubkey: bip32.ExtendedPublicK
return index_extended_pubkey.key;
}

pub fn generatePrivateFromAccountPrivateKey(extended_privkey: bip32.ExtendedPrivateKey, change: u32, index: u32) ![32]u8 {
const change_extended_privkey = try bip32.deriveChildFromExtendedPrivateKey(extended_privkey, change);
const index_extended_privkey = try bip32.deriveChildFromExtendedPrivateKey(change_extended_privkey, index);

return index_extended_privkey.privatekey;
}

test "generateAccount" {
const addr_serialized = "tprv8ZgxMBicQKsPefj8cBDzcXJYcnvWBLQwG9sAvKyAYRPiLtdZXvdAmqtjzeHbX7ZX2LY8Sfb7SaLSJbGCFBPMFZdnmv4D7UebvyLTC974BA4".*;
const master_extended_privkey = try bip32.ExtendedPrivateKey.fromAddress(addr_serialized);
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/ecdsa.zig
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub const Signature = struct {
// nonce is used in tests to recreate deterministic signature.
// I don't like this parameter, using the same nonce can expose the private key, but I havent found any better solution
pub fn sign(pk: [32]u8, z: [32]u8, comptime nonce: ?u256) Signature {
comptime if (nonce == null and is_test == false) {
comptime if (nonce != null and is_test == false) {
unreachable;
};
const n = crypto.secp256k1_number_of_points;
Expand Down
57 changes: 45 additions & 12 deletions src/db/db.zig
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,31 @@ pub fn saveInputsAndMarkOutputs(db: *sqlite.Db, inputs: std.ArrayList(Input)) !v
try stmt_commit.exec(.{}, .{});
}

pub fn getOutput(db: *sqlite.Db, txid: [64]u8, vout: u32) !?Output {
const sql_output = "SELECT txid, vout, amount FROM outputs WHERE txid = ? AND vout = ?";
pub fn existsOutput(db: *sqlite.Db, txid: [64]u8, vout: u32) !bool {
const sql = "SELECT COUNT(*) AS total FROM outputs WHERE txid = ? AND vout = ?";
var stmt = try db.prepare(sql);
defer stmt.deinit();
const row = try stmt.one(struct { total: usize }, .{}, .{ .txid = txid, .vout = vout });
return row.?.total > 1;
}

pub fn getOutput(allocator: std.mem.Allocator, db: *sqlite.Db, txid: [64]u8, vout: u32) !?Output {
const sql_output = "SELECT txid, vout, amount, unspent, path FROM outputs WHERE txid = ? AND vout = ?";
var stmt = try db.prepare(sql_output);
defer stmt.deinit();
const row = try stmt.one(struct { txid: [64]u8, vout: u32, amount: u64 }, .{}, .{ .txid = txid, .vout = vout });
const row = try stmt.oneAlloc(struct { txid: [64]u8, vout: u32, amount: u64, unspent: bool, path: []u8 }, allocator, .{}, .{ .txid = txid, .vout = vout });

if (row != null) {
defer allocator.free(row.?.path);
return Output{
.txid = row.?.txid,
.vout = row.?.vout,
.amount = row.?.amount,
.unspent = row.?.unspent,
.keypath = try KeyPath(5).fromStr(row.?.path),
};
}

return null;
}

Expand Down Expand Up @@ -174,11 +187,11 @@ pub fn getBalance(db: *sqlite.Db, current_block: usize) !u64 {
}

// Memory ownership to the caller
pub fn getDescriptors(allocator: std.mem.Allocator, db: *sqlite.Db) ![]Descriptor {
const sql = "SELECT extended_key, path, private FROM descriptors;";
pub fn getDescriptors(allocator: std.mem.Allocator, db: *sqlite.Db, private: bool) ![]Descriptor {
const sql = "SELECT extended_key, path, private FROM descriptors WHERE private = ?;";
var stmt = try db.prepare(sql);
defer stmt.deinit();
const rows = try stmt.all(struct { extended_key: [111]u8, path: []const u8, private: bool }, allocator, .{}, .{});
const rows = try stmt.all(struct { extended_key: [111]u8, path: []const u8, private: bool }, allocator, .{}, .{ .private = private });
defer {
for (rows) |row| {
allocator.free(row.path);
Expand All @@ -194,7 +207,7 @@ pub fn getDescriptors(allocator: std.mem.Allocator, db: *sqlite.Db) ![]Descripto
return descriptors;
}

pub fn getDescriptor(allocator: std.mem.Allocator, db: *sqlite.Db, path: []u8, private: bool) !?Descriptor {
pub fn getDescriptor(allocator: std.mem.Allocator, db: *sqlite.Db, path: []const u8, private: bool) !?Descriptor {
const sql = "SELECT extended_key, path, private FROM descriptors WHERE path=? AND private=? LIMIT 1;";
var stmt = try db.prepare(sql);
defer stmt.deinit();
Expand Down Expand Up @@ -249,20 +262,40 @@ fn sqliteKeypathLastIndex(str: []const u8) u32 {
return k.path[4];
}

pub fn getLastUsedIndexFromOutputs(db: *sqlite.Db) !?u32 {
const sql_count = "SELECT COUNT(*) as total from outputs;";
pub fn getLastUsedIndexFromOutputs(db: *sqlite.Db, base_path: []u8) !?u32 {
const sql_count = "SELECT COUNT(*) as total from outputs WHERE path LIKE ?;";
var stmt_count = try db.prepare(sql_count);
defer stmt_count.deinit();
const row_count = try stmt_count.one(struct { total: usize }, .{}, .{});
const row_count = try stmt_count.one(struct { total: usize }, .{}, .{base_path});
if (row_count.?.total == 0) {
return null;
}

try db.createScalarFunction("KEYPATH_LAST_INDEX", sqliteKeypathLastIndex, .{});
const sql = "SELECT MAX(KEYPATH_LAST_INDEX(path)) AS last FROM outputs;";
const sql = "SELECT MAX(KEYPATH_LAST_INDEX(path)) AS last FROM outputs WHERE path LIKE ?;";
var stmt = try db.prepare(sql);
defer stmt.deinit();
const row = try stmt.one(struct { last: u32 }, .{}, .{});
const row = try stmt.one(struct { last: u32 }, .{}, .{base_path});
assert(row != null); // a row must exists since the count is > 0
return row.?.last;
}

pub fn getUnspentOutputs(allocator: std.mem.Allocator, db: *sqlite.Db) ![]Output {
const sql = "SELECT txid, vout, amount, unspent, path FROM outputs WHERE unspent = true";
var stmt = try db.prepare(sql);
defer stmt.deinit();
const rows = try stmt.all(struct { txid: [64]u8, vout: u32, amount: u64, unspent: bool, path: []u8 }, allocator, .{}, .{});

defer {
for (rows) |row| {
allocator.free(row.path);
}
allocator.free(rows);
}

var outputs = try allocator.alloc(Output, rows.len);
for (rows, 0..) |row, i| {
outputs[i] = Output{ .txid = row.txid, .vout = row.vout, .amount = row.amount, .unspent = row.unspent, .keypath = try KeyPath(5).fromStr(row.path) };
}
return outputs;
}
8 changes: 4 additions & 4 deletions src/indexer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn main() !void {
}

// Load descriptors
const descriptors = try db.getDescriptors(allocator, &database);
const descriptors = try db.getDescriptors(allocator, &database, false); // only public descriptors
defer allocator.free(descriptors);

for (descriptors) |descriptor| {
Expand Down Expand Up @@ -284,10 +284,10 @@ fn getInputsFor(allocator: std.mem.Allocator, database: *sqlite.Db, transactions
continue;
}
const input = transaction.inputs.items[i];
const existing = try db.getOutput(database, input.prevout.?.txid, input.prevout.?.vout);
if (existing != null) {
const exists = try db.existsOutput(database, input.prevout.?.txid, input.prevout.?.vout);
if (exists == true) {
const txid = try transaction.getTXID();
try inputs.append(.{ .txid = txid, .output_txid = existing.?.txid, .output_vout = existing.?.vout });
try inputs.append(.{ .txid = txid, .output_txid = input.prevout.?.txid, .output_vout = input.prevout.?.vout });
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/tx.zig
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ pub const Transaction = struct {
try writer.print(" script pubkey: {s}\n", .{output.script_pubkey});
try writer.print(" amount: {d}\n\n", .{output.amount});
}

if (self.witness.items.len > 0) {
try writer.print("Witness: \n", .{});
for (0..self.witness.items.len) |i| {
const witness = self.witness.items[i];
for (0..witness.stack_items.items.len) |j| {
const stack_item = witness.stack_items.items[j];
try writer.print(" item: {s}\n", .{stack_item.item});
}
}
}
}
};

Expand Down Expand Up @@ -307,7 +318,7 @@ test "createTx" {
}

// [72]u8 = 64 txid + 8 vout
pub fn signTx(allocator: std.mem.Allocator, tx: *Transaction, privkey: [32]u8, pubkeys: std.AutoHashMap([72]u8, bip32.PublicKey), comptime nonce: ?u256) !void {
pub fn signTx(allocator: std.mem.Allocator, tx: *Transaction, privkeys: std.AutoHashMap([72]u8, [32]u8), pubkeys: std.AutoHashMap([72]u8, bip32.PublicKey), comptime nonce: ?u256) !void {
const inputs_preimage_hash = try getTxInputsPreImageHash(allocator, tx.inputs.items);
const inputs_sequences_preimage_hash = try getTxInputsSequencesPreImageHash(allocator, tx.inputs.items);
const outputs_preimage_hash = try getTxOutputsPreImageHash(allocator, tx.outputs.items);
Expand All @@ -321,6 +332,7 @@ pub fn signTx(allocator: std.mem.Allocator, tx: *Transaction, privkey: [32]u8, p
try utils.intToHexStr(u32, @byteSwap(input.prevout.?.vout), &vout_hex);
_ = try std.fmt.bufPrint(&key, "{s}{s}", .{ input.prevout.?.txid, vout_hex });
const pubkey = pubkeys.get(key).?;
const privkey = privkeys.get(key).?;
const preimage_hash = try getPreImageHash(tx.version, inputs_preimage_hash, inputs_sequences_preimage_hash, outputs_preimage_hash, tx.locktime, input, pubkey, sighash_type);
const witness = try createWitness(allocator, preimage_hash, privkey, pubkey, sighash_type, nonce);
try tx.addWitness(witness);
Expand Down
Loading

0 comments on commit 7bae32f

Please sign in to comment.