diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index 9064f94a..9deec204 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -1,10 +1,7 @@ @page "/browse" -@using Angor.Client.Services @using Angor.Shared.Models @using Angor.Shared.Services -@using Nostr.Client.Keys @using Nostr.Client.Messages -@using System.Text.Json @using Angor.Client.Models @using Angor.Client.Storage @inject ICacheStorage SessionStorage diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index 3946ee88..80aa793a 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -29,6 +29,7 @@ @inject IInvestorTransactionActions _InvestorTransactionActions @inject ISerializer serializer +@inject IEncryptionService encryption @if (!hasWallet) { @@ -287,8 +288,6 @@ private FeeData feeData = new(); - private IJSInProcessObjectReference? javascriptNostrToolsModule; - protected override async Task OnInitializedAsync() { if (!hasWallet) @@ -331,20 +330,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { - if (javascriptNostrToolsModule == null) - { - try - { - //TODO import the nostr tool module directly to c# class - javascriptNostrToolsModule = await JS.InvokeAsync("import", "./NostrToolsMethods.js?version=" + DateTime.UtcNow.Ticks); - } - catch (JSException e) - { - _Logger.LogError(e,"Failed to load the nostr tools module"); - notificationComponent.ShowErrorMessage(e.Message); - } - } - if (firstRender) { if (hasWallet) @@ -612,10 +597,8 @@ var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - var encryptedContent = await javascriptNostrToolsModule.InvokeAsync( - "encryptNostr", - nostrPrivateKeyHex, - investorProject.ProjectInfo.NostrPubKey, + var encryptedContent = await encryption.EncryptNostrContentAsync( + nostrPrivateKeyHex, investorProject.ProjectInfo.NostrPubKey, strippedInvestmentTransaction.ToHex(network.Consensus.ConsensusFactory)); var investmentSigsRequest = _SignService.RequestInvestmentSigs(new SignRecoveryRequest @@ -667,8 +650,8 @@ if (project is not InvestorProject investorProject || investorProject.ReceivedFounderSignatures()) //multiple relays for the same message return; - var signatureJson = await javascriptNostrToolsModule.InvokeAsync( - "decryptNostr", nostrPrivateKeyHex, project.ProjectInfo.NostrPubKey, encryptedSignatures); + var signatureJson = await encryption.DecryptNostrContentAsync( + nostrPrivateKeyHex, project.ProjectInfo.NostrPubKey, encryptedSignatures); _Logger.LogInformation("signature : " + signatureJson); @@ -794,11 +777,8 @@ .ToList() }; - var encryptedProjectIdList = await javascriptNostrToolsModule.InvokeAsync( - "encryptNostr", - rootNostrPrivateKeyHex, - nostrDMKey, - serializer.Serialize(investments)); + var encryptedProjectIdList = await encryption.EncryptNostrContentAsync( + rootNostrPrivateKeyHex, nostrDMKey, serializer.Serialize(investments)); _RelayService.SendDirectMessagesForPubKeyAsync(rootNostrPrivateKeyHex, nostrDMKey, encryptedProjectIdList, x => { diff --git a/src/Angor/Client/Pages/Investor.razor b/src/Angor/Client/Pages/Investor.razor index 7e20c89f..b4b55646 100644 --- a/src/Angor/Client/Pages/Investor.razor +++ b/src/Angor/Client/Pages/Investor.razor @@ -18,6 +18,7 @@ @inject IRelayService _RelayService @inject ISignService _SignService @inject ISerializer serializer +@inject IEncryptionService _encryptionService @inject IJSRuntime JS @@ -173,8 +174,6 @@ public Dictionary Stats = new(); - private IJSInProcessObjectReference? javascriptNostrToolsModule; - private Investments scannedInvestments = new(); protected override async Task OnInitializedAsync() @@ -202,19 +201,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { - if (javascriptNostrToolsModule == null) - { - try - { - //TODO import the nostr tool module directly to c# class - javascriptNostrToolsModule = await JS.InvokeAsync("import", "./NostrToolsMethods.js?version=" + DateTime.UtcNow.Ticks); - } - catch (JSException e) - { - notificationComponent.ShowErrorMessage(e.Message); - } - } - if (RefreshBalanceTriggered) { if (addedProjectsFromEvent) @@ -262,11 +248,8 @@ { Stats.Clear(); - foreach (var project in projects) - { - var projectStats = await _IndexerService.GetProjectStatsAsync(project.ProjectInfo.ProjectIdentifier); - Stats.Add(project.ProjectInfo.ProjectIdentifier, projectStats); - } + var tasks = projects.Select(x => AddProjectStats(x.ProjectInfo.ProjectIdentifier)); + await Task.WhenAll(tasks); } catch (Exception ex) { @@ -274,46 +257,37 @@ } } - public string TrimString(string input) + private async Task AddProjectStats(string projectId) { - if (input.Length > 20) - { - return input.Substring(0, 10) + "..." + input.Substring(input.Length - 10); - } - - return input; + var projectStats = await _IndexerService.GetProjectStatsAsync(projectId); + Stats.Add(projectId, projectStats); } private async Task GetProjectsAndUpdateAsync() { + RefreshBalanceTriggered = true; + if (!passwordComponent.HasPassword()) { passwordComponent.ShowPassword(GetProjectsAndUpdateAsync); + RefreshBalanceTriggered = false; return; } - RefreshBalanceTriggered = true; - var words = await passwordComponent.GetWalletAsync(); var NostrDMPrivateKey = await _DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, 1); var NostrDMPrivateKeyHex = Encoders.Hex.EncodeData(NostrDMPrivateKey.ToBytes()); - var NostrDMPubkey = _DerivationOperations.DeriveNostrPubKey(words, 1); - var rootNostrPubey = _DerivationOperations.DeriveNostrPubKey(words, 0); + var rootNostrPubKeyHex = _DerivationOperations.DeriveNostrPubKey(words, 0); + - await _RelayService.LookupDirectMessagesForPubKeyAsync( NostrDMPubkey, null,1, + await _RelayService.LookupDirectMessagesForPubKeyAsync( NostrDMPrivateKey.PubKey.ToHex()[2..], null,1, async x => { - if (x.Pubkey != rootNostrPubey) - return; - try { - var decryptedString = await javascriptNostrToolsModule.InvokeAsync( - "decryptNostr", - NostrDMPrivateKeyHex, - rootNostrPubey, - x.Content); + var decryptedString = await _encryptionService.DecryptNostrContentAsync( + NostrDMPrivateKeyHex, rootNostrPubKeyHex, x.Content); var projectIdList = serializer.Deserialize(decryptedString); @@ -339,7 +313,7 @@ { _Logger.LogError(e,"failed to get handle investment list event from relay"); } - }); + },rootNostrPubKeyHex); } private async Task GetInvestmentProjectDataAsync(InvestmentState investmentState) @@ -405,8 +379,8 @@ if (investorProject.ReceivedFounderSignatures()) //multiple relays for the same message return; - var signatureJson = await javascriptNostrToolsModule.InvokeAsync( - "decryptNostr", Encoders.Hex.EncodeData(investorNostrPrivateKey.ToBytes()), project.NostrPubKey, encryptedSignatures); + var signatureJson = await _encryptionService.DecryptNostrContentAsync( + Encoders.Hex.EncodeData(investorNostrPrivateKey.ToBytes()), project.NostrPubKey, encryptedSignatures); var res = serializer.Deserialize(signatureJson); diff --git a/src/Angor/Client/Pages/Signatures.razor b/src/Angor/Client/Pages/Signatures.razor index 39336143..6c6a14f6 100644 --- a/src/Angor/Client/Pages/Signatures.razor +++ b/src/Angor/Client/Pages/Signatures.razor @@ -19,6 +19,7 @@ @inject IInvestorTransactionActions InvestorTransactionActions @inject IFounderTransactionActions FounderTransactionActions @inject ISerializer serializer +@inject IEncryptionService encryption @inherits BaseComponent @@ -142,7 +143,6 @@ public FounderProject FounderProject { get; set; } private List signaturesRequests = new(); - private IJSInProcessObjectReference? javascriptNostrToolsModule; private Dictionary signaturesRequestsApproving = new(); @@ -178,22 +178,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { Logger.LogDebug("OnAfterRenderAsync"); - if (javascriptNostrToolsModule == null && signaturesRequests.Any()) - { - try - { - Logger.LogDebug("load nostr tools"); - //TODO import the nostr tool module directly to c# class - javascriptNostrToolsModule = await JS.InvokeAsync("import", "./NostrToolsMethods.js?version=" + DateTime.UtcNow.Ticks); - } - catch (JSException e) - { - Logger.LogError($"Error loading nostr tools: {e.Message}", e); - notificationComponent.ShowErrorMessage(e.Message); - return; - } - } - await FetchSignaturesCheckPassword(); } @@ -276,11 +260,8 @@ foreach (var pendingSignature in signaturesRequests.Where(_ => _.AmountToInvest == null)) { - pendingSignature.TransactionHex = await javascriptNostrToolsModule.InvokeAsync( - "decryptNostr", - nostrPrivateKeyHex, - pendingSignature.investorPubKey, - pendingSignature.TransactionHex); + pendingSignature.TransactionHex = await encryption.DecryptNostrContentAsync( + nostrPrivateKeyHex, pendingSignature.investorNostrPubKey, pendingSignature.TransactionHex); try { @@ -307,11 +288,11 @@ private async Task FetchPendingSignatures(FounderProject project) { await SignService.LookupInvestmentRequestsAsync(project.ProjectInfo.NostrPubKey, null,// project.LastRequestForSignaturesTime , async - (eventId, investorPubKey, encryptedMessage, timeArrived) => + (eventId, investorNostrPubKey, encryptedMessage, timeArrived) => { - Logger.LogDebug($"Sig request event received investorPubKey: {investorPubKey} - timeArrived: {timeArrived}"); + Logger.LogDebug($"Sig request event received investorNostrPubKey: {investorNostrPubKey} - timeArrived: {timeArrived}"); - var sigReq = signaturesRequests.FirstOrDefault(_ => _.investorPubKey == investorPubKey); + var sigReq = signaturesRequests.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); if (sigReq != null) { @@ -334,7 +315,7 @@ var signatureRequest = new SignatureRequest { - investorPubKey = investorPubKey, + investorNostrPubKey = investorNostrPubKey, TimeArrived = timeArrived, TransactionHex = encryptedMessage, //To be encrypted after js interop is loaded EventId = eventId @@ -358,11 +339,11 @@ private void FetchFounderApprovalsSignatures(FounderProject project) { SignService.LookupInvestmentRequestApprovals(project.ProjectInfo.NostrPubKey, - (investorPubKey, timeApproved, reqEventId) => + (investorNostrPubKey, timeApproved, reqEventId) => { - Logger.LogDebug($"Sig response event received investorPubKey: {investorPubKey} - timeApproved: {timeApproved} - reqEventId: {reqEventId}"); + Logger.LogDebug($"Sig response event received investorNostrPubKey: {investorNostrPubKey} - timeApproved: {timeApproved} - reqEventId: {reqEventId}"); - var signatureRequest = signaturesRequests.FirstOrDefault(_ => _.investorPubKey == investorPubKey); + var signatureRequest = signaturesRequests.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); if (signatureRequest is null || signatureRequest.TimeApproved != null) return; //multiple relays could mean the same massage multiple times @@ -513,16 +494,14 @@ var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex); var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - var encryptedContent = await javascriptNostrToolsModule.InvokeAsync( - "encryptNostr", - nostrPrivateKeyHex, - signature.investorPubKey, - sigJson); + var encryptedContent = await encryption.EncryptNostrContentAsync( + nostrPrivateKeyHex, signature.investorNostrPubKey, sigJson); + + FounderProject.LastRequestForSignaturesTime = SignService.SendSignaturesToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorNostrPubKey, signature.EventId); - FounderProject.LastRequestForSignaturesTime = SignService.SendSignaturesToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorPubKey, signature.EventId); Storage.UpdateFounderProject(FounderProject); - signaturesRequests.Single(_ => _.investorPubKey == signature.investorPubKey && _.TimeApproved is null) + signaturesRequests.Single(_ => _.investorNostrPubKey == signature.investorNostrPubKey && _.TimeApproved is null) .TimeApproved = FounderProject.LastRequestForSignaturesTime; return new OperationResult { Success = true }; @@ -549,7 +528,7 @@ public class SignatureRequest { - public string investorPubKey { get; set; } + public string investorNostrPubKey { get; set; } public decimal? AmountToInvest { get; set; } diff --git a/src/Angor/Client/Services/EncryptionService.cs b/src/Angor/Client/Services/EncryptionService.cs index 10688b5d..ada5599b 100644 --- a/src/Angor/Client/Services/EncryptionService.cs +++ b/src/Angor/Client/Services/EncryptionService.cs @@ -1,4 +1,8 @@ -using Microsoft.JSInterop; +using Blockcore.NBitcoin; +using Blockcore.NBitcoin.Crypto; +using Blockcore.NBitcoin.DataEncoders; +using Microsoft.JSInterop; +using NBitcoin.Crypto; namespace Angor.Client.Services { @@ -20,5 +24,26 @@ public async Task DecryptData(string encryptedData, string password) { return await _jsRuntime.InvokeAsync("decryptData", encryptedData, password); } + + public async Task EncryptNostrContentAsync(string nsec, string npub, string content) + { + var secertHex = GetSharedSecretHexWithoutPrefix(nsec, npub); + return await _jsRuntime.InvokeAsync("encryptNostr", secertHex, content); + } + + public async Task DecryptNostrContentAsync(string nsec, string npub, string encryptedContent) + { + var secertHex = GetSharedSecretHexWithoutPrefix(nsec, npub); + return await _jsRuntime.InvokeAsync("decryptNostr", secertHex, encryptedContent); + } + + private static string GetSharedSecretHexWithoutPrefix(string nsec, string npub) + { + var privateKey = new Key(Encoders.Hex.DecodeData(nsec)); + var publicKey = new PubKey("02" + npub); + + var secert = publicKey.GetSharedPubkey(privateKey); + return Encoders.Hex.EncodeData(secert.ToBytes()[1..]); + } } } diff --git a/src/Angor/Client/Services/IEncryptionService.cs b/src/Angor/Client/Services/IEncryptionService.cs index 1be46910..134224f4 100644 --- a/src/Angor/Client/Services/IEncryptionService.cs +++ b/src/Angor/Client/Services/IEncryptionService.cs @@ -6,6 +6,9 @@ public interface IEncryptionService { Task EncryptData(string secretData, string password); Task DecryptData(string encryptedData, string password); + + Task EncryptNostrContentAsync(string nsec,string npub, string content); + Task DecryptNostrContentAsync(string nsec, string npub, string encrptedContent); } } diff --git a/src/Angor/Client/wwwroot/NostrToolsMethods.js b/src/Angor/Client/wwwroot/NostrToolsMethods.js deleted file mode 100644 index cd6cff28..00000000 --- a/src/Angor/Client/wwwroot/NostrToolsMethods.js +++ /dev/null @@ -1,13 +0,0 @@ -import {nip04} from 'https://cdn.jsdelivr.net/npm/nostr-tools@1.17.0/+esm' - -export function encryptNostr(sk1,pk2, message){ - //debugger; - console.log("encrypting the nostr message to pub key " + pk2) - return nip04.encrypt(sk1,pk2, message) -} - -export function decryptNostr(sk2,pk1, message){ - //debugger; - console.log("decrypting the nostr message from pub key " + pk1) - return nip04.decrypt(sk2,pk1,message); -} \ No newline at end of file diff --git a/src/Angor/Client/wwwroot/index.html b/src/Angor/Client/wwwroot/index.html index fee1c5db..0d5ab8c8 100644 --- a/src/Angor/Client/wwwroot/index.html +++ b/src/Angor/Client/wwwroot/index.html @@ -44,6 +44,7 @@ + diff --git a/src/Angor/Client/wwwroot/nostr-tools-methods.js b/src/Angor/Client/wwwroot/nostr-tools-methods.js new file mode 100644 index 00000000..e271a682 --- /dev/null +++ b/src/Angor/Client/wwwroot/nostr-tools-methods.js @@ -0,0 +1,63 @@ +import {Base64} from "./lib/js-base64/base64.mjs"; +function hexToBytes(hex) { + let bytes = []; + for (let c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)); + } + return new Uint8Array(bytes); +} + +async function encryptNostr(sharedSecretHex, message) { + try { + const sharedSecret = hexToBytes(sharedSecretHex); + + const encoder = new TextEncoder(); + const data = encoder.encode(message); + + const iv = crypto.getRandomValues(new Uint8Array(16)); // Initialization vector for AES-CBC + + const key = await crypto.subtle.importKey('raw', sharedSecret, 'AES-CBC', false, ['encrypt']); + + const encrypted = await crypto.subtle.encrypt({ + name: 'AES-CBC', + iv: iv + }, key, data); + + const encryptedBase64 = Base64.fromUint8Array(new Uint8Array(encrypted)); + const ivBase64 = Base64.fromUint8Array(iv); + + return `${encryptedBase64}?iv=${ivBase64}`; + } catch (e) { + console.log(e) + throw e; + } +} + +async function decryptNostr(sharedSecretHex, encryptedMessage) { + try { + const sharedSecret = hexToBytes(sharedSecretHex); + + let [ctb64, ivb64] = encryptedMessage.split("?iv=") + + const iv = Base64.toUint8Array(ivb64); + const ciphertext = Base64.toUint8Array(ctb64); + + const key = await crypto.subtle.importKey('raw', sharedSecret, 'AES-CBC', false, ['decrypt']); + + const decrypted = await crypto.subtle.decrypt({ + name: 'AES-CBC', + iv: iv + }, key, ciphertext); + + const decoder = new TextDecoder(); + return decoder.decode(decrypted); + } + catch (e) + { + console.log(e) + throw e; + } +} + +window.encryptNostr = encryptNostr; +window.decryptNostr = decryptNostr; \ No newline at end of file diff --git a/src/Angor/Shared/Services/IRelayService.cs b/src/Angor/Shared/Services/IRelayService.cs index c33abba1..decab76b 100644 --- a/src/Angor/Shared/Services/IRelayService.cs +++ b/src/Angor/Shared/Services/IRelayService.cs @@ -16,7 +16,7 @@ void LookupProjectsInfoByPubKeys(Action responseDataAction, Action? OnEndO Task LookupSignaturesDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Action onResponseAction); - Task LookupDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Func onResponseAction); + Task LookupDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Func onResponseAction, string? fromNpub); string SendDirectMessagesForPubKeyAsync(string senderNosterPrivateKey, string nostrPubKey, string encryptedMessage, Action onResponseAction); diff --git a/src/Angor/Shared/Services/RelayService.cs b/src/Angor/Shared/Services/RelayService.cs index 13a47956..29bcf699 100644 --- a/src/Angor/Shared/Services/RelayService.cs +++ b/src/Angor/Shared/Services/RelayService.cs @@ -118,7 +118,7 @@ public Task LookupSignaturesDirectMessagesForPubKeyAsync(string nostrPubKey, Dat return Task.CompletedTask; } - public Task LookupDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Func onResponseAction) + public Task LookupDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Func onResponseAction, string? senderNpub = null) { var nostrClient = _communicationFactory.GetOrCreateClient(_networkService); @@ -135,13 +135,18 @@ public Task LookupDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? sin _subscriptionsHandling.TryAddRelaySubscription(subscriptionKey, subscription); } - nostrClient.Send(new NostrRequest(subscriptionKey, new NostrFilter + var nostrFilter = new NostrFilter { P = new[] { nostrPubKey }, Kinds = new[] { NostrKind.EncryptedDm }, Since = since, Limit = limit - })); + }; + + if (senderNpub != null) + nostrFilter.Authors = new[] { senderNpub }; + + nostrClient.Send(new NostrRequest(subscriptionKey, nostrFilter)); return Task.CompletedTask; }