diff --git a/build.zig b/build.zig index c18f104..d4e9df3 100644 --- a/build.zig +++ b/build.zig @@ -190,6 +190,7 @@ pub fn build(b: *std.Build) !void { .root_source_file = b.path("src/lib.zig"), .target = target, .optimize = optimize, + .single_threaded = false, }); lib_unit_tests.root_module.addImport("zul", zul); lib_unit_tests.root_module.addImport("secp256k1", secp256k1.module("secp256k1")); diff --git a/src/core/database/mint_memory.zig b/src/core/database/mint_memory.zig new file mode 100644 index 0000000..2c33756 --- /dev/null +++ b/src/core/database/mint_memory.zig @@ -0,0 +1,592 @@ +const std = @import("std"); +const nuts = @import("../nuts/lib.zig"); +const dhke = @import("../dhke.zig"); +const zul = @import("zul"); +const secp256k1 = @import("secp256k1"); + +const Arened = @import("../../helper/helper.zig").Parsed; +const MintKeySetInfo = @import("../mint/mint.zig").MintKeySetInfo; +const MintQuote = @import("../mint/mint.zig").MintQuote; +const MeltQuote = @import("../mint/mint.zig").MeltQuote; + +/// TODO simple solution for rw locks, use on all structure, as temp solution +/// Mint Memory Database +pub const MintMemoryDatabase = struct { + const Self = @This(); + + lock: std.Thread.RwLock, + + active_keysets: std.AutoHashMap(nuts.CurrencyUnit, nuts.Id), + keysets: std.AutoHashMap(nuts.Id, MintKeySetInfo), + mint_quotes: std.AutoHashMap([16]u8, MintQuote), + melt_quotes: std.AutoHashMap([16]u8, MeltQuote), + proofs: std.AutoHashMap([33]u8, nuts.Proof), + proof_state: std.AutoHashMap([33]u8, nuts.nut07.State), + blinded_signatures: std.AutoHashMap([33]u8, nuts.BlindSignature), + + allocator: std.mem.Allocator, + + pub fn init( + allocator: std.mem.Allocator, + ) !zul.Managed(Self) { + var arena = try allocator.create(std.heap.ArenaAllocator); + errdefer allocator.destroy(arena); + + arena.* = std.heap.ArenaAllocator.init(allocator); + errdefer arena.deinit(); + + const active_keysets = std.AutoHashMap(nuts.CurrencyUnit, nuts.Id).init(arena.allocator()); + + const keysets = std.AutoHashMap(nuts.Id, MintKeySetInfo).init(arena.allocator()); + + const mint_quotes = std.AutoHashMap([16]u8, MintQuote).init(arena.allocator()); + + const melt_quotes = std.AutoHashMap([16]u8, MeltQuote).init(arena.allocator()); + + const proofs = std.AutoHashMap([33]u8, nuts.Proof).init(arena.allocator()); + + const proof_state = std.AutoHashMap([33]u8, nuts.nut07.State).init(arena.allocator()); + + const blinded_signatures = std.AutoHashMap([33]u8, nuts.BlindSignature).init(arena.allocator()); + + return .{ + .value = .{ + .lock = .{}, + .active_keysets = active_keysets, + .keysets = keysets, + .mint_quotes = mint_quotes, + .melt_quotes = melt_quotes, + .proofs = proofs, + .proof_state = proof_state, + .blinded_signatures = blinded_signatures, + .allocator = arena.allocator(), + }, + .arena = arena, + }; + } + + pub fn setActiveKeyset(self: *Self, unit: nuts.CurrencyUnit, id: nuts.Id) !void { + self.lock.lock(); + defer self.lock.unlock(); + + try self.active_keysets.put(unit, id); + } + + pub fn getActiveKeysetId(self: *Self, unit: nuts.CurrencyUnit) ?nuts.Id { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + return self.active_keysets.get(unit); + } + + /// keyset inside is cloned, so caller own keyset + pub fn addKeysetInfo(self: *Self, keyset: MintKeySetInfo) !void { + self.lock.lock(); + defer self.lock.unlock(); + + try self.keysets.put(keyset.id, try keyset.clone(self.allocator)); + } + + /// caller own result, so responsible to free + pub fn getKeysetInfo(self: *Self, allocator: std.mem.Allocator, keyset_id: nuts.Id) !?MintKeySetInfo { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + return if (self.keysets.get(keyset_id)) |ks| try ks.clone(allocator) else null; + } + + pub fn getKeysetInfos(self: *Self, allocator: std.mem.Allocator) !Arened(std.ArrayList(MintKeySetInfo)) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var res = try Arened(std.ArrayList(MintKeySetInfo)).init(allocator); + errdefer res.deinit(); + + res.value = try std.ArrayList(MintKeySetInfo).initCapacity(res.arena.allocator(), self.keysets.count()); + + var it = self.keysets.valueIterator(); + + while (it.next()) |v| { + res.value.appendAssumeCapacity(try v.clone(res.arena.allocator())); + } + + return res; + } + + pub fn addMintQuote(self: *Self, quote: MintQuote) !void { + self.lock.lock(); + defer self.lock.unlock(); + // TODO clone quote + + try self.mint_quotes.put(quote.id, quote); + } + + // caller must free MintQuote + pub fn getMintQuote(self: *Self, allocator: std.mem.Allocator, quote_id: [16]u8) !?MintQuote { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + const quote = self.mint_quotes.get(quote_id) orelse return null; + + return try quote.clone(allocator); + } + + pub fn updateMintQuoteState( + self: *Self, + quote_id: [16]u8, + state: nuts.nut04.QuoteState, + ) !nuts.nut04.QuoteState { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var mint_quote = self.mint_quotes.getPtr(quote_id) orelse return error.UnknownQuote; + + const current_state = mint_quote.state; + mint_quote.state = state; + + return current_state; + } + + /// caller must free array list and every elements + pub fn getMintQuotes(self: *Self, allocator: std.mem.Allocator) !std.ArrayList(MintQuote) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var it = self.mint_quotes.valueIterator(); + + var result = try std.ArrayList(MintQuote).initCapacity(allocator, it.len); + errdefer { + for (result.items) |res| res.deinit(allocator); + result.deinit(); + } + + while (it.next()) |el| { + result.appendAssumeCapacity(try el.clone(allocator)); + } + + return result; + } + + /// caller responsible to free resources + pub fn getMintQuoteByRequestLookupId( + self: *Self, + allocator: std.mem.Allocator, + request: []const u8, + ) !?MintQuote { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + // no need in free resources due arena + const quotes = try self.getMintQuotes(arena.allocator()); + for (quotes.items) |q| { + // if we found, cloning with allocator, so caller responsible on free resources + if (std.mem.eql(u8, q.request_lookup_id, request)) return try q.clone(allocator); + } + + return null; + } + /// caller responsible to free resources + pub fn getMintQuoteByRequest( + self: *Self, + allocator: std.mem.Allocator, + request: []const u8, + ) !?MintQuote { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + // no need in free resources due arena + const quotes = try self.getMintQuotes(arena.allocator()); + for (quotes.items) |q| { + // if we found, cloning with allocator, so caller responsible on free resources + if (std.mem.eql(u8, q.request, request)) return try q.clone(allocator); + } + + return null; + } + + pub fn removeMintQuoteState( + self: *Self, + quote_id: [16]u8, + ) !void { + self.lock.lock(); + defer self.lock.unlock(); + + const kv = self.mint_quotes.fetchRemove(quote_id) orelse return; + kv.value.deinit(self.allocator); + } + + pub fn addMeltQuote(self: *Self, quote: MeltQuote) !void { + self.lock.lock(); + defer self.lock.unlock(); + + try self.melt_quotes.put(quote.id, quote); + } + + // caller must free MeltQuote + pub fn getMeltQuote(self: *Self, allocator: std.mem.Allocator, quote_id: [16]u8) !?MeltQuote { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + const quote = self.melt_quotes.get(quote_id) orelse return null; + + return try quote.clone(allocator); + } + + pub fn updateMeltQuoteState( + self: *Self, + quote_id: [16]u8, + state: nuts.nut05.QuoteState, + ) !nuts.nut05.QuoteState { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var melt_quote = self.melt_quotes.getPtr(quote_id) orelse return error.UnknownQuote; + + const current_state = melt_quote.state; + melt_quote.state = state; + + return current_state; + } + + /// caller must free array list and every elements + pub fn getMeltQuotes(self: *Self, allocator: std.mem.Allocator) !std.ArrayList(MeltQuote) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var it = self.melt_quotes.valueIterator(); + + var result = try std.ArrayList(MeltQuote).initCapacity(allocator, it.len); + errdefer { + for (result.items) |res| res.deinit(allocator); + result.deinit(); + } + + while (it.next()) |el| { + result.appendAssumeCapacity(try el.clone(allocator)); + } + + return result; + } + + /// caller responsible to free resources + pub fn getMeltQuoteByRequestLookupId( + self: *Self, + allocator: std.mem.Allocator, + request: []const u8, + ) !?MeltQuote { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + // no need in free resources due arena + const quotes = try self.getMeltQuotes(arena.allocator()); + for (quotes.items) |q| { + // if we found, cloning with allocator, so caller responsible on free resources + if (std.mem.eql(u8, q.request_lookup_id, request)) return try q.clone(allocator); + } + + return null; + } + /// caller responsible to free resources + pub fn getMeltQuoteByRequest( + self: *Self, + allocator: std.mem.Allocator, + request: []const u8, + ) !?MeltQuote { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + // no need in free resources due arena + const quotes = try self.getMeltQuotes(arena.allocator()); + for (quotes.items) |q| { + // if we found, cloning with allocator, so caller responsible on free resources + if (std.mem.eql(u8, q.request, request)) return try q.clone(allocator); + } + + return null; + } + + pub fn removeMeltQuoteState( + self: *Self, + quote_id: [16]u8, + ) void { + self.lock.lock(); + defer self.lock.unlock(); + + const kv = self.melt_quotes.fetchRemove(quote_id) orelse return; + kv.value.deinit(self.allocator); + } + + pub fn addProofs(self: *Self, proofs: []const nuts.Proof) !void { + self.lock.lock(); + defer self.lock.unlock(); + + // ensuring that capacity of proofs enough for array + try self.proofs.ensureTotalCapacity(@intCast(proofs.len)); + + // we need full copy + for (proofs) |proof| { + const secret_point = try dhke.hashToCurve(proof.secret.toBytes()); + self.proofs.putAssumeCapacity(secret_point.serialize(), try proof.clone(self.allocator)); + } + } + + // caller must free resources + pub fn getProofsByYs(self: *Self, allocator: std.mem.Allocator, ys: []const secp256k1.PublicKey) !std.ArrayList(?nuts.Proof) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var proofs = try std.ArrayList(?nuts.Proof).initCapacity(allocator, ys.len); + errdefer proofs.deinit(); + + for (ys) |y| { + proofs.appendAssumeCapacity(self.proofs.get(y.serialize())); + } + + return proofs; + } + + // caller must deinit result std.ArrayList + pub fn updateProofsStates( + self: *Self, + allocator: std.mem.Allocator, + ys: []const secp256k1.PublicKey, + proof_state: nuts.nut07.State, + ) !std.ArrayList(?nuts.nut07.State) { + self.lock.lock(); + defer self.lock.unlock(); + + var states = try std.ArrayList(?nuts.nut07.State).initCapacity(allocator, ys.len); + errdefer states.deinit(); + + for (ys) |y| { + const kv = try self.proof_state.fetchPut(y.serialize(), proof_state); + states.appendAssumeCapacity(if (kv) |_kv| _kv.value else null); + } + + return states; + } + + // caller must free result + pub fn getProofsStates(self: *Self, allocator: std.mem.Allocator, ys: []secp256k1.PublicKey) !std.ArrayList(?nuts.nut07.State) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var states = try std.ArrayList(?nuts.nut07.State).initCapacity(allocator, ys.len); + errdefer states.deinit(); + + for (ys) |y| { + states.appendAssumeCapacity(self.proof_state.get(y.serialize())); + } + + return states; + } + + // result through Arena, for more easy deallocation + pub fn getProofsByKeysetId( + self: *Self, + allocator: std.mem.Allocator, + id: nuts.Id, + ) !Arened(std.meta.Tuple(&.{ + std.ArrayList(nuts.Proof), + std.ArrayList(?nuts.nut07.State), + })) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var result = try Arened(std.meta.Tuple(&.{ + std.ArrayList(nuts.Proof), + std.ArrayList(?nuts.nut07.State), + })).init(allocator); + errdefer result.deinit(); + + result.value[0] = std.ArrayList(nuts.Proof).init(result.arena.allocator()); + + var proof_ys = std.ArrayList(secp256k1.PublicKey).init(result.arena.allocator()); + defer proof_ys.deinit(); + + var proofs_it = self.proofs.valueIterator(); + while (proofs_it.next()) |proof| { + if (std.meta.eql(id.id, proof.keyset_id.id)) { + const c_proof = try proof.clone(result.arena.allocator()); + try result.value[0].append(c_proof); + try proof_ys.append(try c_proof.y()); + } + } + + const states = try self.getProofsStates(result.arena.allocator(), proof_ys.items); + + std.debug.assert(states.items.len == result.value[0].items.len); + + result.value[1] = states; + + return result; + } + + pub fn addBlindSignatures( + self: *Self, + blinded_messages: []const secp256k1.PublicKey, + blind_signatures: []const nuts.BlindSignature, + ) !void { + self.lock.lock(); + defer self.lock.unlock(); + + for (blinded_messages, blind_signatures) |blinded_message, blind_signature| { + try self.blinded_signatures.put(blinded_message.serialize(), blind_signature); + } + } + + pub fn getBlindSignatures( + self: *Self, + allocator: std.mem.Allocator, + blinded_messages: []const secp256k1.PublicKey, + ) !std.ArrayList(?nuts.BlindSignature) { + var signatures = try std.ArrayList(?nuts.BlindSignature).initCapacity(allocator, blinded_messages.len); + + self.lock.lockShared(); + defer self.lock.unlockShared(); + + for (blinded_messages) |blinded_message| { + signatures.appendAssumeCapacity(self.blinded_signatures.get(blinded_message.serialize())); + } + + return signatures; + } + + /// caller response to free resources + pub fn getBlindSignaturesForKeyset( + self: *Self, + allocator: std.mem.Allocator, + keyset_id: nuts.Id, + ) !std.ArrayList(nuts.BlindSignature) { + self.lock.lockShared(); + defer self.lock.unlockShared(); + + var result = std.ArrayList(nuts.BlindSignature).init(allocator); + errdefer result.deinit(); + + var it = self.blinded_signatures.valueIterator(); + while (it.next()) |b| { + if (std.meta.eql(b.keyset_id, keyset_id)) { + try result.append(b.*); + } + } + + return result; + } +}; + +pub fn worker(m: *MintMemoryDatabase, unit: nuts.CurrencyUnit) !void { + const id = try nuts.Id.fromStr("FFFFFFFFFFFFFFFF"); + + try m.setActiveKeyset(unit, id); + + const id1 = try nuts.Id.fromStr("FFFFFFFFFFFFFFFC"); + + try m.addKeysetInfo(.{ + .id = id1, + .unit = .sat, + .active = true, + .valid_from = 11, + .valid_to = null, + .derivation_path = &.{}, + .derivation_path_index = null, + .max_order = 5, + .input_fee_ppk = 0, + }); + + try m.addKeysetInfo(.{ + .id = id1, + .unit = .sat, + .active = true, + .valid_from = 11, + .valid_to = null, + .derivation_path = &.{}, + .derivation_path_index = null, + .max_order = 5, + .input_fee_ppk = 0, + }); +} + +test MintMemoryDatabase { + var db_arened = try MintMemoryDatabase.init(std.testing.allocator); + defer db_arened.deinit(); + var db = &db_arened.value; + var rnd = std.Random.DefaultPrng.init(std.testing.random_seed); + var rand = rnd.random(); + + // active keyset + { + var id = nuts.Id{ + .version = .version00, + .id = undefined, + }; + rand.bytes(&id.id); + + try db.setActiveKeyset(.sat, id); + try std.testing.expectEqualDeep(db.getActiveKeysetId(.sat).?, id); + } + + { + var id = nuts.Id{ + .version = .version00, + .id = undefined, + }; + rand.bytes(&id.id); + + const info: MintKeySetInfo = .{ + .id = id, + .unit = .sat, + .active = true, + .input_fee_ppk = 10, + .valid_from = 0, + .valid_to = 10, + .derivation_path = &.{}, + .derivation_path_index = 45, + .max_order = 14, + }; + + try db.addKeysetInfo(info); + const info_got = (try db.getKeysetInfo(std.testing.allocator, id)).?; + defer info_got.deinit(std.testing.allocator); + try std.testing.expectEqualDeep( + info, + info_got, + ); + + const infos = try db.getKeysetInfos(std.testing.allocator); + defer infos.deinit(); + + try std.testing.expectEqual(1, infos.value.items.len); + try std.testing.expectEqualDeep(info, infos.value.items[0]); + } + // TODO finish other tests +} + +test "multithread" { + var shared_data = try MintMemoryDatabase.init(std.testing.allocator); + defer shared_data.deinit(); + + const thread1 = try std.Thread.spawn(.{ + .allocator = std.testing.allocator, + }, worker, .{ + &shared_data.value, + .sat, + }); + + const thread2 = try std.Thread.spawn(.{ + .allocator = std.testing.allocator, + }, worker, .{ + &shared_data.value, + .msat, + }); + + // std.log.warn("waiting threads", .{}); + thread1.join(); + thread2.join(); + + std.time.sleep(2e9); + const keyset_id = shared_data.value.getActiveKeysetId(.sat); + _ = keyset_id; // autofix + + // std.log.warn("keysets, count = {any}", .{keyset_id}); +} diff --git a/src/core/lib.zig b/src/core/lib.zig index 5b56d5f..4baa37d 100644 --- a/src/core/lib.zig +++ b/src/core/lib.zig @@ -2,3 +2,5 @@ pub const dhke = @import("dhke.zig"); pub const secret = @import("secret.zig"); pub const amount = @import("amount.zig"); pub const nuts = @import("nuts/lib.zig"); +pub const mint = @import("mint/mint.zig"); +pub const mint_memory = @import("database/mint_memory.zig"); diff --git a/src/mint/database/database.zig b/src/core/mint/database/database.zig similarity index 100% rename from src/mint/database/database.zig rename to src/core/mint/database/database.zig diff --git a/src/mint/lightning/invoices/constants.zig b/src/core/mint/lightning/invoices/constants.zig similarity index 100% rename from src/mint/lightning/invoices/constants.zig rename to src/core/mint/lightning/invoices/constants.zig diff --git a/src/mint/lightning/invoices/error.zig b/src/core/mint/lightning/invoices/error.zig similarity index 100% rename from src/mint/lightning/invoices/error.zig rename to src/core/mint/lightning/invoices/error.zig diff --git a/src/mint/lightning/invoices/invoice.zig b/src/core/mint/lightning/invoices/invoice.zig similarity index 99% rename from src/mint/lightning/invoices/invoice.zig rename to src/core/mint/lightning/invoices/invoice.zig index 9d90ba4..37f3f3b 100644 --- a/src/mint/lightning/invoices/invoice.zig +++ b/src/core/mint/lightning/invoices/invoice.zig @@ -1,6 +1,6 @@ const std = @import("std"); const errors = @import("error.zig"); -const core = @import("../../../core/lib.zig"); +const core = @import("../../../lib.zig"); const constants = @import("constants.zig"); const bech32 = @import("bitcoin").bech32; const secp256k1 = @import("secp256k1"); diff --git a/src/mint/lightning/invoices/lib.zig b/src/core/mint/lightning/invoices/lib.zig similarity index 100% rename from src/mint/lightning/invoices/lib.zig rename to src/core/mint/lightning/invoices/lib.zig diff --git a/src/mint/lightning/invoices/ripemd160.zig b/src/core/mint/lightning/invoices/ripemd160.zig similarity index 100% rename from src/mint/lightning/invoices/ripemd160.zig rename to src/core/mint/lightning/invoices/ripemd160.zig diff --git a/src/mint/lightning/lib.zig b/src/core/mint/lightning/lib.zig similarity index 100% rename from src/mint/lightning/lib.zig rename to src/core/mint/lightning/lib.zig diff --git a/src/mint/lightning/lightning.zig b/src/core/mint/lightning/lightning.zig similarity index 100% rename from src/mint/lightning/lightning.zig rename to src/core/mint/lightning/lightning.zig diff --git a/src/mint/lightning/lnbits.zig b/src/core/mint/lightning/lnbits.zig similarity index 100% rename from src/mint/lightning/lnbits.zig rename to src/core/mint/lightning/lnbits.zig diff --git a/src/core/mint/mint.zig b/src/core/mint/mint.zig new file mode 100644 index 0000000..e0db3c0 --- /dev/null +++ b/src/core/mint/mint.zig @@ -0,0 +1,69 @@ +const std = @import("std"); +const core = @import("../lib.zig"); +const MintInfo = core.nuts.MintInfo; +const secp256k1 = @import("secp256k1"); +const bip32 = @import("bitcoin").bitcoin.bip32; +pub const MintQuote = @import("types.zig").MintQuote; +pub const MeltQuote = @import("types.zig").MeltQuote; + +/// Mint Fee Reserve +pub const FeeReserve = struct { + /// Absolute expected min fee + min_fee_reserve: core.amount.Amount, + /// Percentage expected fee + percent_fee_reserve: f32, +}; + +/// Mint Keyset Info +pub const MintKeySetInfo = struct { + /// Keyset [`Id`] + id: core.nuts.Id, + /// Keyset [`CurrencyUnit`] + unit: core.nuts.CurrencyUnit, + /// Keyset active or inactive + /// Mint will only issue new [`BlindSignature`] on active keysets + active: bool, + /// Starting unix time Keyset is valid from + valid_from: u64, + /// When the Keyset is valid to + /// This is not shown to the wallet and can only be used internally + valid_to: ?u64, + /// [`DerivationPath`] keyset + derivation_path: []const bip32.ChildNumber, + /// DerivationPath index of Keyset + derivation_path_index: ?u32, + /// Max order of keyset + max_order: u8, + /// Input Fee ppk + input_fee_ppk: u64 = 0, + + pub fn deinit(self: *const MintKeySetInfo, allocator: std.mem.Allocator) void { + allocator.free(self.derivation_path); + } + + pub fn clone(self: *const MintKeySetInfo, allocator: std.mem.Allocator) !MintKeySetInfo { + var cloned = self.*; + + const derivation_path = try allocator.alloc(bip32.ChildNumber, self.derivation_path.len); + errdefer allocator.free(derivation_path); + + @memcpy(derivation_path, self.derivation_path); + cloned.derivation_path = derivation_path; + + return cloned; + } +}; + +/// Cashu Mint +pub const Mint = struct { + /// Mint Url + mint_url: std.Uri, + /// Mint Info + mint_info: MintInfo, + /// Mint Storage backend + // pub localstore: Arc + Send + Sync>, + /// Active Mint Keysets + // keysets: Arc>>, + secp_ctx: secp256k1.Secp256k1, + xpriv: bip32.ExtendedPrivKey, +}; diff --git a/src/mint/routes/btconchain.zig b/src/core/mint/routes/btconchain.zig similarity index 100% rename from src/mint/routes/btconchain.zig rename to src/core/mint/routes/btconchain.zig diff --git a/src/mint/routes/default.zig b/src/core/mint/routes/default.zig similarity index 100% rename from src/mint/routes/default.zig rename to src/core/mint/routes/default.zig diff --git a/src/mint/routes/lib.zig b/src/core/mint/routes/lib.zig similarity index 100% rename from src/mint/routes/lib.zig rename to src/core/mint/routes/lib.zig diff --git a/src/mint/types.zig b/src/core/mint/types.zig similarity index 52% rename from src/mint/types.zig rename to src/core/mint/types.zig index 48f4e59..16d501d 100644 --- a/src/mint/types.zig +++ b/src/core/mint/types.zig @@ -1,9 +1,9 @@ -const nuts = @import("../core/lib.zig").nuts; +const nuts = @import("../lib.zig").nuts; const std = @import("std"); -const amount_lib = @import("../core/lib.zig").amount; -const CurrencyUnit = @import("../core/lib.zig").nuts.CurrencyUnit; -const MintQuoteState = @import("../core/lib.zig").nuts.nut04.QuoteState; -const MeltQuoteState = @import("../core/lib.zig").nuts.nut05.QuoteState; +const amount_lib = @import("../lib.zig").amount; +const CurrencyUnit = @import("../lib.zig").nuts.CurrencyUnit; +const MintQuoteState = @import("../lib.zig").nuts.nut04.QuoteState; +const MeltQuoteState = @import("../lib.zig").nuts.nut05.QuoteState; const zul = @import("zul"); /// Mint Quote Info @@ -47,6 +47,25 @@ pub const MintQuote = struct { .request_lookup_id = request_lookup_id, }; } + + pub fn deinit(self: *const MintQuote, allocator: std.mem.Allocator) void { + allocator.free(self.request_lookup_id); + allocator.free(self.request); + } + + pub fn clone(self: *const MintQuote, allocator: std.mem.Allocator) !MintQuote { + const request_lookup = try allocator.alloc(u8, self.request_lookup_id.len); + errdefer allocator.free(request_lookup); + + const request = try allocator.alloc(u8, self.request.len); + errdefer allocator.free(request); + + var cloned = self.*; + cloned.request = request; + cloned.request_lookup_id = request_lookup; + + return cloned; + } }; /// Melt Quote Info @@ -93,4 +112,38 @@ pub const MeltQuote = struct { .request_lookup_id = request_lookup_id, }; } + + pub fn deinit(self: *const MeltQuote, allocator: std.mem.Allocator) void { + allocator.free(self.request); + allocator.free(self.request_lookup_id); + if (self.payment_preimage) |preimage| allocator.free(preimage); + } + + pub fn clone(self: *const MeltQuote, allocator: std.mem.Allocator) !MeltQuote { + var cloned = self.*; + + const request_lookup_id = try allocator.alloc(u8, self.request_lookup_id.len); + errdefer allocator.free(cloned.request_lookup_id); + + @memcpy(request_lookup_id, self.request_lookup_id); + + const request = try allocator.alloc(u8, self.request.len); + errdefer allocator.free(request); + + @memcpy(request, self.request); + + cloned.request_lookup_id = request_lookup_id; + cloned.request = request; + + if (cloned.payment_preimage) |preimage| { + const preimage_cloned = try allocator.alloc(u8, self.request.len); + errdefer allocator.free(preimage_cloned); + + @memcpy(preimage_cloned, preimage); + + cloned.payment_preimage = preimage_cloned; + } + + return cloned; + } }; diff --git a/src/mint/url.zig b/src/core/mint/url.zig similarity index 100% rename from src/mint/url.zig rename to src/core/mint/url.zig diff --git a/src/core/nuts/lib.zig b/src/core/nuts/lib.zig index bb453f5..91babd1 100644 --- a/src/core/nuts/lib.zig +++ b/src/core/nuts/lib.zig @@ -7,6 +7,7 @@ pub usingnamespace @import("nut10/nut10.zig"); pub usingnamespace @import("nut09/nut09.zig"); pub const nut08 = @import("nut08/nut08.zig"); pub usingnamespace @import("nut07/nut07.zig"); +pub const nut07 = @import("nut07/nut07.zig"); pub usingnamespace @import("nut06/nut06.zig"); pub const nut05 = @import("nut05/nut05.zig"); pub const nut04 = @import("nut04/nut04.zig"); @@ -14,4 +15,4 @@ pub usingnamespace @import("nut03/nut03.zig"); pub usingnamespace @import("nut02/nut02.zig"); pub usingnamespace @import("nut01/nut01.zig"); -pub usingnamespace @import("nut00/lib.zig"); +pub usingnamespace @import("nut00/nut00.zig"); diff --git a/src/core/nuts/nut00/lib.zig b/src/core/nuts/nut00/nut00.zig similarity index 95% rename from src/core/nuts/nut00/lib.zig rename to src/core/nuts/nut00/nut00.zig index f7b3e3e..c3166fa 100644 --- a/src/core/nuts/nut00/lib.zig +++ b/src/core/nuts/nut00/nut00.zig @@ -45,6 +45,27 @@ pub const Proof = struct { if (self.witness) |w| w.deinit(); self.secret.deinit(allocator); } + + pub fn clone(self: *const Proof, allocator: std.mem.Allocator) !Proof { + var cloned = self.*; + + if (self.witness) |w| { + cloned.witness = try w.clone(allocator); + } + errdefer if (cloned.witness) |w| w.deinit(); + + cloned.secret = try self.secret.clone(allocator); + errdefer self.secret.deinit(allocator); + + return cloned; + } + + /// Get y from proof + /// + /// Where y is `hash_to_curve(secret)` + pub fn y(self: *const Proof) !secp256k1.PublicKey { + return try dhke.hashToCurve(self.secret.toBytes()); + } }; /// Witness @@ -55,6 +76,13 @@ pub const Witness = union(enum) { /// HTLC Witness htlc_witness: HTLCWitness, // TODO + pub fn clone(self: *const Witness, allocator: std.mem.Allocator) !Witness { + _ = self; // autofix + _ = allocator; // autofix + // TODO impl clone + return undefined; + } + pub fn jsonParse(allocator: std.mem.Allocator, _source: anytype, options: std.json.ParseOptions) !@This() { const parsed = try std.json.innerParse(std.json.Value, allocator, _source, options); diff --git a/src/core/nuts/nut02/nut02.zig b/src/core/nuts/nut02/nut02.zig index c93bdc7..fc20722 100644 --- a/src/core/nuts/nut02/nut02.zig +++ b/src/core/nuts/nut02/nut02.zig @@ -3,7 +3,7 @@ //! const std = @import("std"); -const CurrencyUnit = @import("../nut00/lib.zig").CurrencyUnit; +const CurrencyUnit = @import("../nut00/nut00.zig").CurrencyUnit; const Keys = @import("../nut01/nut01.zig").Keys; const MintKeys = @import("../nut01/nut01.zig").MintKeys; const MintKeyPair = @import("../nut01/nut01.zig").MintKeyPair; diff --git a/src/core/nuts/nut03/nut03.zig b/src/core/nuts/nut03/nut03.zig index 5bd7e7b..2e44908 100644 --- a/src/core/nuts/nut03/nut03.zig +++ b/src/core/nuts/nut03/nut03.zig @@ -2,22 +2,22 @@ //! //! -const BlindSignature = @import("../nut00/lib.zig").BlindSignature; -const BlindedMessage = @import("../nut00/lib.zig").BlindedMessage; -// const PreMintSecrets = @import("../nut00/lib.zig").PreMintSecrets; -const Proof = @import("../nut00/lib.zig").Proof; - -// /// Preswap information -// pub const PreSwap = struct { -// /// Preswap mint secrets -// pre_mint_secrets: PreMintSecrets, -// /// Swap request -// swap_request: SwapRequest, -// /// Amount to increment keyset counter by -// derived_secret_count: u32, -// /// Fee amount -// fee: u64, -// }; +const BlindSignature = @import("../nut00/nut00.zig").BlindSignature; +const BlindedMessage = @import("../nut00/nut00.zig").BlindedMessage; +const PreMintSecrets = @import("../nut00/nut00.zig").PreMintSecrets; +const Proof = @import("../nut00/nut00.zig").Proof; + +/// Preswap information +pub const PreSwap = struct { + /// Preswap mint secrets + pre_mint_secrets: PreMintSecrets, + /// Swap request + swap_request: SwapRequest, + /// Amount to increment keyset counter by + derived_secret_count: u32, + /// Fee amount + fee: u64, +}; /// Split Request [NUT-06] pub const SwapRequest = struct { diff --git a/src/core/nuts/nut04/nut04.zig b/src/core/nuts/nut04/nut04.zig index a061dd0..b059d4d 100644 --- a/src/core/nuts/nut04/nut04.zig +++ b/src/core/nuts/nut04/nut04.zig @@ -2,9 +2,9 @@ //! //! const std = @import("std"); -const CurrencyUnit = @import("../nut00/lib.zig").CurrencyUnit; -const Proof = @import("../nut00/lib.zig").Proof; -const PaymentMethod = @import("../nut00/lib.zig").PaymentMethod; +const CurrencyUnit = @import("../nut00/nut00.zig").CurrencyUnit; +const Proof = @import("../nut00/nut00.zig").Proof; +const PaymentMethod = @import("../nut00/nut00.zig").PaymentMethod; pub const QuoteState = enum { /// Quote has not been paid diff --git a/src/core/nuts/nut05/nut05.zig b/src/core/nuts/nut05/nut05.zig index d702ed1..d272dfe 100644 --- a/src/core/nuts/nut05/nut05.zig +++ b/src/core/nuts/nut05/nut05.zig @@ -1,13 +1,13 @@ //! NUT-05: Melting Tokens //! //! -const BlindSignature = @import("../nut00/lib.zig").BlindSignature; -const BlindedMessage = @import("../nut00/lib.zig").BlindedMessage; -const CurrencyUnit = @import("../nut00/lib.zig").CurrencyUnit; -const Proof = @import("../nut00/lib.zig").Proof; -const PaymentMethod = @import("../nut00/lib.zig").PaymentMethod; +const BlindSignature = @import("../nut00/nut00.zig").BlindSignature; +const BlindedMessage = @import("../nut00/nut00.zig").BlindedMessage; +const CurrencyUnit = @import("../nut00/nut00.zig").CurrencyUnit; +const Proof = @import("../nut00/nut00.zig").Proof; +const PaymentMethod = @import("../nut00/nut00.zig").PaymentMethod; const Mpp = @import("../nut15/nut15.zig").Mpp; -const Bolt11Invoice = @import("../../../mint/lightning/invoices/lib.zig").Bolt11Invoice; +const Bolt11Invoice = @import("../../mint/lightning/invoices/lib.zig").Bolt11Invoice; const std = @import("std"); /// Melt quote request [NUT-05] diff --git a/src/core/nuts/nut09/nut09.zig b/src/core/nuts/nut09/nut09.zig index 9ed9699..84fa3e3 100644 --- a/src/core/nuts/nut09/nut09.zig +++ b/src/core/nuts/nut09/nut09.zig @@ -2,8 +2,8 @@ //! //! const std = @import("std"); -const BlindedMessage = @import("../nut00/lib.zig").BlindedMessage; -const BlindSignature = @import("../nut00/lib.zig").BlindSignature; +const BlindedMessage = @import("../nut00/nut00.zig").BlindedMessage; +const BlindSignature = @import("../nut00/nut00.zig").BlindSignature; const helper = @import("../../../helper/helper.zig"); /// Restore Request [NUT-09] diff --git a/src/core/nuts/nut11/nut11.zig b/src/core/nuts/nut11/nut11.zig index f2c28f9..61dbe01 100644 --- a/src/core/nuts/nut11/nut11.zig +++ b/src/core/nuts/nut11/nut11.zig @@ -3,10 +3,10 @@ //! const std = @import("std"); -const BlindedMessage = @import("../nut00/lib.zig").BlindedMessage; -const Proof = @import("../nut00/lib.zig").Proof; +const BlindedMessage = @import("../nut00/nut00.zig").BlindedMessage; +const Proof = @import("../nut00/nut00.zig").Proof; const secp256k1 = @import("secp256k1"); -const Witness = @import("../nut00/lib.zig").Witness; +const Witness = @import("../nut00/nut00.zig").Witness; const Nut10Secret = @import("../nut10/nut10.zig").Secret; const Id = @import("../nut02/nut02.zig").Id; const helper = @import("../../../helper/helper.zig"); diff --git a/src/core/nuts/nut12/nuts12.zig b/src/core/nuts/nut12/nuts12.zig index cc72754..17b0f41 100644 --- a/src/core/nuts/nut12/nuts12.zig +++ b/src/core/nuts/nut12/nuts12.zig @@ -4,8 +4,8 @@ const std = @import("std"); const secp256k1 = @import("secp256k1"); const dhke = @import("../../dhke.zig"); -const Proof = @import("../nut00/lib.zig").Proof; -const BlindSignature = @import("../nut00/lib.zig").BlindSignature; +const Proof = @import("../nut00/nut00.zig").Proof; +const BlindSignature = @import("../nut00/nut00.zig").BlindSignature; const Id = @import("../nut02/nut02.zig").Id; /// Blinded Signature on Dleq diff --git a/src/core/nuts/nut13/nut13.zig b/src/core/nuts/nut13/nut13.zig index 1ae7b87..7a1abc1 100644 --- a/src/core/nuts/nut13/nut13.zig +++ b/src/core/nuts/nut13/nut13.zig @@ -6,7 +6,7 @@ const secp256k1 = @import("secp256k1"); const SecretKey = secp256k1.SecretKey; const Secret = @import("../../secret.zig").Secret; const Id = @import("../nut02/nut02.zig").Id; -const nut00 = @import("../nut00/lib.zig"); +const nut00 = @import("../nut00/nut00.zig"); const BlindedMessage = nut00.BlindedMessage; const PreMint = nut00.PreMint; const PreMintSecrets = nut00.PreMintSecrets; diff --git a/src/core/nuts/nut14/nut14.zig b/src/core/nuts/nut14/nut14.zig index cb7005d..aded148 100644 --- a/src/core/nuts/nut14/nut14.zig +++ b/src/core/nuts/nut14/nut14.zig @@ -2,8 +2,8 @@ //! //! const std = @import("std"); -const Witness = @import("../nut00/lib.zig").Witness; -const Proof = @import("../nut00/lib.zig").Proof; +const Witness = @import("../nut00/nut00.zig").Witness; +const Proof = @import("../nut00/nut00.zig").Proof; const Secret = @import("../nut10/nut10.zig").Secret; const Conditions = @import("../nut11/nut11.zig").Conditions; const secp256k1 = @import("secp256k1"); diff --git a/src/core/nuts/nut15/nut15.zig b/src/core/nuts/nut15/nut15.zig index 3c69725..74a1710 100644 --- a/src/core/nuts/nut15/nut15.zig +++ b/src/core/nuts/nut15/nut15.zig @@ -2,8 +2,8 @@ //! //! -const CurrencyUnit = @import("../nut00/lib.zig").CurrencyUnit; -const PaymentMethod = @import("../nut00/lib.zig").PaymentMethod; +const CurrencyUnit = @import("../nut00/nut00.zig").CurrencyUnit; +const PaymentMethod = @import("../nut00/nut00.zig").PaymentMethod; const std = @import("std"); diff --git a/src/mint/mint.zig b/src/mint/mint.zig deleted file mode 100644 index a0c714a..0000000 --- a/src/mint/mint.zig +++ /dev/null @@ -1,19 +0,0 @@ -const std = @import("std"); -const core = @import("../core/lib.zig"); -const MintInfo = core.nuts.MintInfo; -const secp256k1 = core.secp256k1; -const bip32 = core.bip32; - -/// Cashu Mint -pub const Mint = struct { - /// Mint Url - mint_url: std.Uri, - /// Mint Info - mint_info: MintInfo, - /// Mint Storage backend - // pub localstore: Arc + Send + Sync>, - /// Active Mint Keysets - // keysets: Arc>>, - secp_ctx: secp256k1.Secp256k1, - xpriv: bip32.ExtendedPrivKey, -};