Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nostr founder signing work #14

Merged
merged 1 commit into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 47 additions & 36 deletions src/Angor/Client/Pages/Invest.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
@using Angor.Shared
@using Angor.Client.Storage
@using Angor.Shared.Models
@using Blockcore.Consensus.TransactionInfo
@using Blockcore.NBitcoin
@using Angor.Client.Services
@using Angor.Shared.ProtocolNew
@using Blockcore.NBitcoin.DataEncoders
@using JSException = Microsoft.JSInterop.JSException
@using Money = Blockcore.NBitcoin.Money
@using Transaction = Blockcore.Consensus.TransactionInfo.Transaction

@inject IJSRuntime JS

Expand Down Expand Up @@ -160,16 +161,19 @@
}
</div>
}
@if (recoverySigs != null)
@if (recoverySigs?.Signatures.Count == project.Stages.Count)
{
<div class="modal fade show d-block" tabindex="-1" style="background: rgba(0, 0, 0, 0.5)">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">The founder has signed the recovery transaction</h5>
<button type="button" class="btn-success" @onclick="PublishSignedTransactionAsync">Invest</button>
</div>
</div>
<div class="card">
<div class="card-body">
@if (!validatingsignaturs)
{
<h5 class="modal-title">The founder has signed the recovery transaction</h5>
<button type="button" class="btn-success" @onclick="PublishSignedTransactionAsync">Invest</button>
}
else
{
<div class="loader"></div>
}
</div>
</div>
}
Expand All @@ -190,6 +194,7 @@
Transaction unSignedTransaction;

SignatureInfo recoverySigs;
bool validatingsignaturs;

private FeeData feeData = new();

Expand Down Expand Up @@ -404,31 +409,7 @@
content = encryptedContent,
NostrPubKey = project.NostrPubKey,
InvestorNostrPrivateKey = nostrPrivateKeyHex
}, async _ =>
{
var test = await javascriptNostrToolsModule.InvokeAsync<string>(
"decryptNostr",
nostrPrivateKeyHex,
project.NostrPubKey,
_);

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

recoverySigs ??= new SignatureInfo();


recoverySigs.Signatures.Add(new SignatureInfoItem
{
Signature = test, StageIndex = recoverySigs.Signatures.Count()
});


if (recoverySigs.Signatures.Count == project.Stages.Count())
{
StateHasChanged();
}

});
}, async _ => await HandleSignatureReceivedAsync(nostrPrivateKeyHex, _));

return new OperationResult { Success = true, };
});
Expand All @@ -443,8 +424,34 @@
}
}

private async Task HandleSignatureReceivedAsync(string? nostrPrivateKeyHex, string _)
{
var signatureJson = await javascriptNostrToolsModule.InvokeAsync<string>(
"decryptNostr",
nostrPrivateKeyHex,
project.NostrPubKey,
_);

var signature = System.Text.Json.JsonSerializer.Deserialize<string>(signatureJson);

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

recoverySigs ??= new SignatureInfo();

recoverySigs.Signatures.Add(new SignatureInfoItem
{
Signature = signature, StageIndex = recoverySigs.Signatures.Count()
});

if (recoverySigs.Signatures.Count == project.Stages.Count())
{
StateHasChanged();
}
}

public async Task PublishSignedTransactionAsync()
{
validatingsignaturs = true;
var operationResult = await notificationComponent.LongOperation(async () =>
{
var validSignatures = _InvestorTransactionActions.CheckInvestorRecoverySignatures(project, signedTransaction, recoverySigs);
Expand All @@ -461,7 +468,9 @@

var response = await _WalletOperations.PublishTransactionAsync(network, signedTransaction);

return !response.Success ? response : new OperationResult { Success = true };
//TODO David remove signatures from storage on fail?

return response.Success ? new SuccessOperationResult() : response;
});

if (operationResult.Success)
Expand All @@ -474,7 +483,9 @@
}
else
{
validatingsignaturs = false;
notificationComponent.ShowErrorMessage(operationResult.Message);
StateHasChanged();
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/Angor/Client/Pages/Settings.razor
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@
return;
}

if (!(uri.Scheme == "http" || uri.Scheme == "https"))
if (uri.Scheme is not ("http" or "https"))
{
notificationComponent.ShowErrorMessage($"invalid url {newIndexerLink} schema must be http or https");
return;
}

newIndexerLink = new Uri($"{uri.Scheme}://{uri.Host}/").ToString();
newIndexerLink = new Uri($"{uri.Scheme}://{uri.Host}").ToString();

if (settingsInfo.Indexers.Any(a => a.Url == newIndexerLink))
{
Expand All @@ -198,13 +198,13 @@
return;
}

if (!(uri.Scheme == "ws" || uri.Scheme == "wss"))
if (uri.Scheme is not ("ws" or "wss"))
{
notificationComponent.ShowErrorMessage($"invalid url {newRelayLink} schema must be ws or wss");
return;
}

newRelayLink = new Uri($"{uri.Scheme}://{uri.Host}/").ToString();
newRelayLink = new Uri($"{uri.Scheme}://{uri.Host}").ToString();

if (settingsInfo.Relays.Any(a => a.Url == newRelayLink))
{
Expand Down
75 changes: 37 additions & 38 deletions src/Angor/Server/TestNostrSigningFromRelay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,51 +101,18 @@ public async Task SignTransactionsFromNostrAsync(string projectIdentifier)

_nostrClient.Streams.EventStream.Where(_ => _.Subscription == nostrPubKey + "1")
.Where(_ => _.Event.Kind == NostrKind.ApplicationSpecificData)
//.Where(_ => _.Event.Pubkey == nostrPubKey)
.Subscribe(_ =>
{
_clientLogger.LogInformation("application specific data" + _.Event.Content);
_storage.Add(JsonConvert.DeserializeObject<ProjectInfo>(_.Event.Content,NostrSerializer.Settings));
_storage.Add(System.Text.Json.JsonSerializer.Deserialize<ProjectInfo>(_.Event.Content));
});

_nostrClient.Streams.EventStream.Where(_ => _.Subscription == nostrPubKey + "2")
.Where(_ => _.Event.Kind == NostrKind.EncryptedDm)
// .Where(_ => _.Event.Tags.ContainsTag("p",nostrPubKey))
.Select(_ => _.Event as NostrEncryptedEvent)
.Subscribe(nostrEvent =>
{
_clientLogger.LogInformation("encrypted direct message");
var project = (_storage.Get().GetAwaiter().GetResult()).First(_ => _.ProjectIdentifier == projectIdentifier);
var transactionHex = nostrEvent.DecryptContent(nostrPrivateKey);

_clientLogger.LogInformation(transactionHex);

var sig = signProject(transactionHex,project,projectKeys.founderSigningPrivateKey);

var stages = sig.Signatures.Select(_ => _.Signature );

foreach (var stage in sig.Signatures)
{
var sigJson = JsonConvert.SerializeObject(stage.Signature);
//JsonConvert.SerializeObject(sig.Signatures.OrderBy(_ => _.StageIndex).Select(_ => _.Signature), NostrSerializer.Settings);

_logger.LogInformation($"Signature to send for stage {stage.StageIndex}: {sigJson}");

var ev = new NostrEvent
{
Kind = NostrKind.EncryptedDm,
CreatedAt = DateTime.UtcNow,
Content = sigJson,
Tags = new NostrEventTags(new[] { NostrEventTag.Profile(nostrEvent.Pubkey) })
};

var signed = NostrEncryptedEvent.EncryptDirectMessage(ev, nostrPrivateKey)
.Sign(nostrPrivateKey);

_nostrClient.Send(new NostrEventRequest(signed));
}


SignInvestorTransactionsAsync(projectIdentifier, nostrEvent, nostrPrivateKey, projectKeys);
});

_nostrClient.Send(new NostrRequest( nostrPubKey + "1", new NostrFilter
Expand All @@ -154,15 +121,47 @@ public async Task SignTransactionsFromNostrAsync(string projectIdentifier)
Kinds = new[] { NostrKind.ApplicationSpecificData },
Limit = 1
}));

_nostrClient.Send(new NostrRequest(nostrPubKey + "2", new NostrFilter
{
P = new []{nostrPubKey},
Kinds = new[] {NostrKind.EncryptedDm },
P = new[] { nostrPubKey },
Kinds = new[] { NostrKind.EncryptedDm },
Since = DateTime.UtcNow
}));
}

private void SignInvestorTransactionsAsync(string projectIdentifier, NostrEncryptedEvent? nostrEvent,
NostrPrivateKey nostrPrivateKey, ProjectKeys projectKeys)
{
_clientLogger.LogInformation("encrypted direct message");
var project = (_storage.Get().GetAwaiter().GetResult()).First(_ => _.ProjectIdentifier == projectIdentifier);
var transactionHex = nostrEvent.DecryptContent(nostrPrivateKey);

_clientLogger.LogInformation(transactionHex);

var sig = signProject(transactionHex, project, projectKeys.founderSigningPrivateKey);

foreach (var stage in sig.Signatures)
{
var sigJson = System.Text.Json.JsonSerializer.Serialize(stage.Signature);

_logger.LogInformation($"Signature to send for stage {stage.StageIndex}: {sigJson}");

var ev = new NostrEvent
{
Kind = NostrKind.EncryptedDm,
CreatedAt = DateTime.UtcNow,
Content = sigJson,
Tags = new NostrEventTags(new[] { NostrEventTag.Profile(nostrEvent.Pubkey) })
};

var signed = NostrEncryptedEvent.EncryptDirectMessage(ev, nostrPrivateKey)
.Sign(nostrPrivateKey);

_nostrClient.Send(new NostrEventRequest(signed));
}
}

private SignatureInfo signProject(string transactionHex,ProjectInfo info, string founderSigningPrivateKey)
{
var investorTrx = _networkConfiguration.GetNetwork().CreateTransaction(transactionHex);
Expand Down
8 changes: 8 additions & 0 deletions src/Angor/Shared/Models/OperationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ public class OperationResult
public bool Success { get; set; }
}

public class SuccessOperationResult : OperationResult
{
public SuccessOperationResult()
{
Success = true;
}
}

public class OperationResult<T> : OperationResult
{
public T? Data { get; set; }
Expand Down
24 changes: 19 additions & 5 deletions src/Angor/Shared/Services/RelayService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Reactive.Linq;
using System.Text.Json;
using Angor.Shared.Models;
using Angor.Shared.Utilities;
using Nostr.Client.Requests;
using Microsoft.Extensions.Logging;
using Nostr.Client.Client;
using Nostr.Client.Communicator;
using Nostr.Client.Json;
using Nostr.Client.Keys;
using Nostr.Client.Messages;
using Nostr.Client.Messages.Metadata;
Expand Down Expand Up @@ -74,11 +75,10 @@ public Task LookupProjectsInfoByPubKeysAsync<T>(Action<T> responseDataAction,par
{
var subscription = _nostrClient.Streams.EventStream
.Where(_ => _.Subscription == subscriptionName)
//.Where(_ => nostrPubKeys.Contains(_.Event.Pubkey))
.Select(_ => _.Event)
.Subscribe(ev =>
{
responseDataAction(Newtonsoft.Json.JsonConvert.DeserializeObject<T>(ev.Content, NostrSerializer.Settings));
responseDataAction(JsonSerializer.Deserialize<T>(ev.Content,settings));
});

subscriptions.Add(subscriptionName, subscription);
Expand Down Expand Up @@ -129,7 +129,7 @@ public Task<string> AddProjectAsync(ProjectInfo project, string hexPrivateKey)
if (!project.NostrPubKey.Contains(key.DerivePublicKey().Hex))
throw new ArgumentException($"The nostr pub key on the project does not fit the npub calculated from the nsec {project.NostrPubKey} {key.DerivePublicKey().Hex}");

var content = Newtonsoft.Json.JsonConvert.SerializeObject(project, NostrSerializer.Settings);
var content = JsonSerializer.Serialize(project,settings);

var signed = GetNip78NostrEvent(content)
.Sign(key);
Expand All @@ -146,7 +146,7 @@ public Task<string> CreateNostrProfileAsync(NostrMetadata metadata, string hexPr
{
var key = NostrPrivateKey.FromHex(hexPrivateKey);

var content = Newtonsoft.Json.JsonConvert.SerializeObject(metadata, NostrSerializer.Settings);
var content = JsonSerializer.Serialize(metadata,settings);

var signed = new NostrEvent
{
Expand Down Expand Up @@ -281,6 +281,20 @@ private void SetupNostrCommunicator()
info.Text, info.MessageType);
});
}

private JsonSerializerOptions settings => new ()
{
// Equivalent to Formatting = Formatting.None
WriteIndented = false,

// Equivalent to NullValueHandling = NullValueHandling.Ignore
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,

// PropertyNamingPolicy equivalent to CamelCasePropertyNamesContractResolver
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

Converters = { new UnixDateTimeConverter() }
};
}

}
7 changes: 4 additions & 3 deletions src/Angor/Shared/Services/SignService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Angor.Client.Services
public interface ISignService
{
Task AddSignKeyAsync(ProjectInfo project, string founderRecoveryPrivateKey, string nostrPrivateKey);
Task<string> RequestInvestmentSigsAsync(SignRecoveryRequest signRecoveryRequest, Action<string> action);
Task<string> RequestInvestmentSigsAsync(SignRecoveryRequest signRecoveryRequest, Func<string,Task> action);
}

public class SignService : ISignService
Expand Down Expand Up @@ -56,7 +56,7 @@ public async Task AddSignKeyAsync(ProjectInfo project, string founderRecoveryPri
response.EnsureSuccessStatusCode();
}

public Task<string> RequestInvestmentSigsAsync(SignRecoveryRequest signRecoveryRequest, Action<string> action)
public Task<string> RequestInvestmentSigsAsync(SignRecoveryRequest signRecoveryRequest, Func<string,Task> action)
{
var sender = NostrPrivateKey.FromHex(signRecoveryRequest.InvestorNostrPrivateKey);
//var receiver = NostrPublicKey.FromHex(signRecoveryRequest.NostrPubKey);
Expand Down Expand Up @@ -85,7 +85,8 @@ public Task<string> RequestInvestmentSigsAsync(SignRecoveryRequest signRecoveryR
Authors = new []{signRecoveryRequest.NostrPubKey},
P = new []{nostrPubKey},
Kinds = new[] { NostrKind.EncryptedDm},
Since = timeOfMessage
Since = timeOfMessage,
Limit = 1
}));

var subscription = _nostrClient.Streams.EventStream
Expand Down
Loading
Loading