Skip to content

Commit

Permalink
Nostr wasm (#121)
Browse files Browse the repository at this point in the history
* decrypting nostr dm using crypto subtle

* Moving all pages to use the encryption service for nostr messages

* Added missed file

* Fixed nostr encryption method

* Update src/Angor/Client/Pages/Signatures.razor

Co-authored-by: Dan Gershony <[email protected]>

* Update src/Angor/Client/Pages/Signatures.razor

---------

Co-authored-by: Dan Gershony <[email protected]>
  • Loading branch information
DavidGershony and dangershony authored Jul 18, 2024
1 parent c5b42d6 commit b0b59f2
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 128 deletions.
3 changes: 0 additions & 3 deletions src/Angor/Client/Pages/Browse.razor
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 7 additions & 27 deletions src/Angor/Client/Pages/Invest.razor
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
@inject IInvestorTransactionActions _InvestorTransactionActions

@inject ISerializer serializer
@inject IEncryptionService encryption

@if (!hasWallet)
{
Expand Down Expand Up @@ -287,8 +288,6 @@

private FeeData feeData = new();

private IJSInProcessObjectReference? javascriptNostrToolsModule;

protected override async Task OnInitializedAsync()
{
if (!hasWallet)
Expand Down Expand Up @@ -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<IJSInProcessObjectReference>("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)
Expand Down Expand Up @@ -612,10 +597,8 @@

var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes());

var encryptedContent = await javascriptNostrToolsModule.InvokeAsync<string>(
"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
Expand Down Expand Up @@ -667,8 +650,8 @@
if (project is not InvestorProject investorProject || investorProject.ReceivedFounderSignatures()) //multiple relays for the same message
return;

var signatureJson = await javascriptNostrToolsModule.InvokeAsync<string>(
"decryptNostr", nostrPrivateKeyHex, project.ProjectInfo.NostrPubKey, encryptedSignatures);
var signatureJson = await encryption.DecryptNostrContentAsync(
nostrPrivateKeyHex, project.ProjectInfo.NostrPubKey, encryptedSignatures);

_Logger.LogInformation("signature : " + signatureJson);

Expand Down Expand Up @@ -794,11 +777,8 @@
.ToList()
};

var encryptedProjectIdList = await javascriptNostrToolsModule.InvokeAsync<string>(
"encryptNostr",
rootNostrPrivateKeyHex,
nostrDMKey,
serializer.Serialize(investments));
var encryptedProjectIdList = await encryption.EncryptNostrContentAsync(
rootNostrPrivateKeyHex, nostrDMKey, serializer.Serialize(investments));

_RelayService.SendDirectMessagesForPubKeyAsync(rootNostrPrivateKeyHex, nostrDMKey, encryptedProjectIdList, x =>
{
Expand Down
60 changes: 17 additions & 43 deletions src/Angor/Client/Pages/Investor.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@inject IRelayService _RelayService
@inject ISignService _SignService
@inject ISerializer serializer
@inject IEncryptionService _encryptionService

@inject IJSRuntime JS

Expand Down Expand Up @@ -173,8 +174,6 @@

public Dictionary<string, ProjectStats> Stats = new();

private IJSInProcessObjectReference? javascriptNostrToolsModule;

private Investments scannedInvestments = new();

protected override async Task OnInitializedAsync()
Expand Down Expand Up @@ -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<IJSInProcessObjectReference>("import", "./NostrToolsMethods.js?version=" + DateTime.UtcNow.Ticks);
}
catch (JSException e)
{
notificationComponent.ShowErrorMessage(e.Message);
}
}

if (RefreshBalanceTriggered)
{
if (addedProjectsFromEvent)
Expand Down Expand Up @@ -262,58 +248,46 @@
{
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)
{
notificationComponent.ShowErrorMessage(ex.Message);
}
}

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<string>(
"decryptNostr",
NostrDMPrivateKeyHex,
rootNostrPubey,
x.Content);
var decryptedString = await _encryptionService.DecryptNostrContentAsync(
NostrDMPrivateKeyHex, rootNostrPubKeyHex, x.Content);

var projectIdList = serializer.Deserialize<Investments>(decryptedString);

Expand All @@ -339,7 +313,7 @@
{
_Logger.LogError(e,"failed to get handle investment list event from relay");
}
});
},rootNostrPubKeyHex);
}

private async Task GetInvestmentProjectDataAsync(InvestmentState investmentState)
Expand Down Expand Up @@ -405,8 +379,8 @@
if (investorProject.ReceivedFounderSignatures()) //multiple relays for the same message
return;

var signatureJson = await javascriptNostrToolsModule.InvokeAsync<string>(
"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<SignatureInfo>(signatureJson);

Expand Down
53 changes: 16 additions & 37 deletions src/Angor/Client/Pages/Signatures.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@inject IInvestorTransactionActions InvestorTransactionActions
@inject IFounderTransactionActions FounderTransactionActions
@inject ISerializer serializer
@inject IEncryptionService encryption

@inherits BaseComponent

Expand Down Expand Up @@ -142,7 +143,6 @@

public FounderProject FounderProject { get; set; }
private List<SignatureRequest> signaturesRequests = new();
private IJSInProcessObjectReference? javascriptNostrToolsModule;


private Dictionary<SignatureRequest,string> signaturesRequestsApproving = new();
Expand Down Expand Up @@ -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<IJSInProcessObjectReference>("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();
}

Expand Down Expand Up @@ -276,11 +260,8 @@

foreach (var pendingSignature in signaturesRequests.Where(_ => _.AmountToInvest == null))
{
pendingSignature.TransactionHex = await javascriptNostrToolsModule.InvokeAsync<string>(
"decryptNostr",
nostrPrivateKeyHex,
pendingSignature.investorPubKey,
pendingSignature.TransactionHex);
pendingSignature.TransactionHex = await encryption.DecryptNostrContentAsync(
nostrPrivateKeyHex, pendingSignature.investorNostrPubKey, pendingSignature.TransactionHex);

try
{
Expand All @@ -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)
{
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -513,16 +494,14 @@
var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex);
var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes());

var encryptedContent = await javascriptNostrToolsModule.InvokeAsync<string>(
"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 };
Expand All @@ -549,7 +528,7 @@

public class SignatureRequest
{
public string investorPubKey { get; set; }
public string investorNostrPubKey { get; set; }

public decimal? AmountToInvest { get; set; }

Expand Down
27 changes: 26 additions & 1 deletion src/Angor/Client/Services/EncryptionService.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -20,5 +24,26 @@ public async Task<string> DecryptData(string encryptedData, string password)
{
return await _jsRuntime.InvokeAsync<string>("decryptData", encryptedData, password);
}

public async Task<string> EncryptNostrContentAsync(string nsec, string npub, string content)
{
var secertHex = GetSharedSecretHexWithoutPrefix(nsec, npub);
return await _jsRuntime.InvokeAsync<string>("encryptNostr", secertHex, content);
}

public async Task<string> DecryptNostrContentAsync(string nsec, string npub, string encryptedContent)
{
var secertHex = GetSharedSecretHexWithoutPrefix(nsec, npub);
return await _jsRuntime.InvokeAsync<string>("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..]);
}
}
}
3 changes: 3 additions & 0 deletions src/Angor/Client/Services/IEncryptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ public interface IEncryptionService
{
Task<string> EncryptData(string secretData, string password);
Task<string> DecryptData(string encryptedData, string password);

Task<string> EncryptNostrContentAsync(string nsec,string npub, string content);
Task<string> DecryptNostrContentAsync(string nsec, string npub, string encrptedContent);
}

}
Loading

0 comments on commit b0b59f2

Please sign in to comment.