Skip to content

Commit

Permalink
support nut18
Browse files Browse the repository at this point in the history
  • Loading branch information
Kukks committed Nov 5, 2024
1 parent 77decea commit 640441e
Show file tree
Hide file tree
Showing 29 changed files with 558 additions and 208 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,27 @@ jobs:
INCLUDE_SYMBOLS: false
VERSION_REGEX: ^\s*<PackageVersion>(.*)<\/PackageVersion>\s*$
TAG_COMMIT: true
TAG_FORMAT: DotNut/v*
- name: Publish NuGet for nostr
if: ${{ github.ref == 'refs/heads/master' }} # Publish only when the push is on master
uses: Rebel028/[email protected]
with:
PROJECT_FILE_PATH: DotNut.Nostr/DotNut.Nostr.csproj
NUGET_KEY: ${{secrets.NUGET_KEY}}
PACKAGE_NAME: DotNut.Nostr
INCLUDE_SYMBOLS: false
VERSION_REGEX: ^\s*<PackageVersion>(.*)<\/PackageVersion>\s*$
TAG_COMMIT: true
TAG_FORMAT: DotNut.Nostr/v*
- name: Publish Github Package Registry for nostr
if: ${{ github.ref == 'refs/heads/master' }} # Publish only when the push is on master
uses: Rebel028/[email protected]
with:
PROJECT_FILE_PATH: DotNut.Nostr/DotNut.Nostr.csproj
NUGET_SOURCE: "https://nuget.pkg.github.com/Kukks"
NUGET_KEY: ${{secrets.GH_TOKEN}}
PACKAGE_NAME: DotNut.Nostr
INCLUDE_SYMBOLS: false
VERSION_REGEX: ^\s*<PackageVersion>(.*)<\/PackageVersion>\s*$
TAG_COMMIT: true
TAG_FORMAT: DotNut/v*
27 changes: 27 additions & 0 deletions DotNut.Nostr/DotNut.Nostr.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Title>DotNut.Nostr</Title>
<Authors>Kukks</Authors>
<Description>Support Cashu payment requests through Nostr </Description>
<Copyright>MIT</Copyright>
<PackageProjectUrl>https://github.com/Kukks/DotNut</PackageProjectUrl>
<PackageVersion>1.0.0</PackageVersion>
<RepositoryUrl>https://github.com/Kukks/DotNut</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>bitcoin cashu ecash secp256k1 nostr</PackageTags>
<PackageLicenseUrl>https://github.com/Kukks/DotNut/blob/master/LICENSE</PackageLicenseUrl>

</PropertyGroup>

<ItemGroup>

<PackageReference Include="NNostr.Client" Version="0.0.53" />
<ProjectReference Include="..\DotNut\DotNut.csproj" />
</ItemGroup>

</Project>
43 changes: 43 additions & 0 deletions DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Security.Cryptography;
using System.Text.Json;
using NBitcoin.Secp256k1;
using NNostr.Client;
using NNostr.Client.Protocols;

namespace DotNut.Nostr;

public class NostrNip17PaymentRequestInterfaceHandler : PaymentRequestInterfaceHandler
{
public static void Register()
{
PaymentRequestTransportInitiator.Handlers.Add(new NostrNip17PaymentRequestInterfaceHandler());
}

public bool CanHandle(PaymentRequest request)
{
return request.Transports.Any(t => t.Type == "nostr" && t.Tags.Any( t => t.Key == "n" && t.Value == "17"));
}

public async Task SendPayment(PaymentRequest request, PaymentRequestPayload payload,
CancellationToken cancellationToken = default)
{
var nprofileStr = request.Transports.First(t => t.Type == "nostr" && t.Tags.Any( t => t.Key == "n" && t.Value == "17")).Target;
var nprofile = (NIP19.NosteProfileNote) NIP19.FromNIP19Note(nprofileStr);
using var client = new CompositeNostrClient(nprofile.Relays.Select(r => new Uri(r)).ToArray());
await client.Connect(cancellationToken);
var ephemeralKey = ECPrivKey.Create(RandomNumberGenerator.GetBytes(32));
var msg = new NostrEvent()
{
Kind = 14,
Content = JsonSerializer.Serialize(payload),
CreatedAt = DateTimeOffset.Now,
PublicKey = ephemeralKey.CreateXOnlyPubKey().ToHex(),
Tags = new(),
};
msg.Id = msg.ComputeId();

var giftWrap = await NIP17.Create(msg, ephemeralKey,ECXOnlyPubKey.Create(Convert.FromHexString(nprofile.PubKey)), null);
await client.SendEventsAndWaitUntilReceived(new []{giftWrap}, cancellationToken);

}
}
19 changes: 19 additions & 0 deletions DotNut.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,25 @@ public void Nut14Tests_HTLCSecret()
var rebuiltNut10 = new Nut10Secret(HTLCProofSecret.Key, rebuiltHtlcSecret);
Assert.Equal(JsonSerializer.Serialize(nut10Secret), JsonSerializer.Serialize(rebuiltNut10));
}

[Fact]
public void Nut18Tests()
{
var creqA =
"creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=";
var pr = PaymentRequest.Parse(creqA);
Assert.Equal("https://nofees.testnut.cashu.space", Assert.Single( pr.Mints));
Assert.Equal(10, pr.Amount);
Assert.Equal("b7a90176", pr.PaymentId);
Assert.Equal("sat", pr.Unit);
var t = Assert.Single(pr.Transports);
Assert.Equal("nostr", t.Type);
Assert.Equal("nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5", t.Target);
Assert.Equal("n",Assert.Single(t.Tags).Key );
Assert.Equal("17",Assert.Single(t.Tags).Value );
Assert.Equal(creqA, pr.ToString());

}

internal readonly struct TestCase(in string path, in string keyHex, in string ccHex)
{
Expand Down
6 changes: 6 additions & 0 deletions DotNut.sln
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNut", "DotNut\DotNut.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNut.Tests", "DotNut.Tests\DotNut.Tests.csproj", "{0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNut.Nostr", "DotNut.Nostr\DotNut.Nostr.csproj", "{EDBCFA42-B7A0-4443-9359-8A44BDA17935}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -18,5 +20,9 @@ Global
{0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106}.Release|Any CPU.Build.0 = Release|Any CPU
{EDBCFA42-B7A0-4443-9359-8A44BDA17935}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDBCFA42-B7A0-4443-9359-8A44BDA17935}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDBCFA42-B7A0-4443-9359-8A44BDA17935}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDBCFA42-B7A0-4443-9359-8A44BDA17935}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
19 changes: 14 additions & 5 deletions DotNut.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACBORObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1178f88c7f664f88be66f2163788c5392f600_003F8a_003F2c7374c2_003FCBORObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACBORReader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1178f88c7f664f88be66f2163788c5392f600_003Fd6_003F6991e711_003FCBORReader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEqualityAsserts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fec11489f13fea2ba3e5dfbdea086f41dac024556d53f318c75a75a2cbd73_003FEqualityAsserts_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AICBORConverter_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1178f88c7f664f88be66f2163788c5392f600_003Fd2_003F856b4d3b_003FICBORConverter_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIGrouping_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F93ad1732e6bb4cc0a5b6f0de2317699584908_003F3b_003F8a9e12bf_003FIGrouping_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonConverterOfT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc247cf3d53f062b5bf983a13ed187d7444d3f29fd6b7ae1a3cdc8cd0f3d213_003FJsonConverterOfT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANostrClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fd0b54a6fb9f089bd45a21661e5f515f5109c46c869c360abb2ca21edbf267d60_003FNostrClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9a6b1457cbcf17db31a383ba49ef9bcc786cf3ef77146d997eee499b27a46d_003FObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStringAsserts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F999ae9cc4ab7b7cfbc5080803e994426e97fd9d87c5b1f44544a799bc114_003FStringAsserts_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002ESerialization_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F60afdfcef9945be6977fa4b76b3d84506e2a2067afb63480aacf49417cfa_003FThrowHelper_002ESerialization_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/Highlighting/HighlightingSourceSnapshotLocation/@EntryValue">C:\Users\evilk\AppData\Local\JetBrains\Rider2024.2\resharper-host\temp\Rider\vAny\CoverageData\_DotNut.1481064820\Snapshot\snapshot.utdcvr</s:String>

<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7c67fc42_002Dfa49_002D41c5_002Db4e4_002D876bfc07dd62/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNuts.Tests.UnitTest1&lt;/TestId&gt;&#xD;
&lt;TestId&gt;xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1.Nut00Tests_TokenSerialization&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b0637c43_002D4622_002D4768_002D94e5_002D70df8e44a8fe/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Nut11_Signatures" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Namespace&gt;DotNuts.Tests&lt;/Namespace&gt;&#xD;
&lt;Project Location="C:\Git\DotNuts\DotNuts.Tests" Presentation="&amp;lt;DotNuts.Tests&amp;gt;" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;Project Location="C:\Git\DotNuts\DotNut.Tests" Presentation="&amp;lt;DotNut.Tests&amp;gt;" /&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
9 changes: 9 additions & 0 deletions DotNut/ApiModels/ContactInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace DotNut.ApiModels;

public class ContactInfo
{
[JsonPropertyName("method")] public string Method { get; set; }
[JsonPropertyName("info")] public string Info { get; set; }
}
6 changes: 0 additions & 6 deletions DotNut/ApiModels/GetInfoResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,4 @@ public class GetInfoResponse
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonPropertyName("nuts")]
public Dictionary<string, JsonDocument>? Nuts { get; set; }
}

public class ContactInfo
{
[JsonPropertyName("method")] public string Method { get; set; }
[JsonPropertyName("info")] public string Info { get; set; }
}
2 changes: 1 addition & 1 deletion DotNut/DotNut.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NBitcoin.Secp256k1" Version="3.1.5" />
<PackageReference Include="NBitcoin.Secp256k1" Version="3.1.6" />
<PackageReference Include="NBip32Fast" Version="1.0.5" />
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
</ItemGroup>
Expand Down
130 changes: 1 addition & 129 deletions DotNut/Encoding/CashuTokenHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System.Text;
using System.Text.Json;
using NBitcoin.Secp256k1;
using PeterO.Cbor;

namespace DotNut;
namespace DotNut;

public static class CashuTokenHelper
{
Expand Down Expand Up @@ -64,127 +59,4 @@ public static CashuToken Decode(string token, out string? version)
token = token.Substring(1);
return encoder.Decode(token);
}
}

public interface ICashuTokenEncoder
{
string Encode(CashuToken token);
CashuToken Decode(string token);
}

public class CashuTokenV3Encoder : ICashuTokenEncoder
{
public string Encode(CashuToken token)
{
var json = JsonSerializer.Serialize(token);
return Base64UrlSafe.Encode(Encoding.UTF8.GetBytes(json));
}

public CashuToken Decode(string token)
{
var json = Encoding.UTF8.GetString(Base64UrlSafe.Decode(token));
return JsonSerializer.Deserialize<CashuToken>(json)!;
}
}

public class CashuTokenV4Encoder : ICashuTokenEncoder, ICBORToFromConverter<CashuToken>
{
public string Encode(CashuToken token)
{
var obj = ToCBORObject(token);
return Base64UrlSafe.Encode(obj.EncodeToBytes());
}

public CashuToken Decode(string token)
{
var obj = CBORObject.DecodeFromBytes(Base64UrlSafe.Decode(token));
return FromCBORObject(obj);
}

public CBORObject ToCBORObject(CashuToken token)
{
//ensure that all token mints are the same
var mints = token.Tokens.Select(token1 => token1.Mint).ToArray();
if (mints.Distinct().Count() != 1)
throw new FormatException("All proofs must have the same mint in v4 tokens");
var proofSets = CBORObject.NewArray();
foreach (var proofSet in token.Tokens.SelectMany(token1 => token1.Proofs).GroupBy(proof => proof.Id))
{
var proofSetItem = CBORObject.NewOrderedMap();
proofSetItem.Add("i", Convert.FromHexString(proofSet.Key.ToString()));
var proofSetItemArray = CBORObject.NewArray();
foreach (var proof in proofSet)
{
var proofItem = CBORObject.NewOrderedMap()
.Add("a", proof.Amount)
.Add("s", Encoding.UTF8.GetString(proof.Secret.GetBytes()))
.Add("c", proof.C.Key.ToBytes());
if (proof.DLEQ is not null)
{
proofItem.Add("d", CBORObject
.NewOrderedMap()
.Add("e", proof.DLEQ.E.Key.ToBytes())
.Add("s", proof.DLEQ.S.Key.ToBytes())
.Add("r", proof.DLEQ.R.Key.ToBytes()));
}

if (proof.Witness is not null)
{
proofItem.Add("w", proof.Witness);
}

proofSetItemArray.Add(proofItem);
}

proofSetItem.Add("p", proofSetItemArray);
proofSets.Add(proofSetItem);
}

var cbor = CBORObject.NewOrderedMap();


if (token.Memo is not null)
cbor.Add("d", token.Memo);
cbor.Add("t", proofSets)
.Add("m", mints.First())
.Add("u", token.Unit!);
return cbor;
}

public CashuToken FromCBORObject(CBORObject obj)
{
return new CashuToken
{
Unit = obj["u"].AsString(),
Memo = obj.GetOrDefault("d", null)?.AsString(),
Tokens =
[
new CashuToken.Token()
{
Mint = obj["m"].AsString(),
Proofs = obj["t"].Values.SelectMany(proofSet =>
{
var id = new KeysetId(Convert.ToHexString(proofSet["i"].GetByteString()).ToLowerInvariant());

return proofSet["p"].Values.Select(proof => new Proof()
{
Amount = proof["a"].AsInt32(),
Secret = JsonSerializer.Deserialize<ISecret>(proof["s"].ToJSONString())!,
C = ECPubKey.Create(proof["c"].GetByteString()),
Witness = proof.GetOrDefault("w", null)?.AsString(),
DLEQ = proof.GetOrDefault("d", null) is { } cborDLEQ
? new DLEQProof
{
E = ECPrivKey.Create(cborDLEQ["e"].GetByteString()),
S = ECPrivKey.Create(cborDLEQ["s"].GetByteString()),
R = ECPrivKey.Create(cborDLEQ["r"].GetByteString())
}
: null,
Id = id
});
}).ToList()
}
]
};
}
}
19 changes: 19 additions & 0 deletions DotNut/Encoding/CashuTokenV3Encoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text;
using System.Text.Json;

namespace DotNut;

public class CashuTokenV3Encoder : ICashuTokenEncoder
{
public string Encode(CashuToken token)
{
var json = JsonSerializer.Serialize(token);
return Base64UrlSafe.Encode(Encoding.UTF8.GetBytes(json));
}

public CashuToken Decode(string token)
{
var json = Encoding.UTF8.GetString(Base64UrlSafe.Decode(token));
return JsonSerializer.Deserialize<CashuToken>(json)!;
}
}
Loading

0 comments on commit 640441e

Please sign in to comment.