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..f9a216c 100644 --- a/src/core/lib.zig +++ b/src/core/lib.zig @@ -2,3 +2,6 @@ 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"); +pub const lightning = @import("lightning/lightning.zig"); diff --git a/src/core/lightning/lightning.zig b/src/core/lightning/lightning.zig new file mode 100644 index 0000000..4008420 --- /dev/null +++ b/src/core/lightning/lightning.zig @@ -0,0 +1,94 @@ +//! Mint Lightning +const root = @import("../../lib.zig"); +const Bolt11Invoice = root.lightning_invoices.Bolt11Invoice; +const Amount = root.core.amount.Amount; +const MeltQuoteState = root.core.nuts.nut05.QuoteState; +const CurrencyUnit = root.core.nuts.CurrencyUnit; + +/// Create invoice response +pub const CreateInvoiceResponse = struct { + /// Id that is used to look up the invoice from the ln backend + request_lookup_id: []const u8, + /// Bolt11 payment request + request: Bolt11Invoice, + /// Unix Expiry of Invoice + expiry: ?u64, +}; + +/// Pay invoice response +pub const PayInvoiceResponse = struct { + /// Payment hash + payment_hash: []const u8, + /// Payment Preimage + payment_preimage: ?[]const u8, + /// Status + status: MeltQuoteState, + /// Totoal Amount Spent + total_spent: Amount, +}; + +/// Payment quote response +pub const PaymentQuoteResponse = struct { + /// Request look up id + request_lookup_id: []const u8, + /// Amount + amount: Amount, + /// Fee required for melt + fee: u64, +}; + +/// Ln backend settings +pub const Settings = struct { + /// MPP supported + mpp: bool, + /// Min amount to mint + mint_settings: MintMeltSettings, + /// Max amount to mint + melt_settings: MintMeltSettings, + /// Base unit of backend + unit: CurrencyUnit, +}; + +/// Mint or melt settings +pub const MintMeltSettings = struct { + /// Min Amount + min_amount: Amount = 1, + /// Max Amount + max_amount: Amount = 500000, + /// Enabled + enabled: bool = true, +}; + +const MSAT_IN_SAT: u64 = 1000; + +/// Helper function to convert units +pub fn toUnit( + amount: u64, + current_unit: CurrencyUnit, + target_unit: CurrencyUnit, +) !Amount { + switch (current_unit) { + .sat => switch (target_unit) { + .sat => return amount, + .msat => return amount * MSAT_IN_SAT, + else => {}, + }, + .msat => switch (target_unit) { + .sat => return amount / MSAT_IN_SAT, + .msat => return amount, + else => {}, + }, + .usd => switch (target_unit) { + .usd => return amount, + else => {}, + }, + .eur => switch (target_unit) { + .eur => return amount, + else => {}, + }, + } + + return error.CannotConvertUnits; +} + +// TODO implement MintLighting interface here 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/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..a335b44 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("../../../lightning_invoices/invoice.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/fake_wallet/fake_wallet.zig b/src/fake_wallet/fake_wallet.zig new file mode 100644 index 0000000..ae91a9d --- /dev/null +++ b/src/fake_wallet/fake_wallet.zig @@ -0,0 +1,14 @@ +//! Fake LN Backend +//! +//! Used for testing where quotes are auto filled +const core = @import("../core/lib.zig"); +// const lightning = @import("../core/mint/lightning/lib.zig"). + +/// Fake Wallet +pub const FakeWallet = struct { + fee_reserve: core.mint.FeeReserve, + // sender: tokio::sync::mpsc::Sender, + // receiver: Arc>>>, + // mint_settings: MintMeltSettings, + // melt_settings: MintMeltSettings, +}; diff --git a/src/lib.zig b/src/lib.zig index 7ce22c1..e20b04b 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,5 +1,6 @@ const std = @import("std"); pub const core = @import("core/lib.zig"); +pub const lightning_invoices = @import("lightning_invoices/invoice.zig"); test { std.testing.log_level = .warn; diff --git a/src/mint/lightning/invoices/constants.zig b/src/lightning_invoices/constants.zig similarity index 100% rename from src/mint/lightning/invoices/constants.zig rename to src/lightning_invoices/constants.zig diff --git a/src/mint/lightning/invoices/error.zig b/src/lightning_invoices/error.zig similarity index 95% rename from src/mint/lightning/invoices/error.zig rename to src/lightning_invoices/error.zig index e7febe3..da7be84 100644 --- a/src/mint/lightning/invoices/error.zig +++ b/src/lightning_invoices/error.zig @@ -1,5 +1,3 @@ -const std = @import("std"); - pub const Bolt11ParseError = error{ // Bech32Error(bech32::Error), ParseAmountError, diff --git a/src/mint/lightning/invoices/invoice.zig b/src/lightning_invoices/invoice.zig similarity index 99% rename from src/mint/lightning/invoices/invoice.zig rename to src/lightning_invoices/invoice.zig index 9d90ba4..6da3ce4 100644 --- a/src/mint/lightning/invoices/invoice.zig +++ b/src/lightning_invoices/invoice.zig @@ -1,9 +1,8 @@ const std = @import("std"); -const errors = @import("error.zig"); -const core = @import("../../../core/lib.zig"); const constants = @import("constants.zig"); const bech32 = @import("bitcoin").bech32; const secp256k1 = @import("secp256k1"); +const errors = @import("error.zig"); /// Construct the invoice's HRP and signatureless data into a preimage to be hashed. pub fn constructInvoicePreimage(allocator: std.mem.Allocator, hrp_bytes: []const u8, data_without_signature: []const u5) !std.ArrayList(u8) { diff --git a/src/mint/lightning/invoices/lib.zig b/src/mint/lightning/invoices/lib.zig deleted file mode 100644 index 3605bed..0000000 --- a/src/mint/lightning/invoices/lib.zig +++ /dev/null @@ -1,4 +0,0 @@ -const std = @import("std"); - -pub usingnamespace @import("ripemd160.zig"); -pub usingnamespace @import("invoice.zig"); diff --git a/src/mint/lightning/invoices/ripemd160.zig b/src/mint/lightning/invoices/ripemd160.zig deleted file mode 100644 index 59be865..0000000 --- a/src/mint/lightning/invoices/ripemd160.zig +++ /dev/null @@ -1,263 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; - -pub const Ripemd160 = struct { - const Self = @This(); - pub const block_length = 64; - pub const digest_length = 20; - pub const Options = struct {}; - - s: [5]u32, - // Streaming Cache - buf: [64]u8 = undefined, - buf_len: u8 = 0, - total_len: u64 = 0, - - pub fn init(options: Options) Self { - _ = options; - return Self{ - .s = [_]u32{ - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }, - }; - } - - pub fn update(d: *Self, b: []const u8) void { - var off: usize = 0; - - // Partial buffer exists from previous update. Copy into buffer then hash. - if (d.buf_len != 0 and d.buf_len + b.len >= 64) { - off += 64 - d.buf_len; - @memcpy(d.buf[d.buf_len..][0..off], b[0..off]); - - d.round(&d.buf); - d.buf_len = 0; - } - - // Full middle blocks. - while (off + 64 <= b.len) : (off += 64) { - d.round(b[off..][0..64]); - } - - // Copy any remainder for next pass. - const b_slice = b[off..]; - @memcpy(d.buf[d.buf_len..][0..b_slice.len], b_slice); - d.buf_len += @as(u8, @intCast(b[off..].len)); - - d.total_len += b.len; - } - - fn blockToWords(block: *const [block_length]u8) [16]u32 { - var words: [16]u32 = undefined; - for (words, 0..) |_, i| { - // zig fmt: off - words[i] = 0; - words[i] |= (@as(u32, block[i * 4 + 3]) << 24); - words[i] |= (@as(u32, block[i * 4 + 2]) << 16); - words[i] |= (@as(u32, block[i * 4 + 1]) << 8); - words[i] |= (@as(u32, block[i * 4 + 0]) << 0); - // zig fmt: on - } - return words; - } - - fn func(j: usize, x: u32, y: u32, z: u32) u32 { - return switch (j) { - // f(j, x, y, z) = x XOR y XOR z (0 <= j <= 15) - 0...15 => x ^ y ^ z, - // f(j, x, y, z) = (x AND y) OR (NOT(x) AND z) (16 <= j <= 31) - 16...31 => (x & y) | (~x & z), - // f(j, x, y, z) = (x OR NOT(y)) XOR z (32 <= j <= 47) - 32...47 => (x | ~y) ^ z, - // f(j, x, y, z) = (x AND z) OR (y AND NOT(z)) (48 <= j <= 63) - 48...63 => (x & z) | (y & ~z), - // f(j, x, y, z) = x XOR (y OR NOT(z)) (64 <= j <= 79) - // !!! omg xor and or 64 - 64...79 => x ^ (y | ~z), - else => unreachable, - }; - } - - fn round(d: *Self, b: *const [block_length]u8) void { - var leftA = d.s[0]; - var leftB = d.s[1]; - var leftC = d.s[2]; - var leftD = d.s[3]; - var leftE = d.s[4]; - - var rightA = d.s[0]; - var rightB = d.s[1]; - var rightC = d.s[2]; - var rightD = d.s[3]; - var rightE = d.s[4]; - - const words: [16]u32 = blockToWords(b); - var tmp: u32 = undefined; - var j: usize = 0; - while (j < 80) : (j += 1) { - // zig fmt: off - tmp = std.math.rotl(u32, leftA - +% func(j, leftB, leftC, leftD) - +% words[left_selecting_words[j]] - +% left_K[j / 16], - left_tmp_shift_amount[j]) +% leftE; - // zig fmt: on - leftA = leftE; - leftE = leftD; - leftD = std.math.rotl(u32, leftC, 10); - leftC = leftB; - leftB = tmp; - - // zig fmt: off - tmp = std.math.rotl(u32, rightA - +% func(79 - j, rightB, rightC, rightD) - +% words[right_selecting_words[j]] - +% right_K[j / 16], - right_tmp_shift_amount[j]) +% rightE; - // zig fmt: on - rightA = rightE; - rightE = rightD; - rightD = std.math.rotl(u32, rightC, 10); - rightC = rightB; - rightB = tmp; - } - - tmp = d.s[1] +% leftC +% rightD; - d.s[1] = d.s[2] +% leftD +% rightE; - d.s[2] = d.s[3] +% leftE +% rightA; - d.s[3] = d.s[4] +% leftA +% rightB; - d.s[4] = d.s[0] +% leftB +% rightC; - d.s[0] = tmp; - } - - pub fn final(d: *Self, out: *[digest_length]u8) void { - // The buffer here will never be completely full. - @memset(d.buf[d.buf_len..], 0); - - // Append padding bits. - d.buf[d.buf_len] = 0x80; - d.buf_len += 1; - - // > 448 mod 512 so need to add an extra round to wrap around. - if (64 - d.buf_len < 8) { - d.round(d.buf[0..]); - @memset(d.buf[0..], 0); - } - - // Append message length in more simple way - const len = (d.total_len * 8); - mem.writeInt(u64, d.buf[56..64], len, .little); - - d.round(d.buf[0..]); - - for (d.s, 0..) |s, j| { - mem.writeInt(u32, out[4 * j ..][0..4], s, .little); - } - } - - pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void { - var d = Ripemd160.init(options); - d.update(b); - d.final(out); - } -}; - -test "test vectors" { - const input = [_][]const u8{ - "", - "a", - "abc", - "message digest", - "abcdefghijklmnopqrstuvwxyz", - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", - "1234567890" ** 8, - "a" ** 1000000, - }; - const output = [_][]const u8{ - "9c1185a5c5e9fc54612808977ee8f548b2258d31", - "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", - "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", - "5d0689ef49d2fae572b881b123a85ffa21595f36", - "f71c27109c692c1b56bbdceb5b9d2865b3708dbc", - "12a053384a9c0c88e405a06c27dcf49ada62eb2b", - "b0e20b6e3116640286ed3a87a5713079b21f5189", - "9b752e45573d4b39f4dbd3323cab82bf63326bfb", - "52783243c1697bdbe16d37f97f68f08325dc1528", - }; - for (0..input.len) |i| { - var expected_output: [Ripemd160.digest_length]u8 = undefined; - _ = try std.fmt.hexToBytes(&expected_output, output[i]); - var actual_output: [Ripemd160.digest_length]u8 = undefined; - Ripemd160.hash(input[i], &actual_output, .{}); - try testing.expectEqualSlices(u8, &expected_output, &actual_output); - } -} - -test "streaming" { - var h = Ripemd160.init(.{}); - var out: [Ripemd160.digest_length]u8 = undefined; - h.final(&out); - try testing.expectEqualSlices(u8, &[_]u8{ - 0x9c, 0x11, 0x85, 0xa5, 0xc5, 0xe9, 0xfc, 0x54, 0x61, 0x28, - 0x08, 0x97, 0x7e, 0xe8, 0xf5, 0x48, 0xb2, 0x25, 0x8d, 0x31, - }, &out); - - h = Ripemd160.init(.{}); - h.update("abc"); - h.final(&out); - try testing.expectEqualSlices(u8, &[_]u8{ - 0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a, 0x9b, 0x04, - 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87, 0xf1, 0x5a, 0x0b, 0xfc, - }, &out); - - h = Ripemd160.init(.{}); - h.update("a"); - h.update("b"); - h.update("c"); - h.final(&out); - try testing.expectEqualSlices(u8, &[_]u8{ - 0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a, 0x9b, 0x04, - 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87, 0xf1, 0x5a, 0x0b, 0xfc, - }, &out); -} - -const left_selecting_words = [80]u32{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, - 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, - 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, - 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, -}; - -const right_selecting_words = [80]u32{ - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, - 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, - 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, - 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, - 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, -}; - -const left_tmp_shift_amount = [80]u32{ - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, - 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, - 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, - 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, -}; - -const right_tmp_shift_amount = [80]u32{ - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, - 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, - 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, - 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, -}; - -const left_K = [5]u32{ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E }; -const right_K = [5]u32{ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000 }; 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, -}; diff --git a/src/router/router.zig b/src/router/router.zig new file mode 100644 index 0000000..239abf3 --- /dev/null +++ b/src/router/router.zig @@ -0,0 +1,4 @@ +// Router creating routes for http server +const core = @import("../core/lib.zig"); + +const Mint = core.mint.Mint;