diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fd8dfba..cc52ad9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,37 @@
on: push
jobs:
+ tests:
+ runs-on: ubuntu-latest
+ steps:
+ # Checkout the code
+ - uses: actions/checkout@v5
+
+ # Install .NET Core SDK
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Pull and start mint
+ run: |
+ docker run -d -p 3338:3338 \
+ --name cdk-mint \
+ -e CDK_MINTD_DATABASE=sqlite \
+ -e CDK_MINTD_LN_BACKEND=fakewallet \
+ -e CDK_MINTD_INPUT_FEE_PPK=100 \
+ -e CDK_MINTD_LISTEN_HOST=0.0.0.0 \
+ -e CDK_MINTD_LISTEN_PORT=3338 \
+ -e CDK_MINTD_FAKE_WALLET_MIN_DELAY=0 \
+ -e CDK_MINTD_MNEMONIC='abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' \
+ cashubtc/mintd:latest-amd64
+
+ - name: Wait for mint to be ready
+ run: |
+ timeout 60s bash -c 'until curl -f localhost:3338/v1/info; do sleep 2; done'
+
+ - name: Test
+ run: dotnet test
build:
runs-on: ubuntu-latest
steps:
@@ -13,9 +44,6 @@ jobs:
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.x
-
- - name: Test
- run: dotnet test
- name: Publish NuGet
if: ${{ github.ref == 'refs/heads/master' }} # Publish only when the push is on master
diff --git a/.gitignore b/.gitignore
index cddd443..62e59ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
**/bin
**/obj
.idea
-.idea
-.vs
\ No newline at end of file
+.vs
+*.DotSettings.user
\ No newline at end of file
diff --git a/DotNut.Demo/DotNut.Demo.csproj b/DotNut.Demo/DotNut.Demo.csproj
index bb84a1f..232ca1c 100644
--- a/DotNut.Demo/DotNut.Demo.csproj
+++ b/DotNut.Demo/DotNut.Demo.csproj
@@ -1,5 +1,4 @@
-
Exe
net8.0
@@ -10,5 +9,4 @@
-
diff --git a/DotNut.Demo/Program.cs b/DotNut.Demo/Program.cs
index 9dd99ee..290ec2a 100644
--- a/DotNut.Demo/Program.cs
+++ b/DotNut.Demo/Program.cs
@@ -1,9 +1,9 @@
+using System.Security.Cryptography;
using DotNut.Api;
using DotNut.ApiModels;
-using NBitcoin.Secp256k1;
-using System.Security.Cryptography;
using DotNut.NBitcoin.BIP39;
using DotNut.NUT13;
+using NBitcoin.Secp256k1;
namespace DotNut.Demo;
@@ -12,20 +12,20 @@ class Program
private static readonly string DefaultMintUrl = "https://testnut.cashu.space";
private static CashuHttpClient? _client;
private static List _wallet = new();
-
+
static async Task Main(string[] args)
{
Console.WriteLine("🥜 DotNut - Cashu Library Demo");
Console.WriteLine("==============================");
Console.WriteLine();
-
+
await InitializeMint();
-
+
while (true)
{
ShowMenu();
var choice = Console.ReadLine();
-
+
try
{
switch (choice?.ToLower())
@@ -81,13 +81,13 @@ static async Task Main(string[] args)
Console.WriteLine($" Detail: {cashuEx.Error.Detail}");
}
}
-
+
Console.WriteLine("\nPress any key to continue...");
Console.Read();
Console.Clear();
}
}
-
+
private static void ShowMenu()
{
Console.WriteLine("📋 Available Demos:");
@@ -106,7 +106,7 @@ private static void ShowMenu()
Console.WriteLine();
Console.Write("Choose an option: ");
}
-
+
private static async Task InitializeMint()
{
try
@@ -114,7 +114,7 @@ private static async Task InitializeMint()
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(DefaultMintUrl);
_client = new CashuHttpClient(httpClient);
-
+
Console.WriteLine($"🔗 Initialized connection to: {DefaultMintUrl}");
}
catch (Exception ex)
@@ -122,18 +122,18 @@ private static async Task InitializeMint()
Console.WriteLine($"❌ Failed to initialize mint connection: {ex.Message}");
}
}
-
+
private static async Task ConnectToMintDemo()
{
Console.WriteLine("🔗 Connect to Mint & Get Info Demo");
Console.WriteLine("==================================");
-
+
if (_client == null)
{
Console.WriteLine("❌ Client not initialized");
return;
}
-
+
try
{
// Get mint information
@@ -141,24 +141,26 @@ private static async Task ConnectToMintDemo()
Console.WriteLine($"✅ Connected to mint: {info.Name}");
Console.WriteLine($" Description: {info.Description}");
Console.WriteLine($" Version: {info.Version}");
- Console.WriteLine($" Contact: {string.Join(", ", info.Contact?.Select(c => $"{c.Method}: {c.Info}") ?? new[] { "N/A" })}");
-
+ Console.WriteLine(
+ $" Contact: {string.Join(", ", info.Contact?.Select(c => $"{c.Method}: {c.Info}") ?? new[] { "N/A" })}"
+ );
+
// Get available keysets
var keysets = await _client.GetKeysets();
Console.WriteLine($" Available keysets: {keysets.Keysets.Length}");
-
+
foreach (var keyset in keysets.Keysets.Take(3))
{
Console.WriteLine($" - {keyset.Id} ({keyset.Unit}) [{keyset.Active}]");
}
-
+
// Get keys for the first active keyset
var activeKeyset = keysets.Keysets.FirstOrDefault(k => k.Active);
if (activeKeyset != null)
{
var keys = await _client.GetKeys(activeKeyset.Id);
Console.WriteLine($" Keys in active keyset ({activeKeyset.Id}):");
-
+
foreach (var key in keys.Keysets.First().Keys.Take(5))
{
Console.WriteLine($" - Amount {key.Key}: {key.Value}");
@@ -170,16 +172,16 @@ private static async Task ConnectToMintDemo()
Console.WriteLine($"❌ Failed to connect to mint: {ex.Message}");
}
}
-
+
private static async Task TokenCreationDemo()
{
Console.WriteLine("🪙 Token Creation Demo");
Console.WriteLine("======================");
-
+
// Create some example proofs for demonstration
var proofs = CreateExampleProofs();
_wallet.AddRange(proofs);
-
+
// Create a token
var token = new CashuToken
{
@@ -187,19 +189,15 @@ private static async Task TokenCreationDemo()
Memo = "Demo payment - Coffee ☕",
Tokens = new List
{
- new CashuToken.Token
- {
- Mint = DefaultMintUrl,
- Proofs = proofs
- }
- }
+ new CashuToken.Token { Mint = DefaultMintUrl, Proofs = proofs },
+ },
};
-
+
Console.WriteLine($"✅ Created token with {proofs.Count} proofs");
Console.WriteLine($" Total amount: {token.TotalAmount()} sats");
Console.WriteLine($" Memo: {token.Memo}");
Console.WriteLine($" Mint: {token.Tokens.First().Mint}");
-
+
// Show proof details
Console.WriteLine(" Proofs:");
foreach (var proof in proofs)
@@ -207,12 +205,12 @@ private static async Task TokenCreationDemo()
Console.WriteLine($" - {proof.Amount} sats (ID: {proof.Id})");
}
}
-
+
private static async Task TokenEncodingDemo()
{
Console.WriteLine("🔄 Token Encoding/Decoding Demo");
Console.WriteLine("===============================");
-
+
var proofs = CreateExampleProofs();
var token = new CashuToken
{
@@ -220,31 +218,27 @@ private static async Task TokenEncodingDemo()
Memo = "Encoding demo token",
Tokens = new List
{
- new CashuToken.Token
- {
- Mint = DefaultMintUrl,
- Proofs = proofs
- }
- }
+ new CashuToken.Token { Mint = DefaultMintUrl, Proofs = proofs },
+ },
};
-
+
// V3 Encoding (JSON-based)
Console.WriteLine("📝 V3 Encoding (JSON-based):");
var v3Token = token.Encode("A");
Console.WriteLine($" Length: {v3Token.Length} characters");
Console.WriteLine($" Token: {v3Token.Substring(0, Math.Min(80, v3Token.Length))}...");
-
+
// V4 Encoding (CBOR-based, more compact)
Console.WriteLine("\n📦 V4 Encoding (CBOR-based, compact):");
var v4Token = token.Encode("B");
Console.WriteLine($" Length: {v4Token.Length} characters");
Console.WriteLine($" Token: {v4Token.Substring(0, Math.Min(80, v4Token.Length))}...");
-
+
// URI format
Console.WriteLine("\n🔗 URI Format:");
var uriToken = token.Encode("B", makeUri: true);
Console.WriteLine($" URI: {uriToken.Substring(0, Math.Min(80, uriToken.Length))}...");
-
+
// Decode and verify
Console.WriteLine("\n🔍 Decoding V4 token:");
var decoded = CashuTokenHelper.Decode(v4Token, out string version);
@@ -252,47 +246,57 @@ private static async Task TokenEncodingDemo()
Console.WriteLine($" Amount: {decoded.TotalAmount()} sats");
Console.WriteLine($" Memo: {decoded.Memo}");
Console.WriteLine($" Proofs: {decoded.Tokens.First().Proofs.Count}");
-
- Console.WriteLine($"\n💾 Space savings: V4 is {((double)(v3Token.Length - v4Token.Length) / v3Token.Length * 100):F1}% smaller than V3");
+
+ Console.WriteLine(
+ $"\n💾 Space savings: V4 is {((double)(v3Token.Length - v4Token.Length) / v3Token.Length * 100):F1}% smaller than V3"
+ );
}
-
+
private static async Task LightningMintDemo()
{
Console.WriteLine("⚡ Lightning Mint Quote Demo");
Console.WriteLine("============================");
- Console.WriteLine("ℹ️ This demo shows how to create mint quotes - actual minting requires paying a real Lightning invoice");
-
+ Console.WriteLine(
+ "ℹ️ This demo shows how to create mint quotes - actual minting requires paying a real Lightning invoice"
+ );
+
if (_client == null)
{
Console.WriteLine("❌ Client not initialized");
return;
}
-
+
try
{
// Create mint quote
var mintRequest = new PostMintQuoteBolt11Request
{
Amount = 1000, // 1000 sats
- Unit = "sat"
+ Unit = "sat",
};
-
- var mintQuote = await _client.CreateMintQuote(
- "bolt11", mintRequest);
-
+
+ var mintQuote = await _client.CreateMintQuote<
+ PostMintQuoteBolt11Response,
+ PostMintQuoteBolt11Request
+ >("bolt11", mintRequest);
+
Console.WriteLine("✅ Mint quote created successfully!");
Console.WriteLine($" Quote ID: {mintQuote.Quote}");
Console.WriteLine($" Amount: {mintQuote.Amount} {mintRequest.Unit}");
Console.WriteLine($" Unit: {mintQuote.Unit ?? mintRequest.Unit}");
- Console.WriteLine($" Expiry: {DateTimeOffset.FromUnixTimeSeconds(mintQuote.Expiry ?? 0).UtcDateTime}");
+ Console.WriteLine(
+ $" Expiry: {DateTimeOffset.FromUnixTimeSeconds(mintQuote.Expiry ?? 0).UtcDateTime}"
+ );
Console.WriteLine($" State: {mintQuote.State}");
Console.WriteLine("\n📄 Lightning Invoice:");
Console.WriteLine($" {mintQuote.Request}");
-
+
Console.WriteLine("\n💡 To complete minting:");
Console.WriteLine(" 1. Pay the Lightning invoice above");
Console.WriteLine(" 2. Create blinded messages for desired denominations");
- Console.WriteLine(" 3. Call the mint endpoint with the quote ID and blinded messages");
+ Console.WriteLine(
+ " 3. Call the mint endpoint with the quote ID and blinded messages"
+ );
Console.WriteLine(" 4. Unblind the returned signatures to get your proofs");
}
catch (Exception ex)
@@ -300,44 +304,51 @@ private static async Task LightningMintDemo()
Console.WriteLine($"❌ Failed to create mint quote: {ex.Message}");
}
}
-
+
private static async Task LightningMeltDemo()
{
Console.WriteLine("⚡ Lightning Melt Quote Demo");
Console.WriteLine("============================");
- Console.WriteLine("ℹ️ This demo shows how to create melt quotes - actual melting requires valid proofs");
-
+ Console.WriteLine(
+ "ℹ️ This demo shows how to create melt quotes - actual melting requires valid proofs"
+ );
+
if (_client == null)
{
Console.WriteLine("❌ Client not initialized");
return;
}
-
+
// Example Lightning invoice (fake for demo purposes)
- var exampleInvoice = "lnbc10n1pj9x8x8pp5k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6qdqqcqzpgxqyz5vqsp5example";
-
+ var exampleInvoice =
+ "lnbc10n1pj9x8x8pp5k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6k6qdqqcqzpgxqyz5vqsp5example";
+
try
{
var meltRequest = new PostMeltQuoteBolt11Request
{
Request = exampleInvoice,
- Unit = "sat"
+ Unit = "sat",
};
-
+
// Note: This will likely fail with the example invoice, but shows the API usage
Console.WriteLine("📤 Attempting to create melt quote...");
Console.WriteLine($" Invoice: {exampleInvoice.Substring(0, 50)}...");
-
- var meltQuote = await _client.CreateMeltQuote(
- "bolt11", meltRequest);
-
+
+ var meltQuote = await _client.CreateMeltQuote<
+ PostMeltQuoteBolt11Response,
+ PostMeltQuoteBolt11Request
+ >("bolt11", meltRequest);
+
Console.WriteLine("✅ Melt quote created successfully!");
Console.WriteLine($" Quote ID: {meltQuote.Quote}");
Console.WriteLine($" Amount: {meltQuote.Amount} {meltRequest.Unit}");
Console.WriteLine($" Fee reserve: {meltQuote.FeeReserve} {meltRequest.Unit}");
- Console.WriteLine($" Expiry: {DateTimeOffset.FromUnixTimeSeconds(meltQuote.Expiry ?? 0).UtcDateTime}");
+ Console.WriteLine(
+ $" Expiry: {DateTimeOffset.FromUnixTimeSeconds(meltQuote.Expiry ?? 0).UtcDateTime}"
+ );
Console.WriteLine($" State: {meltQuote.State}");
-
+
Console.WriteLine("\n💡 To complete melting:");
Console.WriteLine(" 1. Provide proofs with sufficient value (amount + fee)");
Console.WriteLine(" 2. Call the melt endpoint with quote ID and proofs");
@@ -352,58 +363,64 @@ private static async Task LightningMeltDemo()
Console.WriteLine(" 3. Ensure you have sufficient proofs in your wallet");
}
}
-
+
private static async Task TokenSwapDemo()
{
Console.WriteLine("🔄 Token Swapping Demo");
Console.WriteLine("======================");
- Console.WriteLine("ℹ️ Swapping allows you to change token denominations or refresh secrets");
-
+ Console.WriteLine(
+ "ℹ️ Swapping allows you to change token denominations or refresh secrets"
+ );
+
if (_wallet.Count == 0)
{
Console.WriteLine("⚠️ No proofs in wallet. Creating example proofs for demo...");
_wallet.AddRange(CreateExampleProofs());
}
-
+
var inputProofs = _wallet.Take(2).ToList();
- Console.WriteLine($"📥 Input proofs: {inputProofs.Count} proofs totaling {inputProofs.Sum(p => (long)p.Amount)} sats");
-
+ Console.WriteLine(
+ $"📥 Input proofs: {inputProofs.Count} proofs totaling {inputProofs.Sum(p => (long)p.Amount)} sats"
+ );
+
foreach (var proof in inputProofs)
{
Console.WriteLine($" - {proof.Amount} sats (Secret: {proof.Secret})");
}
-
+
// In a real implementation, you would:
// 1. Create blinded messages for new denominations
// 2. Send swap request to mint
// 3. Unblind the returned signatures
-
+
Console.WriteLine("\n💡 Swap process would involve:");
Console.WriteLine(" 1. Creating blinded messages for desired output amounts");
- Console.WriteLine(" 2. Sending PostSwapRequest with input proofs and output blinded messages");
+ Console.WriteLine(
+ " 2. Sending PostSwapRequest with input proofs and output blinded messages"
+ );
Console.WriteLine(" 3. Receiving BlindSignatures from the mint");
Console.WriteLine(" 4. Unblinding signatures to get new proofs with fresh secrets");
Console.WriteLine(" 5. The old proofs become invalid, new proofs are added to wallet");
}
-
+
private static async Task SecretsDemo()
{
Console.WriteLine("🔐 Working with Secrets Demo");
Console.WriteLine("============================");
-
+
// Simple string secret
Console.WriteLine("1️⃣ Simple String Secret:");
var stringSecret = new StringSecret("my-random-secret-12345");
Console.WriteLine($" Secret: {stringSecret}");
Console.WriteLine($" Curve point: {stringSecret.ToCurve().ToHex()}");
-
+
// Random secret generation
Console.WriteLine("\n2️⃣ Random Secret Generation:");
var randomBytes = new byte[32];
RandomNumberGenerator.Fill(randomBytes);
var randomSecret = new StringSecret(Convert.ToHexString(randomBytes).ToLower());
Console.WriteLine($" Random secret: {randomSecret}");
-
+
// Demonstrate secret uniqueness
Console.WriteLine("\n3️⃣ Secret Uniqueness:");
var secret1 = new StringSecret("test-secret-1");
@@ -411,145 +428,147 @@ private static async Task SecretsDemo()
Console.WriteLine($" Secret 1 → Curve: {secret1.ToCurve().ToHex()}");
Console.WriteLine($" Secret 2 → Curve: {secret2.ToCurve().ToHex()}");
Console.WriteLine($" Different secrets produce different curve points ✅");
-
+
Console.WriteLine("\n💡 Key points about secrets:");
Console.WriteLine(" - Secrets are hashed to elliptic curve points");
Console.WriteLine(" - Each secret maps to a unique point on the curve");
Console.WriteLine(" - Changing even one character creates a completely different point");
Console.WriteLine(" - Secrets should be random and unpredictable");
}
-
+
private static async Task MnemonicDemo()
{
Console.WriteLine("🎲 Mnemonic Secrets Demo (NUT-13)");
Console.WriteLine("==================================");
-
+
// Create a mnemonic
var mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);
Console.WriteLine($"📝 Generated mnemonic:");
Console.WriteLine($" {mnemonic}");
-
+
// Example keyset ID (normally you'd get this from the mint)
var keysetId = new KeysetId("009a1f293253e41e");
-
+
Console.WriteLine($"\n🔑 Deriving secrets from mnemonic:");
Console.WriteLine($" Keyset ID: {keysetId}");
-
+
// Derive multiple secrets
for (uint i = 0; i < 5; i++)
{
var secret = mnemonic.DeriveSecret(keysetId, counter: i);
var blindingFactor = mnemonic.DeriveBlindingFactor(keysetId, counter: i);
-
+
Console.WriteLine($" Counter {i}:");
Console.WriteLine($" Secret: {secret}");
Console.WriteLine($" Blinding: {Convert.ToHexString(blindingFactor).ToLower()}");
}
-
+
Console.WriteLine("\n💡 Benefits of deterministic secrets:");
Console.WriteLine(" - Reproducible from mnemonic phrase");
Console.WriteLine(" - No need to store individual secrets");
Console.WriteLine(" - Can recover proofs if you lose wallet data");
Console.WriteLine(" - Counter ensures each secret is unique");
-
+
Console.WriteLine("\n⚠️ Security considerations:");
Console.WriteLine(" - Keep your mnemonic phrase secure");
Console.WriteLine(" - Anyone with the mnemonic can recreate your secrets");
Console.WriteLine(" - Use proper entropy when generating mnemonics");
}
-
+
private static async Task P2PKDemo()
{
Console.WriteLine("🔒 Pay-to-Public-Key Demo (NUT-11)");
Console.WriteLine("===================================");
-
+
// Create some public keys for the demo
var privKey1 = ECPrivKey.Create(RandomNumberGenerator.GetBytes(32));
var privKey2 = ECPrivKey.Create(RandomNumberGenerator.GetBytes(32));
var pubKey1 = privKey1.CreatePubKey();
var pubKey2 = privKey2.CreatePubKey();
-
+
Console.WriteLine("🔑 Generated demo keys:");
Console.WriteLine($" PubKey 1: {pubKey1}");
Console.WriteLine($" PubKey 2: {pubKey2}");
-
+
// Create a 1-of-2 multisig P2PK secret
Console.WriteLine("\n🏗️ Creating 1-of-2 multisig P2PK:");
- var p2pkBuilder = new P2PKBuilder
+ var p2pkBuilder = new P2PkBuilder
{
Pubkeys = new[] { pubKey1, pubKey2 },
SignatureThreshold = 1, // 1-of-2 multisig
- SigFlag = "SIG_INPUTS"
+ SigFlag = "SIG_INPUTS",
};
-
+
var p2pkSecret = p2pkBuilder.Build();
var nut10Secret = new Nut10Secret(P2PKProofSecret.Key, p2pkSecret);
-
- Console.WriteLine($" Signature threshold: {p2pkBuilder.SignatureThreshold}-of-{p2pkBuilder.Pubkeys.Length}");
+
+ Console.WriteLine(
+ $" Signature threshold: {p2pkBuilder.SignatureThreshold}-of-{p2pkBuilder.Pubkeys.Length}"
+ );
Console.WriteLine($" Signature flag: {p2pkBuilder.SigFlag}");
Console.WriteLine($" P2PK secret created ✅");
-
+
// Create a time-locked P2PK
Console.WriteLine("\n⏰ Creating time-locked P2PK:");
- var timeLockedBuilder = new P2PKBuilder
+ var timeLockedBuilder = new P2PkBuilder
{
Pubkeys = new[] { pubKey1 },
SignatureThreshold = 1,
SigFlag = "SIG_INPUTS",
Lock = DateTimeOffset.UtcNow.AddHours(1), // Lock for 1 hour
- RefundPubkeys = new[] { pubKey2 } // Refund key after timeout
+ RefundPubkeys = new[] { pubKey2 }, // Refund key after timeout
};
-
+
var timeLockedSecret = timeLockedBuilder.Build();
var timeLockedNut10 = new Nut10Secret(P2PKProofSecret.Key, timeLockedSecret);
-
+
Console.WriteLine($" Lock time: {timeLockedBuilder.Lock}");
Console.WriteLine($" Refund key: {pubKey2}");
Console.WriteLine($" Time-locked P2PK secret created ✅");
-
+
Console.WriteLine("\n💡 P2PK use cases:");
Console.WriteLine(" - Multisignature wallets");
Console.WriteLine(" - Escrow services");
Console.WriteLine(" - Time-locked payments");
Console.WriteLine(" - Conditional spending");
-
+
Console.WriteLine("\n🔓 To spend P2PK proofs:");
Console.WriteLine(" - Create signatures with required private keys");
Console.WriteLine(" - Include witness data in the proof");
Console.WriteLine(" - Mint validates signatures against public keys");
}
-
+
private static async Task CheckProofStatesDemo()
{
Console.WriteLine("🔍 Check Proof States Demo");
Console.WriteLine("==========================");
-
+
if (_client == null)
{
Console.WriteLine("❌ Client not initialized");
return;
}
-
+
if (_wallet.Count == 0)
{
Console.WriteLine("⚠️ No proofs in wallet. Creating example proofs...");
_wallet.AddRange(CreateExampleProofs());
}
-
+
try
{
var proofsToCheck = _wallet.Take(3).ToList();
Console.WriteLine($"📋 Checking state of {proofsToCheck.Count} proofs...");
-
+
// Create check state request
var stateRequest = new PostCheckStateRequest
{
- Ys = proofsToCheck.Select(p => p.C.ToString()).ToArray()
+ Ys = proofsToCheck.Select(p => p.C.ToString()).ToArray(),
};
-
+
// Note: This will likely fail with fake proofs, but shows the API usage
var stateResponse = await _client.CheckState(stateRequest);
-
+
Console.WriteLine("✅ State check successful:");
for (int i = 0; i < stateResponse.States.Length; i++)
{
@@ -572,58 +591,62 @@ private static async Task CheckProofStatesDemo()
Console.WriteLine(" - Check states before attempting to spend proofs");
}
}
-
+
private static void ShowWallet()
{
Console.WriteLine("💰 Current Wallet");
Console.WriteLine("=================");
-
+
if (_wallet.Count == 0)
{
Console.WriteLine(" Empty wallet - no proofs stored");
return;
}
-
+
var totalAmount = _wallet.Sum(p => (long)p.Amount);
Console.WriteLine($" Total balance: {totalAmount} sats");
Console.WriteLine($" Number of proofs: {_wallet.Count}");
Console.WriteLine();
-
+
Console.WriteLine(" Proof details:");
foreach (var proof in _wallet.Take(10)) // Show first 10 proofs
{
- Console.WriteLine($" - {proof.Amount,4} sats | ID: {proof.Id} | Secret: {proof.Secret.ToString().Substring(0, Math.Min(20, proof.Secret.ToString().Length))}...");
+ Console.WriteLine(
+ $" - {proof.Amount, 4} sats | ID: {proof.Id} | Secret: {proof.Secret.ToString().Substring(0, Math.Min(20, proof.Secret.ToString().Length))}..."
+ );
}
-
+
if (_wallet.Count > 10)
{
Console.WriteLine($" ... and {_wallet.Count - 10} more proofs");
}
-
+
// Show denomination breakdown
var denominations = _wallet.GroupBy(p => p.Amount).OrderBy(g => g.Key);
Console.WriteLine("\n Denomination breakdown:");
foreach (var denom in denominations)
{
- Console.WriteLine($" {denom.Key,4} sats: {denom.Count()} proofs = {denom.Key * (ulong)denom.Count()} sats");
+ Console.WriteLine(
+ $" {denom.Key, 4} sats: {denom.Count()} proofs = {denom.Key * (ulong)denom.Count()} sats"
+ );
}
}
-
+
private static List CreateExampleProofs()
{
// Create example proofs for demonstration
// In a real application, these would come from minting operations
var keysetId = new KeysetId("009a1f293253e41e");
-
+
var proofs = new List();
var amounts = new ulong[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };
-
+
foreach (var amount in amounts.Take(5)) // Create 5 demo proofs
{
var secret = new StringSecret($"demo-secret-{amount}-{Guid.NewGuid()}");
var privKey = ECPrivKey.Create(RandomNumberGenerator.GetBytes(32));
var pubKey = privKey.CreatePubKey();
-
+
var proof = new Proof
{
Amount = amount,
@@ -632,10 +655,10 @@ private static List CreateExampleProofs()
C = pubKey,
// Note: In real usage, these would be proper cryptographic proofs from the mint
};
-
+
proofs.Add(proof);
}
-
+
return proofs;
}
}
@@ -647,4 +670,4 @@ public static ulong TotalAmount(this CashuToken token)
{
return (ulong)token.Tokens.SelectMany(t => t.Proofs).Sum(p => (long)p.Amount);
}
-}
\ No newline at end of file
+}
diff --git a/DotNut.Nostr/DotNut.Nostr.csproj b/DotNut.Nostr/DotNut.Nostr.csproj
index 4bed5fa..05d32c2 100644
--- a/DotNut.Nostr/DotNut.Nostr.csproj
+++ b/DotNut.Nostr/DotNut.Nostr.csproj
@@ -1,27 +1,23 @@
+
+ net8.0
+ enable
+ enable
+ true
+ DotNut.Nostr
+ Kukks
+ Support Cashu payment requests through Nostr
+ MIT
+ https://github.com/Kukks/DotNut
+ 1.0.0
+ https://github.com/Kukks/DotNut
+ git
+ bitcoin cashu ecash secp256k1 nostr
+ https://github.com/Kukks/DotNut/blob/master/LICENSE
+
-
- net8.0
- enable
- enable
- true
- DotNut.Nostr
- Kukks
- Support Cashu payment requests through Nostr
- MIT
- https://github.com/Kukks/DotNut
- 1.0.0
- https://github.com/Kukks/DotNut
- git
- bitcoin cashu ecash secp256k1 nostr
- https://github.com/Kukks/DotNut/blob/master/LICENSE
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs b/DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs
index 28d25b4..7c43f12 100644
--- a/DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs
+++ b/DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs
@@ -10,7 +10,9 @@ public class NostrNip17PaymentRequestInterfaceHandler : PaymentRequestInterfaceH
{
public static void Register()
{
- PaymentRequestTransportInitiator.Handlers.Add(new NostrNip17PaymentRequestInterfaceHandler());
+ PaymentRequestTransportInitiator.Handlers.Add(
+ new NostrNip17PaymentRequestInterfaceHandler()
+ );
}
public bool CanHandle(PaymentRequest request)
@@ -18,13 +20,15 @@ public bool CanHandle(PaymentRequest request)
return request.Transports.Any(t =>
t.Type == "nostr" &&
t.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true);
-
}
- public async Task SendPayment(PaymentRequest request, PaymentRequestPayload payload,
- CancellationToken cancellationToken = default)
- {
- var nostrTransport = request.Transports.FirstOrDefault(t =>
+ public async Task SendPayment(
+ PaymentRequest request,
+ PaymentRequestPayload payload,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var nostrTransport = request.Transports.FirstOrDefault(t =>
t.Type == "nostr" &&
t.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true);
if (nostrTransport is null)
@@ -32,9 +36,8 @@ public async Task SendPayment(PaymentRequest request, PaymentRequestPayload payl
throw new InvalidOperationException("No NIP17 nostr transport found.");
}
var nprofileStr = nostrTransport.Target;
-
- var nprofile = (NIP19.NosteProfileNote) NIP19.FromNIP19Note(nprofileStr);
- using var client = new CompositeNostrClient(nprofile.Relays.Select(r => new Uri(r)).ToArray());
+ 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()
@@ -46,9 +49,13 @@ public async Task SendPayment(PaymentRequest request, PaymentRequestPayload payl
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);
+ var giftWrap = await NIP17.Create(
+ msg,
+ ephemeralKey,
+ ECXOnlyPubKey.Create(Convert.FromHexString(nprofile.PubKey)),
+ null
+ );
+ await client.SendEventsAndWaitUntilReceived(new[] { giftWrap }, cancellationToken);
}
-}
\ No newline at end of file
+}
diff --git a/DotNut.Tests/DotNut.Tests.csproj b/DotNut.Tests/DotNut.Tests.csproj
index 122cff3..fcb505b 100644
--- a/DotNut.Tests/DotNut.Tests.csproj
+++ b/DotNut.Tests/DotNut.Tests.csproj
@@ -1,29 +1,27 @@
+
+ net8.0
+ enable
+ enable
-
- net8.0
- enable
- enable
+ false
+ true
+
- false
- true
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
diff --git a/DotNut.Tests/GlobalUsings.cs b/DotNut.Tests/GlobalUsings.cs
index 8c927eb..c802f44 100644
--- a/DotNut.Tests/GlobalUsings.cs
+++ b/DotNut.Tests/GlobalUsings.cs
@@ -1 +1 @@
-global using Xunit;
\ No newline at end of file
+global using Xunit;
diff --git a/DotNut.Tests/Integration.cs b/DotNut.Tests/Integration.cs
new file mode 100644
index 0000000..b9ed52f
--- /dev/null
+++ b/DotNut.Tests/Integration.cs
@@ -0,0 +1,981 @@
+using System.Security.Cryptography;
+using DotNut.Abstractions;
+using DotNut.Abstractions.Websockets;
+using DotNut.Api;
+using DotNut.ApiModels;
+
+namespace DotNut.Tests;
+
+public class Integration
+{
+ private static string MintUrl = "http://localhost:3338";
+
+ // private static string MintUrl = "https://fake.thesimplekid.dev";
+ // private static string MintUrl = "https://testnut.cashu.space";
+
+ private static string seed =
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
+
+ // for now cdk mint returns 500 if there's created melt quote for the same invoice twice
+ private static readonly Dictionary valuesInvoices = new Dictionary()
+ {
+ {
+ 500,
+ "lnbc5u1p5sh0yvsp53seej3qkkxe6xxk9mufaj7y3jc9s9kvfn4g3whppwqcl4vcjraaspp5vtv793xc9ksch8zekkhqtv54a2evh7vq4zuywcmk9nzt69qma5yqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjq0ly0l075re9ltgqzdycartvas6g4c7kwwzpasj7a98c0ss679hdsr080vqqdcgqqqqqqqqnqqqqryqqxv9qxpqysgqwq50283v8asna95fktaeg80kq9evs0chaw44y6y649qsql9vsfc5gfcsp8rdwwyccepwy83n7g0s25n3lpv3hjgcr220n5w806fja8gp2xjvd7"
+ },
+ {
+ 501,
+ "lnbc5010n1p5shs9rsp5a2qhmn05xsd8vcm5jx9v2aswkz0pxguk4jqlaxsazzcg5rduan2qpp5al2k5zwruvlx34sxxdys2sj696m58uqgjvzxxrxhvuyswhmzg5cqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjqw7c9dkkx4nur9sztw2zzpzj8u8rgsqgdsykylg5pwplh26824lc7rvlqcqqn3gqqyqqqqlgqqqqqqgq2q9qxpqysgqgpj2x2aw2dv5tzhx86th6a5vutpcdxz9htewqgvzjgqkzwmh6xs5mw5xcgrzyq77f35shv0gg5ygtjmn7e73wg8v0a9g836ufszdxmqqqu3642"
+ },
+ {
+ 502,
+ "lnbc5020n1p5j3nxasp5qz7utfrp954nxp8049tqzg0t23krdj59thfcrc2g5h6lsemzvyfqpp5ms6xd7grtak0nr8lwytsclmq3d233v7gy7j0kuw32txhjq0f8ngqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjqg587a2yyuqeua9c3j8nw7wwpx709slwl5lzfs0t0vq3kdwemzp67rtwevqq95gqqyqqq5sgqqq9yzqq2q9qxpqysgqgpp0zsetj9fedvr0szpwjfw2weckygmjthhnfpp2cerjtrn8n0pxyvrtc00l0jwzkqhwedcvgqljtwx3a7qplqp43jlxe4mpmw5svlgqfwa9yy"
+ },
+ {
+ 503,
+ "lnbc5030n1p5n9kk6sp5ee6rsflv9rnnyt80ucc0fzlwa975nmufs2dn0x3u0hlerxxtc4nqpp5ew5mxfmu966c8wywvnvtgsljduq0jduvdpc3jzqzq9uqafx7ejgshp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjqdqne4nrkxmz96ktnngat4nzx7sv0kf5uqmgfvqvvars7pac7fn9wr8fjvqq3csqqqqqqqqqqqqq0scqvs9qxpqysgq5lqwgfk6vv36tnlx2tv6reu2587x8ha2wsht0s75dpzvmknpgepsqaq9wnlx7n87j3x6w0vvkvc4qgda6mhacygn9f0xgagwt84uxtcqgtcpfs"
+ },
+ {
+ 999,
+ "lnbc9990n1p5j3cf7sp575w4pw93kfrghl2gh68885v76gwjpzuv435t52q846cvx4w7yuvqpp5hdzvm3yf0r3vj99c7esmcv7zuj2fralf2twhl6s9xqcgr8g7nwyqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjqt0mfswatysklf4z358sztscs5t0vdghmd5vfe9c9sa0gy6r5pdugrs7myqqvgqqqyqqqqqqqqqq86qq8s9qxpqysgq5wh9l4fy32ww4770mqm7yqvhwllaqyssvp335gjz6t59ca03gecyvdd9uv0ztrcm2uf2352wvwxcfh7yukucp4p6zu6ll867aj686wsqz0jlmt"
+ },
+ {
+ 1000,
+ "lnbc10u1p5w6vggsp5gn5xhswgn5299w6elu2z0vzjxhf9hwd6pwjcgfwphaxunyu0dx6spp5a60trrhce2u6tzqjwjczem8rpdesgzkawcqg2xqaesz2kd50z4uqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjqw22kc09xj0dm65ew5h5r003vtn72eyzchdgjag66l0yhwdudfmuzrwvesqq8qgqqgqqqqqqqqqqzhsq2q9qxpqysgq955gcfr95wwz0ehtnk3xraatkyhj88z44ku7yqutnwnt3gkh82jxehvdff7n2js2p54jgpvg6dmwmq8t9d8x05j63mqjrsr4cwd4lpcpnc39ru"
+ },
+ {
+ 1150,
+ "lnbc11500n1p5jnmr7sp5u0s2wpuqn4mp0axyzgmsxzf5v8sy3zmzz9a7jyq38luyx9cntazqpp57j3carehwt4tqthxz9z7ea80t0htklh4v6v96dtn4vxuu4kwsershp53mwsvrcmkv743nyfzjp5a5fqrg2yngda3apf7jf9rzsuwt82wt3sxq9z0rgqcqpnrzjqg587a2yyuqeua9c3j8nw7wwpx709slwl5lzfs0t0vq3kdwemzp67rtwevqq95gqqyqqq5sgqqq9yzqq2q9qxpqysgqe97nwd9q74ua0sl9877sdprjcuc6jpyy8c52azpz8au6ur8q3838c0a0upnahs8w3sec8kxh26m3v9rkgqej36652t3sa5t25svacdcq5qwwjp"
+ },
+ {
+ 1151,
+ "lnbc11510n1p5n9hzpsp5ey8npxa4nsaet73nc74lky0mv780h6890ua3kqhffvn8heqzk33spp5df0xt9s0e0kh0rx7dcy39u4q3g7cknk88wr4s90cldv6z2vwspgqhp5uwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2sxq9z0rgqcqpnrzjq0xp6zfjhwvmq6tltd09jcdc82ml6eh3alzvnaw8httxcx7tu78syrvfkqqqm0qqqyqqqqlgqqqvx5qqjq9qxpqysgqwnys3mklnsnrw5ysa8cjtynlqnllyxskcsamr7x96nl5kllyqcznlyeeuklr3zydeq43k6ckyrgqqfg965dsdjc675lvlssn0z4sxusq0lzrx6"
+ },
+ {
+ 2000,
+ "lnbc20u1p5094fksp54vrdcymel5awhrpc0m6z4kvhhyvqlwkshkyt2wr6eyljkz8c798qpp59f2vc8td8tu62gtf4qfwzkrkxedsey7a5ajrd48a25z2kkwg407shp5nklhn663zgwcdnh7pe5jxt6td0cchhre6hxzdxrjdlfwtpq60f5sxq9z0rgqcqpnrzjqw0de9yc0j8n4hpgm269tm7qph4gwcyf5ys02uaapvpugrva87c7zr045uqq4jsqpsqqqqlgqqqqrcgq2q9qxpqysgq6g2pamgjumh6uw5k5rj2ket44wh8nfzs5gzyygl54hu5cefuxdhxp9h5mrg64rh07znktn9x9d5vg6fc0rw7m63x8rg4qk3kw6d8sycpywn48m"
+ },
+ };
+
+ private static readonly Dictionary bolt12Invoices = new()
+ {
+ {
+ 1200,
+ "lno1zrxq8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0qwumyhd9aa7p77jkp946nkphl2lutxa3e5zp8yx36pyycqas85txgqsre4qsrhyu2jqk5svgnwe5tng78r24dlwwglluetkdv4a5ppc3wanqqvlfkaqp5hhc6jl8eq0mau6wsdxevary7e0e3rpmma7plggygs7fr4e6dj8vflurnt7ajhgwxfu9hmqmf48wqd6tzuxmwdcgk9p6wspfqer0xj883lysflutn8qvudzakypdv8a7kqqsv0vcrt5w208yr5uzregj7whghy"
+ },
+ };
+ private static ICounter counter = new InMemoryCounter();
+
+ [Fact]
+ public async Task FetchesInfoSuccessfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+ var info = await wallet.GetInfo();
+ Assert.NotNull(info);
+ }
+
+ [Fact]
+ public async Task MintsBolt11Successfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithUnit("sat")
+ .WithAmount(1337)
+ .ProcessAsyncBolt11();
+
+ Assert.NotNull(mintQuote);
+
+ var paymentRequest = mintQuote.GetQuote().Request;
+ Assert.Contains("lnbc1337", paymentRequest);
+
+ await PayInvoice();
+
+ var mintResponse = await mintQuote.Mint();
+ Assert.NotNull(mintResponse);
+ Assert.Equal(1337UL, Utils.SumProofs(mintResponse));
+ }
+
+ [Fact]
+ public async Task MintsBolt12Successfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+ var privkey = new PrivKey(RandomNumberGenerator.GetHexString(64));
+
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithPubkey(privkey.Key.CreatePubKey())
+ .WithUnit("sat")
+ .WithAmount(1337)
+ .ProcessAsyncBolt12();
+
+ Assert.NotNull(mintQuote);
+
+ var paymentRequest = mintQuote.GetQuote().Request;
+ Assert.NotNull(paymentRequest);
+ mintQuote.SignWithPrivkey(privkey);
+
+ await PayInvoice();
+ var mintResponse = await mintQuote.Mint();
+ Assert.NotNull(mintResponse);
+ Assert.Equal(1337UL, Utils.SumProofs(mintResponse));
+ }
+
+ [Fact]
+ public async Task MintsDeterministicSuccessfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl).WithMnemonic(seed).WithCounter(counter);
+
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithUnit("sat")
+ .WithAmount(1337)
+ .ProcessAsyncBolt11();
+
+ Assert.NotNull(mintQuote);
+
+ var paymentRequest = mintQuote.GetQuote().Request;
+ Assert.Contains("lnbc1337", paymentRequest);
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+
+ var keysetId = mintedProofs.First().Id;
+ var currentCounter = await counter.GetCounterForId(keysetId);
+ // counter is bumped after every use, so its already one more
+ Assert.Equal(currentCounter, (uint)mintedProofs.Count);
+ }
+
+ [Fact]
+ public async Task RestoresSuccessfully()
+ {
+ var phreshCounter = new InMemoryCounter();
+
+ var wallet = Wallet
+ .Create()
+ .WithCounter(phreshCounter)
+ .WithMint(MintUrl)
+ .WithMnemonic(seed);
+
+ var restoredProofs = await wallet.Restore().ProcessAsync();
+
+ var keys = (await wallet.GetKeys()).First().Keys;
+ var expectedAmount = Utils.SplitToProofsAmounts(1336UL, keys).Count; // (one for fee)
+ var keysets = await wallet.GetKeysets();
+
+ foreach (var keyset in keysets)
+ {
+ // new counter will be bumped to newest state
+ Assert.Equal(
+ await counter.GetCounterForId(keyset.Id) + expectedAmount,
+ await phreshCounter.GetCounterForId(keyset.Id)
+ );
+ }
+ Assert.Equal(expectedAmount, restoredProofs.Count());
+
+ // assign restored counter to previous one, so next tests can use it safely
+ counter = phreshCounter;
+ }
+
+ [Fact]
+ public async Task SwapsSuccessfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ // 1. mint some proofs (deterministic, because why not)
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithAmount(64)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ //2. Swap them
+ var newProofs = await wallet.Swap().FromInputs(mintedProofs).ProcessAsync();
+
+ Assert.NotEmpty(newProofs);
+ }
+
+ [Fact]
+ public async Task SwapsDeterministicSuccessfully()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl).WithMnemonic(seed).WithCounter(counter);
+
+ // 1. mint some proofs (deterministic, because why not)
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithAmount(64)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ //2. Swap them
+ var newProofs = await wallet.Swap().FromInputs(mintedProofs).ProcessAsync();
+
+ Assert.NotEmpty(newProofs);
+ }
+
+ [Fact]
+ public async Task MeltsBolt11Successfully()
+ {
+ // mint proofs
+ var wallet = Wallet.Create().WithMint(MintUrl).WithMnemonic(seed).WithCounter(counter);
+
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithUnit("sat")
+ .WithAmount(1337)
+ .ProcessAsyncBolt11();
+ await Task.Delay(3000);
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ // create melt quote
+ var meltQuote = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[999])
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ // select proofs to send
+ var q = meltQuote.GetQuote();
+ var selectedProofs = await wallet.SelectProofsToSend(
+ mintedProofs,
+ q.Amount + (ulong)q.FeeReserve,
+ true
+ );
+
+ //melt proofs
+ var change = await meltQuote.Melt(selectedProofs.Send);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task MeltsBolt12Successfully()
+ {
+ var privkeyBob = new PrivKey(RandomNumberGenerator.GetBytes(32));
+
+ // mint proofs
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithUnit("sat")
+ .WithAmount(1337)
+ .WithPubkey(privkeyBob.Key.CreatePubKey())
+ .ProcessAsyncBolt12();
+
+ await Task.Delay(3000);
+
+ mintQuote.SignWithPrivkey(privkeyBob);
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ var Ids = mintedProofs.Select(proof => proof.Id).Count();
+
+ Console.WriteLine($"amounts {Ids}");
+ // create melt quote
+ var meltQuote = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(bolt12Invoices[1200])
+ .WithUnit("sat")
+ .WithAmount(1200) // it turns out that this invoice is amountless
+ .ProcessAsyncBolt12();
+
+ // select proofs to send
+ var q = meltQuote.GetQuote();
+ var selectedProofs = await wallet.SelectProofsToSend(
+ mintedProofs,
+ q.Amount + (ulong)q.FeeReserve,
+ true
+ );
+
+ //melt proofs
+ var change = await meltQuote.Melt(selectedProofs.Send);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task SubscribeToMintMeltQuoteUpdates()
+ {
+ await using var service = new WebsocketService();
+ var connection = await service.ConnectAsync(MintUrl);
+ Assert.NotNull(connection);
+
+ var wallet = Wallet.Create().WithMint(MintUrl).WithWebsocketService(service);
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(3338)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ var quote = mintHandler.GetQuote();
+
+ var sub = await service.SubscribeToMintQuoteAsync(MintUrl, new[] { quote.Quote });
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(240));
+
+ int connectedCount = 0;
+ bool gotPaid = false;
+
+ await foreach (var msg in sub.NotificationChannel.Reader.ReadAllAsync(cts.Token))
+ {
+ switch (msg)
+ {
+ case WsMessage.Response:
+ connectedCount++;
+ break;
+
+ case WsMessage.Notification notification:
+ var parsed = NotificationParser.ParsePayload(
+ notification.Value
+ );
+ if (parsed?.State == "PAID")
+ {
+ gotPaid = true;
+ await sub.CloseAsync();
+ }
+
+ break;
+
+ case WsMessage.Error error:
+ Assert.Fail($"WebSocket error: {error}");
+ break;
+ }
+
+ if (gotPaid)
+ break;
+ }
+
+ Assert.Equal(1, connectedCount);
+ Assert.True(gotPaid, "Expected to receive PAID notification");
+
+ var proofs = await mintHandler.Mint(cts.Token);
+ Assert.NotEmpty(proofs);
+ }
+
+ [Fact]
+ public async Task InvoiceWithDescription()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithDescription("Test Description")
+ .WithAmount(1337)
+ .ProcessAsyncBolt11();
+
+ Assert.NotNull(quote);
+ }
+
+ [Fact]
+ public async Task FeeForExternalInvoice()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[2000])
+ .ProcessAsyncBolt11();
+
+ Assert.NotNull(meltHandler);
+
+ var quote = meltHandler.GetQuote();
+
+ Assert.NotNull(quote);
+ Assert.True(quote.FeeReserve > 0);
+ }
+
+ [Fact]
+ public async Task SwapP2Pk()
+ {
+ // p2pk aren't deterministic, so wallet is initialized without mnemonic and counter
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyAlice = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(
+ new P2PkBuilder()
+ {
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ }
+ )
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await mintHandler.Mint();
+
+ await Assert.ThrowsAsync(async () =>
+ await wallet.Swap().FromInputs(proofs).ProcessAsync()
+ );
+
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(proofs)
+ .WithPrivkeys([privKeyBob])
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ }
+
+ [Fact]
+ public async Task MintMeltP2PkMultisig()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyAlice = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(
+ new P2PkBuilder()
+ {
+ Pubkeys = [privKeyBob.Key.CreatePubKey(), privKeyAlice.Key.CreatePubKey()],
+ SignatureThreshold = 2,
+ }
+ )
+ .ProcessAsyncBolt11();
+ await PayInvoice();
+
+ var proofs = await mintHandler.Mint();
+
+ Assert.NotEmpty(proofs);
+
+ // no privkeys
+ await Assert.ThrowsAsync(async () =>
+ {
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[500])
+ .ProcessAsyncBolt11();
+ await meltHandler.Melt(proofs);
+ });
+
+ var handler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[501])
+ .WithPrivKeys([privKeyBob, privKeyAlice])
+ .ProcessAsyncBolt11();
+
+ var q = handler.GetQuote();
+
+ var amountToPay = q.Amount + (ulong)q.FeeReserve;
+ var selectorResponse = await wallet.SelectProofsToSend(proofs, amountToPay, true);
+ var change = await handler.Melt(selectorResponse.Send);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task MintSwapP2PkSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyAlice = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(
+ new P2PkBuilder()
+ {
+ SigFlag = "SIG_ALL",
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ }
+ )
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await mintHandler.Mint();
+
+ await Assert.ThrowsAsync(async () =>
+ await wallet.Swap().FromInputs(proofs).ProcessAsync()
+ );
+
+
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(proofs)
+ .WithPrivkeys([privKeyBob])
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ }
+
+ [Fact]
+ public async Task MintSwapP2Bk()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var privKeyAlice = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var builder = new P2PkBuilder()
+ {
+ Pubkeys = [privKeyBob.Key.CreatePubKey(), privKeyAlice.Key.CreatePubKey()],
+ };
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(builder)
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await quote.Mint();
+
+ Assert.NotEmpty(proofs);
+ Assert.NotEmpty(proofs.Select(p => p.P2PkE));
+
+ var newProofs = await wallet
+ .Swap()
+ .FromInputs(proofs)
+ .WithPrivkeys([privKeyBob, privKeyAlice])
+ .ProcessAsync();
+
+ Assert.NotEmpty(newProofs);
+ }
+
+ [Fact]
+ public async Task MintMeltP2Bk()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var builder = new P2PkBuilder() { Pubkeys = [privKeyBob.Key.CreatePubKey()] };
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(builder)
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await quote.Mint();
+
+ Assert.NotEmpty(proofs);
+ Assert.NotEmpty(proofs.Select(p => p.P2PkE));
+
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[502])
+ .WithPrivKeys([privKeyBob])
+ .ProcessAsyncBolt11();
+
+ var change = await meltHandler.Melt(proofs);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task MintMeltP2BkSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var builder = new P2PkBuilder()
+ {
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SigFlag = "SIG_ALL",
+ };
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(builder)
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await quote.Mint();
+
+ Assert.NotEmpty(proofs);
+ Assert.NotEmpty(proofs.Select(p => p.P2PkE));
+
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[503])
+ .WithPrivKeys([privKeyBob])
+ .ProcessAsyncBolt11();
+
+ var change = await meltHandler.Melt(proofs);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task MintSwapP2BkSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyAlice = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithP2PkLock(
+ new P2PkBuilder()
+ {
+ SigFlag = "SIG_ALL",
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ }
+ )
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await mintHandler.Mint();
+
+ await Assert.ThrowsAsync(async () =>
+ await wallet.Swap().FromInputs(proofs).ProcessAsync()
+ );
+
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(proofs)
+ .WithPrivkeys([privKeyBob])
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ }
+
+ [Fact]
+ public async Task MintSwapHTLC()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var preimage = "0000000000000000000000000000000000000000000000000000000000000001";
+ var hashLock = Convert.ToHexString(SHA256.HashData(Convert.FromHexString(preimage)));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithHTLCLock(
+ new HTLCBuilder()
+ {
+ HashLock = hashLock,
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ }
+ )
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var htlcProofs = await mintHandler.Mint();
+
+ Assert.NotEmpty(htlcProofs);
+ Assert.Equal(1337UL, Utils.SumProofs(htlcProofs));
+
+ // try swap without preimage - should fail
+ await Assert.ThrowsAsync(async () =>
+ {
+ await wallet.Swap().FromInputs(htlcProofs).WithPrivkeys([privKeyBob]).ProcessAsync();
+ });
+
+ // swap with correct preimage and signature
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(htlcProofs)
+ .WithPrivkeys([privKeyBob])
+ .WithHtlcPreimage(preimage)
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ // fee is 100 ppk - it can be calculated before but here we don't care
+ Assert.Equal(1337UL - 1, Utils.SumProofs(swappedProofs));
+ }
+
+ [Fact]
+ public async Task MintSwapHTLCSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var preimage = "0000000000000000000000000000000000000000000000000000000000000001";
+ var hashLock = Convert.ToHexString(SHA256.HashData(Convert.FromHexString(preimage)));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithHTLCLock(
+ new HTLCBuilder()
+ {
+ HashLock = hashLock,
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ SigFlag = "SIG_ALL",
+ }
+ )
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var htlcProofs = await mintHandler.Mint();
+
+ Assert.NotEmpty(htlcProofs);
+ Assert.Equal(1337UL, Utils.SumProofs(htlcProofs));
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await wallet.Swap().FromInputs(htlcProofs).WithPrivkeys([privKeyBob]).ProcessAsync();
+ });
+
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(htlcProofs)
+ .WithPrivkeys([privKeyBob])
+ .WithHtlcPreimage(preimage)
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ Assert.Equal(1337UL - 1, Utils.SumProofs(swappedProofs));
+ }
+
+ [Fact]
+ public async Task MintSwapHtlcP2BkSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var preimage = "0000000000000000000000000000000000000000000000000000000000000001";
+ var hashLock = Convert.ToHexString(SHA256.HashData(Convert.FromHexString(preimage)));
+
+ var mintHandler = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithHTLCLock(
+ new HTLCBuilder()
+ {
+ HashLock = hashLock,
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ SigFlag = "SIG_ALL",
+ }
+ )
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var htlcProofs = await mintHandler.Mint();
+
+ Assert.NotEmpty(htlcProofs);
+ Assert.Equal(1337UL, Utils.SumProofs(htlcProofs));
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await wallet.Swap().FromInputs(htlcProofs).WithPrivkeys([privKeyBob]).ProcessAsync();
+ });
+
+ var swappedProofs = await wallet
+ .Swap()
+ .FromInputs(htlcProofs)
+ .WithPrivkeys([privKeyBob])
+ .WithHtlcPreimage(preimage)
+ .ProcessAsync();
+
+ Assert.NotEmpty(swappedProofs);
+ Assert.Equal(1337UL - 1, Utils.SumProofs(swappedProofs));
+ }
+
+ [Fact]
+ public async Task MintMeltHTLCP2Bk()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var preimage = new string('0', 63) + "1";
+
+ var builder = new HTLCBuilder()
+ {
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ HashLock = Convert.ToHexString(SHA256.HashData(Convert.FromHexString(preimage))),
+ };
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithHTLCLock(builder)
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await quote.Mint();
+
+ Assert.NotEmpty(proofs);
+ Assert.NotEmpty(proofs.Select(p => p.P2PkE));
+
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[1150])
+ .WithPrivKeys([privKeyBob])
+ .WithHTLCPreimage(preimage)
+ .ProcessAsyncBolt11();
+
+ var change = await meltHandler.Melt(proofs);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task MintMeltHTLCP2BkSigAll()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ var privKeyBob = new PrivKey(RandomNumberGenerator.GetHexString(64, true));
+ var preimage = new string('0', 63) + "1";
+
+ var builder = new HTLCBuilder()
+ {
+ SigFlag = "SIG_ALL",
+ Pubkeys = [privKeyBob.Key.CreatePubKey()],
+ HashLock = Convert.ToHexString(SHA256.HashData(Convert.FromHexString(preimage))),
+ };
+
+ var quote = await wallet
+ .CreateMintQuote()
+ .WithAmount(1337)
+ .WithHTLCLock(builder)
+ .BlindPubkeys()
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var proofs = await quote.Mint();
+
+ Assert.NotEmpty(proofs);
+ Assert.NotEmpty(proofs.Select(p => p.P2PkE));
+
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[1151])
+ .WithPrivKeys([privKeyBob])
+ .WithHTLCPreimage(preimage)
+ .ProcessAsyncBolt11();
+
+ var change = await meltHandler.Melt(proofs);
+
+ Assert.NotEmpty(change);
+ }
+
+ [Fact]
+ public async Task SwapWithCustomAmounts()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ // mint some proofs
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithAmount(100)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ // swap with specific amounts
+ var desiredAmounts = new List { 32, 32, 32, 2, 1 }; // 96 sat (should consume 1 for fees)
+ var newProofs = await wallet
+ .Swap()
+ .FromInputs(mintedProofs)
+ .WithAmounts(desiredAmounts)
+ .ProcessAsync();
+
+ Assert.NotEmpty(newProofs);
+ // amount should be at least the requested amounts
+ Assert.True(Utils.SumProofs(newProofs) >= 96);
+ }
+
+ [Fact]
+ public async Task SwapToSpecificKeyset()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ // get active keyset
+ var activeKeysetId = await wallet.GetActiveKeysetId("sat");
+ Assert.NotNull(activeKeysetId);
+
+ // mint some proofs
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithAmount(64)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ // swap to specific keyset
+ var newProofs = await wallet
+ .Swap()
+ .FromInputs(mintedProofs)
+ .ForKeyset(activeKeysetId)
+ .ProcessAsync();
+
+ Assert.NotEmpty(newProofs);
+ Assert.All(newProofs, p => Assert.Equal(activeKeysetId, p.Id));
+ }
+
+ [Fact]
+ public async Task MeltWithInsufficientFunds()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+
+ // mint small amount
+ var mintQuote = await wallet
+ .CreateMintQuote()
+ .WithAmount(10)
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ await PayInvoice();
+ var mintedProofs = await mintQuote.Mint();
+ Assert.NotEmpty(mintedProofs);
+
+ // try to melt for larger invoice - should fail during proof selection
+ var meltHandler = await wallet
+ .CreateMeltQuote()
+ .WithInvoice(valuesInvoices[1000]) // 1000 sat invoice
+ .WithUnit("sat")
+ .ProcessAsyncBolt11();
+
+ var quote = meltHandler.GetQuote();
+ var amountNeeded = quote.Amount + (ulong)quote.FeeReserve;
+
+ // selectProofsToSend should return empty Send list when insufficient
+ var selection = await wallet.SelectProofsToSend(mintedProofs, amountNeeded, true);
+ Assert.Empty(selection.Send);
+ Assert.NotEmpty(selection.Keep);
+ }
+
+ private async Task PayInvoice()
+ {
+ //We're using fakewallet, so after 3 secs it will get paid automatically. After 3.5 sec its 1000% paid.
+ await Task.Delay(3500);
+ }
+}
diff --git a/DotNut.Tests/UnitTest1.cs b/DotNut.Tests/UnitTest1.cs
index 94b899b..7d56cf7 100644
--- a/DotNut.Tests/UnitTest1.cs
+++ b/DotNut.Tests/UnitTest1.cs
@@ -17,12 +17,18 @@ public UnitTest1(ITestOutputHelper testOutputHelper)
_testOutputHelper = testOutputHelper;
}
- [InlineData("0000000000000000000000000000000000000000000000000000000000000000",
- "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725")]
- [InlineData("0000000000000000000000000000000000000000000000000000000000000001",
- "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf")]
- [InlineData("0000000000000000000000000000000000000000000000000000000000000002",
- "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f")]
+ [InlineData(
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725"
+ )]
+ [InlineData(
+ "0000000000000000000000000000000000000000000000000000000000000001",
+ "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf"
+ )]
+ [InlineData(
+ "0000000000000000000000000000000000000000000000000000000000000002",
+ "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f"
+ )]
[Theory]
public void Nut00Tests_HashToCurve(string message, string point)
{
@@ -30,13 +36,16 @@ public void Nut00Tests_HashToCurve(string message, string point)
Assert.Equal(point, result.ToHex());
}
-
- [InlineData("d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6",
+ [InlineData(
+ "d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6",
"99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a",
- "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d")]
- [InlineData("f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60",
+ "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
+ )]
+ [InlineData(
+ "f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60",
"f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50",
- "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763")]
+ "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
+ )]
[Theory]
public void Nut00Tests_BlindedMessages(string x, string r, string b)
{
@@ -47,12 +56,16 @@ public void Nut00Tests_BlindedMessages(string x, string r, string b)
Assert.Equal(b, computedB.ToHex());
}
- [InlineData("0000000000000000000000000000000000000000000000000000000000000001",
+ [InlineData(
+ "0000000000000000000000000000000000000000000000000000000000000001",
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
- "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")]
- [InlineData("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+ "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
+ )]
+ [InlineData(
+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
- "0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d")]
+ "0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d"
+ )]
[Theory]
public void Nut00Tests_BlindedSignatures(string k, string b_, string blindedKey)
{
@@ -69,94 +82,122 @@ public void Nut00Tests_TokenSerialization()
{
string originalToken =
"cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
- var result = CashuTokenHelper.Decode(
- originalToken,
- out var v);
+ var result = CashuTokenHelper.Decode(originalToken, out var v);
Assert.Equal("A", v);
Assert.Equal("Thank you.", result.Memo);
Assert.Equal("sat", result.Unit);
var token = Assert.Single(result.Tokens);
Assert.Equal("https://8333.space:3338", token.Mint);
Assert.Equal(2, token.Proofs.Count);
- Assert.Collection(token.Proofs, proof =>
+ Assert.Collection(
+ token.Proofs,
+ proof =>
{
Assert.Equal((ulong)2, proof.Amount);
Assert.Equal(new KeysetId("009a1f293253e41e"), proof.Id);
- Assert.Equal("407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837",
- Assert.IsType(proof.Secret).Secret);
- Assert.Equal("02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea".ToPubKey(),
- (ECPubKey) proof.C);
- }, proof =>
+ Assert.Equal(
+ "407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837",
+ Assert.IsType(proof.Secret).Secret
+ );
+ Assert.Equal(
+ "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea".ToPubKey(),
+ (ECPubKey)proof.C
+ );
+ },
+ proof =>
{
Assert.Equal((ulong)8, proof.Amount);
Assert.Equal(new KeysetId("009a1f293253e41e"), proof.Id);
- Assert.Equal("fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be",
- Assert.IsType(proof.Secret).Secret);
- Assert.Equal("029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059".ToPubKey(),
- (ECPubKey) proof.C);
+ Assert.Equal(
+ "fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be",
+ Assert.IsType(proof.Secret).Secret
+ );
+ Assert.Equal(
+ "029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059".ToPubKey(),
+ (ECPubKey)proof.C
+ );
}
);
Assert.Equal(originalToken, result.Encode("A", false));
- Assert.Throws(() => CashuTokenHelper.Decode(
- "casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9",
- out _));
- Assert.Throws(() => CashuTokenHelper.Decode(
- "eyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9",
- out _));
-
-
+ Assert.Throws(() =>
+ CashuTokenHelper.Decode(
+ "casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9",
+ out _
+ )
+ );
+ Assert.Throws(() =>
+ CashuTokenHelper.Decode(
+ "eyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9",
+ out _
+ )
+ );
-
var v4Token =
"cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA";
result = CashuTokenHelper.Decode(v4Token, out v);
-
+
Assert.Equal("B", v);
Assert.Null(result.Memo);
Assert.Equal("sat", result.Unit);
token = Assert.Single(result.Tokens);
Assert.Equal("http://localhost:3338", token.Mint);
Assert.Equal(3, token.Proofs.Count);
- Assert.Collection(token.Proofs, proof =>
+ Assert.Collection(
+ token.Proofs,
+ proof =>
{
Assert.Equal((ulong)1, proof.Amount);
Assert.Equal(new KeysetId("00ffd48b8f5ecf80"), proof.Id);
- Assert.Equal("acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388",
- Assert.IsType(proof.Secret).Secret);
- Assert.Equal("0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf".ToPubKey(),
- (ECPubKey) proof.C);
- }, proof =>
+ Assert.Equal(
+ "acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388",
+ Assert.IsType(proof.Secret).Secret
+ );
+ Assert.Equal(
+ "0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf".ToPubKey(),
+ (ECPubKey)proof.C
+ );
+ },
+ proof =>
{
Assert.Equal((ulong)2, proof.Amount);
Assert.Equal(new KeysetId("00ad268c4d1f5826"), proof.Id);
- Assert.Equal("1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee",
- Assert.IsType(proof.Secret).Secret);
- Assert.Equal("023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d".ToPubKey(),
- (ECPubKey) proof.C);
- }, proof =>
+ Assert.Equal(
+ "1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee",
+ Assert.IsType(proof.Secret).Secret
+ );
+ Assert.Equal(
+ "023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d".ToPubKey(),
+ (ECPubKey)proof.C
+ );
+ },
+ proof =>
{
Assert.Equal((ulong)1, proof.Amount);
Assert.Equal(new KeysetId("00ad268c4d1f5826"), proof.Id);
- Assert.Equal("56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57",
- Assert.IsType(proof.Secret).Secret);
- Assert.Equal("0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63".ToPubKey(),
- (ECPubKey) proof.C);
+ Assert.Equal(
+ "56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57",
+ Assert.IsType(proof.Secret).Secret
+ );
+ Assert.Equal(
+ "0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63".ToPubKey(),
+ (ECPubKey)proof.C
+ );
}
);
Assert.Equal(v4Token, result.Encode("B", false));
-
-
}
[Theory]
[InlineData(
- "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}")]
+ "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}"
+ )]
[InlineData(
- "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}")]
+ "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}"
+ )]
public void Nut01Tests_Keysets_Invalid(string keyset)
{
Assert.ThrowsAny(() => JsonSerializer.Deserialize(keyset));
@@ -164,9 +205,11 @@ public void Nut01Tests_Keysets_Invalid(string keyset)
[Theory]
[InlineData(
- "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}")]
+ "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}"
+ )]
[InlineData(
- "{\n \"1\":\"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\"2\":\"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\"4\":\"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\"8\":\"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\"16\":\"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\"32\":\"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\"64\":\"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\"128\":\"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\"256\":\"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\"512\":\"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\"1024\":\"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\"2048\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\"4096\":\"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\"8192\":\"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\"16384\":\"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\"32768\":\"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\"65536\":\"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\"131072\":\"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\"262144\":\"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\"524288\":\"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\"1048576\":\"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\"2097152\":\"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\"4194304\":\"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\"8388608\":\"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\"16777216\":\"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\"33554432\":\"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\"67108864\":\"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\"134217728\":\"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\"268435456\":\"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\"536870912\":\"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\"1073741824\":\"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\"2147483648\":\"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\"4294967296\":\"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\"8589934592\":\"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\"17179869184\":\"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\"34359738368\":\"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\"68719476736\":\"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\"137438953472\":\"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\"274877906944\":\"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\"549755813888\":\"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\"1099511627776\":\"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\"2199023255552\":\"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\"4398046511104\":\"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\"8796093022208\":\"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\"17592186044416\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\"35184372088832\":\"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\"70368744177664\":\"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\"140737488355328\":\"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\"281474976710656\":\"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\"562949953421312\":\"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\"1125899906842624\":\"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\"2251799813685248\":\"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\"4503599627370496\":\"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\"9007199254740992\":\"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\"18014398509481984\":\"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\"36028797018963968\":\"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\"72057594037927936\":\"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\"144115188075855872\":\"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\"288230376151711744\":\"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\"576460752303423488\":\"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\"1152921504606846976\":\"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\"2305843009213693952\":\"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\"4611686018427387904\":\"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\"9223372036854775808\":\"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}")]
+ "{\n \"1\":\"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\"2\":\"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\"4\":\"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\"8\":\"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\"16\":\"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\"32\":\"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\"64\":\"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\"128\":\"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\"256\":\"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\"512\":\"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\"1024\":\"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\"2048\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\"4096\":\"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\"8192\":\"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\"16384\":\"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\"32768\":\"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\"65536\":\"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\"131072\":\"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\"262144\":\"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\"524288\":\"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\"1048576\":\"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\"2097152\":\"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\"4194304\":\"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\"8388608\":\"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\"16777216\":\"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\"33554432\":\"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\"67108864\":\"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\"134217728\":\"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\"268435456\":\"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\"536870912\":\"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\"1073741824\":\"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\"2147483648\":\"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\"4294967296\":\"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\"8589934592\":\"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\"17179869184\":\"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\"34359738368\":\"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\"68719476736\":\"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\"137438953472\":\"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\"274877906944\":\"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\"549755813888\":\"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\"1099511627776\":\"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\"2199023255552\":\"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\"4398046511104\":\"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\"8796093022208\":\"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\"17592186044416\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\"35184372088832\":\"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\"70368744177664\":\"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\"140737488355328\":\"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\"281474976710656\":\"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\"562949953421312\":\"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\"1125899906842624\":\"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\"2251799813685248\":\"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\"4503599627370496\":\"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\"9007199254740992\":\"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\"18014398509481984\":\"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\"36028797018963968\":\"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\"72057594037927936\":\"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\"144115188075855872\":\"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\"288230376151711744\":\"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\"576460752303423488\":\"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\"1152921504606846976\":\"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\"2305843009213693952\":\"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\"4611686018427387904\":\"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\"9223372036854775808\":\"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}"
+ )]
public void Nut01Tests_Keysets_Valid(string keyset)
{
JsonSerializer.Deserialize(keyset);
@@ -174,35 +217,53 @@ public void Nut01Tests_Keysets_Valid(string keyset)
[Theory]
// v1
- [InlineData("00456a94ab4e1c46",
- "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}")]
- [InlineData("000f01df73ea149a",
- "{\n \"1\":\"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\"2\":\"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\"4\":\"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\"8\":\"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\"16\":\"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\"32\":\"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\"64\":\"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\"128\":\"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\"256\":\"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\"512\":\"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\"1024\":\"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\"2048\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\"4096\":\"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\"8192\":\"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\"16384\":\"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\"32768\":\"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\"65536\":\"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\"131072\":\"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\"262144\":\"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\"524288\":\"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\"1048576\":\"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\"2097152\":\"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\"4194304\":\"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\"8388608\":\"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\"16777216\":\"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\"33554432\":\"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\"67108864\":\"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\"134217728\":\"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\"268435456\":\"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\"536870912\":\"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\"1073741824\":\"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\"2147483648\":\"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\"4294967296\":\"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\"8589934592\":\"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\"17179869184\":\"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\"34359738368\":\"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\"68719476736\":\"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\"137438953472\":\"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\"274877906944\":\"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\"549755813888\":\"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\"1099511627776\":\"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\"2199023255552\":\"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\"4398046511104\":\"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\"8796093022208\":\"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\"17592186044416\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\"35184372088832\":\"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\"70368744177664\":\"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\"140737488355328\":\"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\"281474976710656\":\"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\"562949953421312\":\"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\"1125899906842624\":\"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\"2251799813685248\":\"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\"4503599627370496\":\"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\"9007199254740992\":\"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\"18014398509481984\":\"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\"36028797018963968\":\"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\"72057594037927936\":\"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\"144115188075855872\":\"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\"288230376151711744\":\"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\"576460752303423488\":\"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\"1152921504606846976\":\"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\"2305843009213693952\":\"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\"4611686018427387904\":\"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\"9223372036854775808\":\"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}")]
+ [InlineData(
+ "00456a94ab4e1c46",
+ "{\n \"1\":\"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\"2\":\"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\"4\":\"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\"8\":\"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}"
+ )]
+ [InlineData(
+ "000f01df73ea149a",
+ "{\n \"1\":\"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\"2\":\"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\"4\":\"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\"8\":\"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\"16\":\"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\"32\":\"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\"64\":\"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\"128\":\"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\"256\":\"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\"512\":\"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\"1024\":\"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\"2048\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\"4096\":\"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\"8192\":\"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\"16384\":\"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\"32768\":\"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\"65536\":\"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\"131072\":\"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\"262144\":\"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\"524288\":\"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\"1048576\":\"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\"2097152\":\"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\"4194304\":\"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\"8388608\":\"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\"16777216\":\"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\"33554432\":\"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\"67108864\":\"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\"134217728\":\"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\"268435456\":\"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\"536870912\":\"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\"1073741824\":\"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\"2147483648\":\"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\"4294967296\":\"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\"8589934592\":\"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\"17179869184\":\"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\"34359738368\":\"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\"68719476736\":\"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\"137438953472\":\"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\"274877906944\":\"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\"549755813888\":\"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\"1099511627776\":\"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\"2199023255552\":\"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\"4398046511104\":\"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\"8796093022208\":\"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\"17592186044416\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\"35184372088832\":\"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\"70368744177664\":\"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\"140737488355328\":\"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\"281474976710656\":\"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\"562949953421312\":\"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\"1125899906842624\":\"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\"2251799813685248\":\"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\"4503599627370496\":\"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\"9007199254740992\":\"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\"18014398509481984\":\"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\"36028797018963968\":\"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\"72057594037927936\":\"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\"144115188075855872\":\"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\"288230376151711744\":\"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\"576460752303423488\":\"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\"1152921504606846976\":\"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\"2305843009213693952\":\"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\"4611686018427387904\":\"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\"9223372036854775808\":\"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}"
+ )]
// v2
- [InlineData("015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a",
+ [InlineData(
+ "015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a",
"{\n \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}",
- (byte)1,
+ (byte)1,
"sat",
- 100UL,
- "2059210353"
- )]
- [InlineData("01ab6aa4ff30390da34986d84be5274b48ad7a74265d791095bfc39f4098d9764f",
+ 100UL,
+ 2059210353UL
+ )]
+ [InlineData(
+ "01ab6aa4ff30390da34986d84be5274b48ad7a74265d791095bfc39f4098d9764f",
"{\n \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}",
(byte)0x01,
"sat",
0UL,
- "2059210353"
- )]
- [InlineData("012fbb01a4e200c76df911eeba3b8fe1831202914b24664f4bccbd25852a6708f8",
+ 2059210353UL
+ )]
+ [InlineData(
+ "012fbb01a4e200c76df911eeba3b8fe1831202914b24664f4bccbd25852a6708f8",
"{\n \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}",
(byte)0x01,
"sat",
- 0UL)]
- public void Nut02Tests_KeysetIdMatch(string keysetId, string keyset, byte? version = null, string? unit = null, ulong? inputFee = null, string? finalExpiration = null)
+ 0UL
+ )]
+ public void Nut02Tests_KeysetIdMatch(
+ string keysetId,
+ string keyset,
+ byte? version = null,
+ string? unit = null,
+ ulong? inputFee = null,
+ ulong? finalExpiration = null
+ )
{
var keysetIdParsed = new KeysetId(keysetId);
var keysetParsed = JsonSerializer.Deserialize(keyset);
- Assert.Equal(keysetIdParsed, keysetParsed.GetKeysetId(version ?? 0x00, unit, inputFee, finalExpiration));
+ Assert.Equal(
+ keysetIdParsed,
+ keysetParsed.GetKeysetId(version ?? 0x00, unit, inputFee, finalExpiration)
+ );
}
[Theory]
@@ -214,23 +275,25 @@ public void Nut02Tests_KeysetIdVersion(string keysetId, byte version)
var keysetIdParsed = new KeysetId(keysetId);
Assert.Equal(version, keysetIdParsed.GetVersion());
}
-
-
[Fact]
public void Nut04Tests_Proofs_1()
{
var a = "0000000000000000000000000000000000000000000000000000000000000001".ToPrivKey();
var A = a.CreatePubKey();
- Assert.Equal("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".ToPubKey(), A);
+ Assert.Equal(
+ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".ToPubKey(),
+ A
+ );
var message = new StringSecret("secret_msg");
- var blindingFactor = "0000000000000000000000000000000000000000000000000000000000000001".ToPrivKey();
+ var blindingFactor =
+ "0000000000000000000000000000000000000000000000000000000000000001".ToPrivKey();
// var Y = Cashu.MessageToCurve(message);
var Y = message.ToCurve();
var B_ = Cashu.ComputeB_(Y, blindingFactor);
var C_ = Cashu.ComputeC_(B_, a);
- //p doesn;t have to be blinding factor. in fact it should be random nonce
-
+ //p doesn;t have to be blinding factor. in fact it should be random nonce
+
var proof = Cashu.ComputeProof(B_, a, blindingFactor);
Cashu.VerifyProof(B_, C_, proof.e, proof.s, A);
var C = Cashu.ComputeC(C_, blindingFactor, A);
@@ -238,12 +301,12 @@ public void Nut04Tests_Proofs_1()
Cashu.VerifyProof(Y, blindingFactor, C, proof.e, proof.s, A);
}
-
[Fact]
public void Nut04Tests_Proofs_2()
{
var A = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".ToPubKey();
- var proof = JsonSerializer.Deserialize(@"
+ var proof = JsonSerializer.Deserialize(
+ @"
{
""amount"": 1,
@@ -257,32 +320,48 @@ public void Nut04Tests_Proofs_2()
}
}
-");
+"
+ );
Assert.NotNull(proof?.DLEQ);
- Cashu.VerifyProof(Cashu.HexToCurve(Assert.IsType(proof.Secret).Secret), proof.DLEQ.R, proof.C,
- proof.DLEQ.E, proof.DLEQ.S, A);
+ Cashu.VerifyProof(
+ Cashu.HexToCurve(Assert.IsType(proof.Secret).Secret),
+ proof.DLEQ.R,
+ proof.C,
+ proof.DLEQ.E,
+ proof.DLEQ.S,
+ A
+ );
}
[Fact]
public void Nut11_Signatures()
{
- var secretKey =
- ECPrivKey.Create(Convert.FromHexString("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37"));
+ var secretKey = ECPrivKey.Create(
+ Convert.FromHexString(
+ "99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37"
+ )
+ );
- var signing_key_two =
- ECPrivKey.Create(Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"));
+ var signing_key_two = ECPrivKey.Create(
+ Convert.FromHexString(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ )
+ );
- var signing_key_three =
- ECPrivKey.Create(Convert.FromHexString("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"));
+ var signing_key_three = ECPrivKey.Create(
+ Convert.FromHexString(
+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
+ )
+ );
- var conditions = new P2PKBuilder
+ var conditions = new P2PkBuilder
{
Lock = DateTimeOffset.FromUnixTimeSeconds(21000000000),
- Pubkeys = new[] {signing_key_two.CreatePubKey(), signing_key_three.CreatePubKey()},
- RefundPubkeys = new[] {secretKey.CreatePubKey()},
+ Pubkeys = new[] { signing_key_two.CreatePubKey(), signing_key_three.CreatePubKey() },
+ RefundPubkeys = new[] { secretKey.CreatePubKey() },
SignatureThreshold = 2,
- SigFlag = "SIG_INPUTS"
+ SigFlag = "SIG_INPUTS",
};
var p2pkProofSecret = conditions.Build();
@@ -295,10 +374,13 @@ public void Nut11_Signatures()
Secret = secret,
C = "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904".ToPubKey(),
};
- var witness = p2pkProofSecret.GenerateWitness(proof, new[] {signing_key_two, signing_key_three});
+ var witness = p2pkProofSecret.GenerateWitness(
+ proof,
+ new[] { signing_key_two, signing_key_three }
+ );
proof.Witness = JsonSerializer.Serialize(witness);
Assert.True(p2pkProofSecret.VerifyWitness(proof.Secret, witness));
-
+
// SIG_INPUTS
var valid1 =
@@ -306,76 +388,119 @@ public void Nut11_Signatures()
var valid1Proof = JsonSerializer.Deserialize(valid1);
var valid1ProofSecret = Assert.IsType(valid1Proof.Secret);
Assert.Equal(P2PKProofSecret.Key, valid1ProofSecret!.Key);
- var valid1ProofSecretp2pkValue = Assert.IsType(valid1ProofSecret.ProofSecret);
+ var valid1ProofSecretp2pkValue = Assert.IsType(
+ valid1ProofSecret.ProofSecret
+ );
var valid1ProofWitnessP2pk = JsonSerializer.Deserialize(valid1Proof.Witness);
- Assert.True(valid1ProofSecretp2pkValue.VerifyWitness(valid1Proof.Secret, valid1ProofWitnessP2pk));
+ Assert.True(
+ valid1ProofSecretp2pkValue.VerifyWitness(valid1Proof.Secret, valid1ProofWitnessP2pk)
+ );
var invalid1 =
"{\n \"amount\": 1,\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n \"id\": \"009a1f293253e41e\",\n \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"\n}";
var invalid1Proof = JsonSerializer.Deserialize(invalid1);
var invalid1ProofSecret = Assert.IsType(invalid1Proof.Secret);
Assert.Equal(P2PKProofSecret.Key, invalid1ProofSecret!.Key);
- var invalid1ProofSecretp2pkValue = Assert.IsType(invalid1ProofSecret.ProofSecret);
- var invalid1ProofWitnessP2pk = JsonSerializer.Deserialize(invalid1Proof.Witness);
- Assert.False(invalid1ProofSecretp2pkValue.VerifyWitness(invalid1Proof.Secret, invalid1ProofWitnessP2pk));
+ var invalid1ProofSecretp2pkValue = Assert.IsType(
+ invalid1ProofSecret.ProofSecret
+ );
+ var invalid1ProofWitnessP2pk = JsonSerializer.Deserialize(
+ invalid1Proof.Witness
+ );
+ Assert.False(
+ invalid1ProofSecretp2pkValue.VerifyWitness(
+ invalid1Proof.Secret,
+ invalid1ProofWitnessP2pk
+ )
+ );
var validMultisig =
"{\"amount\":1,\"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\"C\":\"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\"id\":\"009a1f293253e41e\",\"witness\":\"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\",\\\"9a72ca2d4d5075be5b511ee48dbc5e45f259bcf4a4e8bf18587f433098a9cd61ff9737dc6e8022de57c76560214c4568377792d4c2c6432886cc7050487a1f22\\\"]}\"}";
var validMultisigProof = JsonSerializer.Deserialize(validMultisig);
var validMultisigProofSecret = Assert.IsType(validMultisigProof.Secret);
Assert.Equal(P2PKProofSecret.Key, validMultisigProofSecret!.Key);
- var validMultisigProofSecretp2pkValue = Assert.IsType(validMultisigProofSecret.ProofSecret);
- var validMultisigProofWitnessP2pk = JsonSerializer.Deserialize(validMultisigProof.Witness);
+ var validMultisigProofSecretp2pkValue = Assert.IsType(
+ validMultisigProofSecret.ProofSecret
+ );
+ var validMultisigProofWitnessP2pk = JsonSerializer.Deserialize(
+ validMultisigProof.Witness
+ );
Assert.True(
- validMultisigProofSecretp2pkValue.VerifyWitness(validMultisigProof.Secret, validMultisigProofWitnessP2pk));
+ validMultisigProofSecretp2pkValue.VerifyWitness(
+ validMultisigProof.Secret,
+ validMultisigProofWitnessP2pk
+ )
+ );
var invalidMultisig =
"{\"amount\":1,\"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\"C\":\"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\"id\":\"009a1f293253e41e\",\"witness\":\"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"}";
var invalidMultisigProof = JsonSerializer.Deserialize(invalidMultisig);
var invalidMultisigProofSecret = Assert.IsType(invalidMultisigProof.Secret);
Assert.Equal(P2PKProofSecret.Key, invalidMultisigProofSecret!.Key);
- var invalidMultisigProofSecretp2pkValue =
- Assert.IsType(invalidMultisigProofSecret.ProofSecret);
- var invalidMultisigProofWitnessP2pk = JsonSerializer.Deserialize(invalidMultisigProof.Witness);
- Assert.False(invalidMultisigProofSecretp2pkValue.VerifyWitness(invalidMultisigProof.Secret,
- invalidMultisigProofWitnessP2pk));
+ var invalidMultisigProofSecretp2pkValue = Assert.IsType(
+ invalidMultisigProofSecret.ProofSecret
+ );
+ var invalidMultisigProofWitnessP2pk = JsonSerializer.Deserialize(
+ invalidMultisigProof.Witness
+ );
+ Assert.False(
+ invalidMultisigProofSecretp2pkValue.VerifyWitness(
+ invalidMultisigProof.Secret,
+ invalidMultisigProofWitnessP2pk
+ )
+ );
var validProofRefund =
"{\n \"amount\": 64,\n \"C\": \"0257353051c02e2d650dede3159915c8be123ba4f47cf33183c7fedd20bd91a79b\",\n \"id\": \"001b6c716bf42c7e\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"4bc88ee09d1886c7461d45da205ca3274e1e3d9da2667c4865045cb18265a407\\\",\\\"data\\\":\\\"03d5edeb839be873df2348785506d36565f3b8f390fb931709a422b5a247ddefb1\\\",\\\"tags\\\":[[\\\"locktime\\\",\\\"21\\\"],[\\\"refund\\\",\\\"0234ad87e907e117db1590cc20a3942ffdfd5137aa563d36095d5cf5f96bada122\\\"]]}]\",\n \"witness\": \"{\\\"signatures\\\":[\\\"b316c2ff9c15f0c5c3d230e99ad94bc76a11dfccbdc820366a3db7210288f22ef6cedcded1152904ec31056d1d5176d83a2d96df5cd4ff86afdde1c90c63af5e\\\"]}\"\n}";
var validProofRefundParsed = JsonSerializer.Deserialize(validProofRefund);
var validProofRefundSecret = Assert.IsType(validProofRefundParsed.Secret);
Assert.Equal(P2PKProofSecret.Key, validProofRefundSecret!.Key);
- var validProofRefundSecretp2pkValue = Assert.IsType(validProofRefundSecret.ProofSecret);
- var validProofRefundWitnessP2pk = JsonSerializer.Deserialize(validProofRefundParsed.Witness);
+ var validProofRefundSecretp2pkValue = Assert.IsType(
+ validProofRefundSecret.ProofSecret
+ );
+ var validProofRefundWitnessP2pk = JsonSerializer.Deserialize(
+ validProofRefundParsed.Witness
+ );
Assert.True(
- validProofRefundSecretp2pkValue.VerifyWitness(validProofRefundParsed.Secret, validProofRefundWitnessP2pk));
-
+ validProofRefundSecretp2pkValue.VerifyWitness(
+ validProofRefundParsed.Secret,
+ validProofRefundWitnessP2pk
+ )
+ );
var invalidProofRefund =
"{\n \"amount\": 64,\n \"C\": \"0215865e3b30bdf6f5cdc1ee2c33379d5629bdf2eff2595603d939ff8c65d80586\",\n \"id\": \"001b6c716bf42c7e\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0c3d085898f1abf2b5521035f4d0f4ecf68c6a5109f6bc836833a1188f06be65\\\",\\\"data\\\":\\\"03206e0d488387a816bbafd957be51b073432c6c7a403ec4c2a0b27647326c5150\\\",\\\"tags\\\":[[\\\"locktime\\\",\\\"99999999999\\\"],[\\\"refund\\\",\\\"026acbcd0fff3a424499c83ec892d3155c9d1984438659f448d9d0f1af3e92276a\\\"]]}]\",\n \"witness\": \"{\\\"signatures\\\":[\\\"e5b10d7627ab39bd0cefa219c63752a0026aa5ae754b91a0c7ee2596222f87942c442aca2957166a6b468350c09c9968792784d2ae7c42fc91739b55689f4c7a\\\"]}\"\n}";
var invalidProofRefundParsed = JsonSerializer.Deserialize(invalidProofRefund);
var invalidProofRefundSecret = Assert.IsType(invalidProofRefundParsed.Secret);
Assert.Equal(P2PKProofSecret.Key, invalidProofRefundSecret!.Key);
- var invalidProofRefundSecretp2pkValue = Assert.IsType(invalidProofRefundSecret.ProofSecret);
- var invalidProofRefundWitnessP2pk = JsonSerializer.Deserialize(invalidProofRefundParsed.Witness);
- Assert.False(invalidProofRefundSecretp2pkValue.VerifyWitness(invalidProofRefundParsed.Secret,
- invalidProofRefundWitnessP2pk));
+ var invalidProofRefundSecretp2pkValue = Assert.IsType(
+ invalidProofRefundSecret.ProofSecret
+ );
+ var invalidProofRefundWitnessP2pk = JsonSerializer.Deserialize(
+ invalidProofRefundParsed.Witness
+ );
+ Assert.False(
+ invalidProofRefundSecretp2pkValue.VerifyWitness(
+ invalidProofRefundParsed.Secret,
+ invalidProofRefundWitnessP2pk
+ )
+ );
}
[Fact]
public void Nut11_New_P2PkRules()
{
- // since https://github.com/cashubtc/nuts/pull/315 p2pk and htlc behavior will be changed. After locktime, the
+ // since https://github.com/cashubtc/nuts/pull/315 p2pk and htlc behavior will be changed. After locktime, the
// proof will be spendable on both (refund and normal) paths.
-
- var spendableProof =
+
+ var spendableProof =
"{\n \"amount\": 64,\n \"C\": \"02d7cd858d866fca404b5cb1ffd813946e6d19efa1af00d654080fd20266bdc0b1\",\n \"id\": \"001b6c716bf42c7e\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"395162bf2d0add3c66aea9f22c45251dbee6e04bd9282addbb366a94cd4fb482\\\",\\\"data\\\":\\\"03ab50a667926fac858bac540766254c14b2b0334d10e8ec766455310224bbecf4\\\",\\\"tags\\\":[[\\\"locktime\\\",\\\"21\\\"],[\\\"pubkeys\\\",\\\"0229a91adec8dd9badb228c628a07fc1bf707a9b7d95dd505c490b1766fa7dc541\\\",\\\"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"refund\\\",\\\"03ab50a667926fac858bac540766254c14b2b0334d10e8ec766455310224bbecf4\\\",\\\"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\\\"]]}]\"\n}";
var spendableProofParsed = JsonSerializer.Deserialize(spendableProof);
Assert.NotNull(spendableProofParsed);
var spendableProofSecret = Assert.IsType(spendableProofParsed.Secret);
Assert.Equal(P2PKProofSecret.Key, spendableProofSecret.Key);
var secretValue = Assert.IsType(spendableProofSecret.ProofSecret);
-
+
// "standard path" witness, n_sigs = 2.
// since locktime is expired, it would fail under old conditions. now the proof should remain spendable
var validWitness1 =
@@ -398,91 +523,247 @@ public void Nut11_SIG_ALL()
var swapRequest =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\\\",\\\"data\\\":\\\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd\",\n \"witness\": \"{\\\"signatures\\\":[\\\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
var swapRequestParsed = JsonSerializer.Deserialize(swapRequest);
- var msgToSign = "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd2038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39";
- Assert.Equal(msgToSign, SigAllHandler.GetMessageToSign(swapRequestParsed.Inputs, swapRequestParsed.Outputs));
+ var msgToSign =
+ "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd2038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39";
+ Assert.Equal(
+ msgToSign,
+ SigAllHandler.GetMessageToSign(swapRequestParsed.Inputs, swapRequestParsed.Outputs)
+ );
var signedSwapRequest =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\\\",\\\"data\\\":\\\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd\",\n \"witness\": \"{\\\"signatures\\\":[\\\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
- var signedSwapRequestParsed = JsonSerializer.Deserialize(signedSwapRequest);
- Assert.True(SigAllHandler.VerifySigAllWitness(signedSwapRequestParsed.Inputs, signedSwapRequestParsed.Outputs));
- var witness = JsonSerializer.Deserialize(signedSwapRequestParsed.Inputs.First().Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(signedSwapRequestParsed.Inputs, signedSwapRequestParsed.Outputs, witness));
+ var signedSwapRequestParsed = JsonSerializer.Deserialize(
+ signedSwapRequest
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ signedSwapRequestParsed.Inputs,
+ signedSwapRequestParsed.Outputs
+ )
+ );
+ var witness = JsonSerializer.Deserialize(
+ signedSwapRequestParsed.Inputs.First().Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ signedSwapRequestParsed.Inputs,
+ signedSwapRequestParsed.Outputs,
+ witness
+ )
+ );
var validSwapRequest =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\\\",\\\"data\\\":\\\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd\",\n \"witness\": \"{\\\"signatures\\\":[\\\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
var validSwapRequestParsed = JsonSerializer.Deserialize(validSwapRequest);
- var witness1 = JsonSerializer.Deserialize(validSwapRequestParsed?.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestParsed.Inputs, validSwapRequestParsed.Outputs, witness1));
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestParsed.Inputs, validSwapRequestParsed.Outputs));
-
+ var witness1 = JsonSerializer.Deserialize(
+ validSwapRequestParsed?.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestParsed.Inputs,
+ validSwapRequestParsed.Outputs,
+ witness1
+ )
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestParsed.Inputs,
+ validSwapRequestParsed.Outputs
+ )
+ );
+
var invalidSwapRequest =
"{\n \"inputs\": [\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"fa6dd3fac9086c153878dec90b9e37163d38ff2ecf8b37db6470e9d185abbbae\\\",\\\"data\\\":\\\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"024d232312bab25af2e73f41d56864d378edca9109ae8f76e1030e02e585847786\",\n \"witness\": \"{\\\"signatures\\\":[\\\"27b4d260a1186e3b62a26c0d14ffeab3b9f7c3889e78707b8fd3836b473a00601afbd53a2288ad20a624a8bbe3344453215ea075fc0ce479dd8666fd3d9162cc\\\"]}\"\n },\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"4007b21fc5f5b1d4920bc0a08b158d98fd0fb2b0b0262b57ff53c6c5d6c2ae8c\\\",\\\"data\\\":\\\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\\\",\\\"tags\\\":[[\\\"locktime\\\",\\\"122222222222222\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02417400f2af09772219c831501afcbab4efb3b2e75175635d5474069608deb641\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n },\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5\"\n },\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"02c0d4fce02a7a0f09e3f1bca952db910b17e81a7ebcbce62cd8dcfb127d21e37b\"\n }\n ]\n}";
- var invalidSwapRequestParsed = JsonSerializer.Deserialize(invalidSwapRequest);
- Assert.False(SigAllHandler.VerifySigAllWitness(invalidSwapRequestParsed.Inputs, invalidSwapRequestParsed.Outputs));
- var witness2 = JsonSerializer.Deserialize(invalidSwapRequestParsed?.Inputs[0].Witness);
- Assert.False(SigAllHandler.VerifySigAllWitness(invalidSwapRequestParsed.Inputs, invalidSwapRequestParsed.Outputs, witness2));
-
- var validSwapRequestMultisig =
+ var invalidSwapRequestParsed = JsonSerializer.Deserialize(
+ invalidSwapRequest
+ );
+ Assert.False(
+ SigAllHandler.VerifySigAllWitness(
+ invalidSwapRequestParsed.Inputs,
+ invalidSwapRequestParsed.Outputs
+ )
+ );
+ var witness2 = JsonSerializer.Deserialize(
+ invalidSwapRequestParsed?.Inputs[0].Witness
+ );
+ Assert.False(
+ SigAllHandler.VerifySigAllWitness(
+ invalidSwapRequestParsed.Inputs,
+ invalidSwapRequestParsed.Outputs,
+ witness2
+ )
+ );
+
+ var validSwapRequestMultisig =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"04bfd885fc982d553711092d037fdceb7320fd8f96b0d4fd6d31a65b83b94272\\\",\\\"data\\\":\\\"0275e78025b558dbe6cb8fdd032a2e7613ca14fda5c1f4c4e3427f5077a7bd90e4\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"035163650bbd5ed4be7693f40f340346ba548b941074e9138b67ef6c42755f3449\\\",\\\"02817d22a8edc44c4141e192995a7976647c335092199f9e076a170c7336e2f5cc\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"03866a09946562482c576ca989d06371e412b221890804c7da8887d321380755be\",\n \"witness\": \"{\\\"signatures\\\":[\\\"be1d72c5ca16a93c5a34f25ec63ce632ddc3176787dac363321af3fd0f55d1927e07451bc451ffe5c682d76688ea9925d7977dffbb15bd79763b527f474734b0\\\",\\\"669d6d10d7ed35395009f222f6c7bdc28a378a1ebb72ee43117be5754648501da3bedf2fd6ff0c7849ac92683538c60af0af504102e40f2d8daca8e08b1ca16b\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
- var validSwapRequestMultisigParsed = JsonSerializer.Deserialize(validSwapRequestMultisig);
- var witness3 = JsonSerializer.Deserialize(validSwapRequestMultisigParsed.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestMultisigParsed.Inputs, validSwapRequestMultisigParsed.Outputs, witness3));
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestMultisigParsed.Inputs, validSwapRequestMultisigParsed.Outputs));
-
- var validSwapRequestMultisigRefundLocktime =
+ var validSwapRequestMultisigParsed = JsonSerializer.Deserialize(
+ validSwapRequestMultisig
+ );
+ var witness3 = JsonSerializer.Deserialize(
+ validSwapRequestMultisigParsed.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestMultisigParsed.Inputs,
+ validSwapRequestMultisigParsed.Outputs,
+ witness3
+ )
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestMultisigParsed.Inputs,
+ validSwapRequestMultisigParsed.Outputs
+ )
+ );
+
+
+ var validSwapRequestMultisigRefundLocktime =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"9ea35553beb18d553d0a53120d0175a0991ca6109370338406eed007b26eacd1\\\",\\\"data\\\":\\\"02af21e09300af92e7b48c48afdb12e22933738cfb9bba67b27c00c679aae3ec25\\\",\\\"tags\\\":[[\\\"locktime\\\",\\\"1\\\"],[\\\"refund\\\",\\\"02637c19143c58b2c58bd378400a7b82bdc91d6dedaeb803b28640ef7d28a887ac\\\",\\\"0345c7fdf7ec7c8e746cca264bf27509eb4edb9ac421f8fbfab1dec64945a4d797\\\"],[\\\"n_sigs_refund\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"03dd83536fbbcbb74ccb3c87147df26753fd499cc2c095f74367fff0fb459c312e\",\n \"witness\": \"{\\\"signatures\\\":[\\\"23b58ef28cd22f3dff421121240ddd621deee83a3bc229fd67019c2e338d91e2c61577e081e1375dbab369307bba265e887857110ca3b4bd949211a0a298805f\\\",\\\"7e75948ef1513564fdcecfcbd389deac67c730f7004f8631ba90c0844d3e8c0cf470b656306877df5141f65fd3b7e85445a8452c3323ab273e6d0d44843817ed\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
var validSwapRequestMultisigRefundLocktimeParsed =
JsonSerializer.Deserialize(validSwapRequestMultisigRefundLocktime);
- var witness5 = JsonSerializer.Deserialize(validSwapRequestMultisigRefundLocktimeParsed.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestMultisigRefundLocktimeParsed.Inputs, validSwapRequestMultisigRefundLocktimeParsed.Outputs, witness5));
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestMultisigRefundLocktimeParsed.Inputs, validSwapRequestMultisigRefundLocktimeParsed.Outputs));
-
- var validSwapRequestHTLC =
+ var witness5 = JsonSerializer.Deserialize(
+ validSwapRequestMultisigRefundLocktimeParsed.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestMultisigRefundLocktimeParsed.Inputs,
+ validSwapRequestMultisigRefundLocktimeParsed.Outputs,
+ witness5
+ )
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestMultisigRefundLocktimeParsed.Inputs,
+ validSwapRequestMultisigRefundLocktimeParsed.Outputs
+ )
+ );
+
+ var validSwapRequestHTLC =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"HTLC\\\",{\\\"nonce\\\":\\\"d730dd70cd7ec6e687829857de8e70aab2b970712f4dbe288343eca20e63c28c\\\",\\\"data\\\":\\\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0350cda8a1d5257dbd6ba8401a9a27384b9ab699e636e986101172167799469b14\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"03ff6567e2e6c31db5cb7189dab2b5121930086791c93899e4eff3dda61cb57273\",\n \"witness\": \"{\\\"preimage\\\":\\\"0000000000000000000000000000000000000000000000000000000000000001\\\",\\\"signatures\\\":[\\\"a4c00a9ad07f9936e404494fda99a9b935c82d7c053173b304b8663124c81d4b00f64a225f5acf41043ca52b06382722bd04ded0fbeb0fcc404eed3b24778b88\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
- var validSwapRequestHTLCParsed =
- JsonSerializer.Deserialize(validSwapRequestHTLC);
- var witness6 = JsonSerializer.Deserialize(validSwapRequestHTLCParsed.Inputs[0].Witness);
- var b = SigAllHandler.VerifySigAllWitness(validSwapRequestHTLCParsed.Inputs, validSwapRequestHTLCParsed.Outputs,
- witness6);
+ var validSwapRequestHTLCParsed = JsonSerializer.Deserialize(
+ validSwapRequestHTLC
+ );
+ var witness6 = JsonSerializer.Deserialize(
+ validSwapRequestHTLCParsed.Inputs[0].Witness
+ );
+ var b = SigAllHandler.VerifySigAllWitness(
+ validSwapRequestHTLCParsed.Inputs,
+ validSwapRequestHTLCParsed.Outputs,
+ witness6
+ );
Assert.True(b);
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestHTLCParsed.Inputs, validSwapRequestHTLCParsed.Outputs));
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestHTLCParsed.Inputs,
+ validSwapRequestHTLCParsed.Outputs
+ )
+ );
- var invalidSwapRequestHTLC =
+ var invalidSwapRequestHTLC =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"HTLC\\\",{\\\"nonce\\\":\\\"512c4045f12fdfd6f55059669c189e040c37c1ce2f8be104ed6aec296acce4e9\\\",\\\"data\\\":\\\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"03ba83defd31c63f8841d188f0d41b5bb3af1bb3c08d0ba46f8f1d26a4d45e8cad\\\"],[\\\"locktime\\\",\\\"4854185133\\\"],[\\\"refund\\\",\\\"032f1008a79c722e93a1b4b853f85f38283f9ef74ee4c5c91293eb1cc3c5e46e34\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02207abeff828146f1fc3909c74613d5605bd057f16791994b3c91f045b39a6939\",\n \"witness\": \"{\\\"preimage\\\":\\\"0000000000000000000000000000000000000000000000000000000000000001\\\",\\\"signatures\\\":[\\\"7816d57871bde5be2e4281065dbe5b15f641d8f1ed9437a3ae556464d6f9b8a0a2e6660337a915f2c26dce1453a416daf682b8fb593b67a0750fce071e0759b9\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n },\n {\n \"amount\": 1,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5\"\n }\n ]\n}";
- var invalidSwapRequestHTLCParsed = JsonSerializer.Deserialize(invalidSwapRequestHTLC);
- Assert.False(SigAllHandler.VerifySigAllWitness(invalidSwapRequestHTLCParsed.Inputs, invalidSwapRequestHTLCParsed.Outputs));
- var witness7 = JsonSerializer.Deserialize(invalidSwapRequestHTLCParsed.Inputs[0].Witness);
- Assert.False(SigAllHandler.VerifySigAllWitness(invalidSwapRequestHTLCParsed.Inputs, invalidSwapRequestHTLCParsed.Outputs, witness7));
+ var invalidSwapRequestHTLCParsed = JsonSerializer.Deserialize(
+ invalidSwapRequestHTLC
+ );
+ Assert.False(
+ SigAllHandler.VerifySigAllWitness(
+ invalidSwapRequestHTLCParsed.Inputs,
+ invalidSwapRequestHTLCParsed.Outputs
+ )
+ );
+ var witness7 = JsonSerializer.Deserialize(
+ invalidSwapRequestHTLCParsed.Inputs[0].Witness
+ );
+ Assert.False(
+ SigAllHandler.VerifySigAllWitness(
+ invalidSwapRequestHTLCParsed.Inputs,
+ invalidSwapRequestHTLCParsed.Outputs,
+ witness7
+ )
+ );
var validSwapRequestHTLCMultisig =
"{\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"HTLC\\\",{\\\"nonce\\\":\\\"c9b0fabb8007c0db4bef64d5d128cdcf3c79e8bb780c3294adf4c88e96c32647\\\",\\\"data\\\":\\\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"039e6ec7e922abb4162235b3a42965eb11510b07b7461f6b1a17478b1c9c64d100\\\"],[\\\"locktime\\\",\\\"1\\\"],[\\\"refund\\\",\\\"02ce1bbd2c9a4be8029c9a6435ad601c45677f5cde81f8a7f0ed535e0039d0eb6c\\\",\\\"03c43c00ff57f63cfa9e732f0520c342123e21331d0121139f1b636921eeec095f\\\"],[\\\"n_sigs_refund\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"0344b6f1471cf18a8cbae0e624018c816be5e3a9b04dcb7689f64173c1ae90a3a5\",\n \"witness\": \"{\\\"preimage\\\":\\\"0000000000000000000000000000000000000000000000000000000000000001\\\",\\\"signatures\\\":[\\\"98e21672d409cc782c720f203d8284f0af0c8713f18167499f9f101b7050c3e657fb0e57478ebd8bd561c31aa6c30f4cd20ec38c73f5755b7b4ddee693bca5a5\\\",\\\"693f40129dbf905ed9c8008081c694f72a36de354f9f4fa7a61b389cf781f62a0ae0586612fb2eb504faaf897fefb6742309186117f4743bcebcb8e350e975e2\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
- var validSwapRequestHTLCMultisigParsed = JsonSerializer.Deserialize(validSwapRequestHTLCMultisig);
- var witness8 = JsonSerializer.Deserialize(validSwapRequestHTLCMultisigParsed.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestHTLCMultisigParsed.Inputs, validSwapRequestHTLCMultisigParsed.Outputs, witness8));
- Assert.True(SigAllHandler.VerifySigAllWitness(validSwapRequestHTLCMultisigParsed.Inputs, validSwapRequestHTLCMultisigParsed.Outputs));
+ var validSwapRequestHTLCMultisigParsed = JsonSerializer.Deserialize(
+ validSwapRequestHTLCMultisig
+ );
+ var witness8 = JsonSerializer.Deserialize(
+ validSwapRequestHTLCMultisigParsed.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestHTLCMultisigParsed.Inputs,
+ validSwapRequestHTLCMultisigParsed.Outputs,
+ witness8
+ )
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ validSwapRequestHTLCMultisigParsed.Inputs,
+ validSwapRequestHTLCMultisigParsed.Outputs
+ )
+ );
- var meltRequest =
+ var meltRequest =
"{\n \"quote\": \"cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0\",\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\\\",\\\"data\\\":\\\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b\",\n \"witness\": \"{\\\"signatures\\\":[\\\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 0,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
var meltRequestParsed = JsonSerializer.Deserialize(meltRequest);
var msg2 =
"[\"P2PK\",{\"nonce\":\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\",\"data\":\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b0038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0";
- Assert.Equal(SigAllHandler.GetMessageToSign(meltRequestParsed.Inputs, meltRequestParsed.Outputs, meltRequestParsed.Quote), msg2);
+ Assert.Equal(
+ SigAllHandler.GetMessageToSign(
+ meltRequestParsed.Inputs,
+ meltRequestParsed.Outputs,
+ meltRequestParsed.Quote
+ ),
+ msg2
+ );
- var meltRequestValid =
+ var meltRequestValid =
"{\n \"quote\": \"cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0\",\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\\\",\\\"data\\\":\\\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b\",\n \"witness\": \"{\\\"signatures\\\":[\\\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 0,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
var meltRequestValidParsed = JsonSerializer.Deserialize(meltRequestValid);
- Assert.True(SigAllHandler.VerifySigAllWitness(meltRequestValidParsed.Inputs, meltRequestValidParsed.Outputs, meltRequestValidParsed.Quote));
- var witness9 = JsonSerializer.Deserialize(meltRequestValidParsed.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(meltRequestValidParsed.Inputs, meltRequestValidParsed.Outputs, witness9, meltRequestValidParsed.Quote));
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ meltRequestValidParsed.Inputs,
+ meltRequestValidParsed.Outputs,
+ meltRequestValidParsed.Quote
+ )
+ );
+ var witness9 = JsonSerializer.Deserialize(
+ meltRequestValidParsed.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ meltRequestValidParsed.Inputs,
+ meltRequestValidParsed.Outputs,
+ witness9,
+ meltRequestValidParsed.Quote
+ )
+ );
var meltRequestMultisig =
"{\n \"quote\": \"Db3qEMVwFN2tf_1JxbZp29aL5cVXpSMIwpYfyOVF\",\n \"inputs\": [\n {\n \"amount\": 2,\n \"id\": \"00bfa73302d12ffd\",\n \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"68d7822538740e4f9c9ebf5183ef6c4501c7a9bca4e509ce2e41e1d62e7b8a99\\\",\\\"data\\\":\\\"0394e841bd59aeadce16380df6174cb29c9fea83b0b65b226575e6d73cc5a1bd59\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"033d892d7ad2a7d53708b7a5a2af101cbcef69522bd368eacf55fcb4f1b0494058\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_ALL\\\"]]}]\",\n \"C\": \"03a70c42ec9d7192422c7f7a3ad017deda309fb4a2453fcf9357795ea706cc87a9\",\n \"witness\": \"{\\\"signatures\\\":[\\\"ed739970d003f703da2f101a51767b63858f4894468cc334be04aa3befab1617a81e3eef093441afb499974152d279e59d9582a31dc68adbc17ffc22a2516086\\\",\\\"f9efe1c70eb61e7ad8bd615c50ff850410a4135ea73ba5fd8e12a734743ad045e575e9e76ea5c52c8e7908d3ad5c0eaae93337e5c11109e52848dc328d6757a2\\\"]}\"\n }\n ],\n \"outputs\": [\n {\n \"amount\": 0,\n \"id\": \"00bfa73302d12ffd\",\n \"B_\": \"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39\"\n }\n ]\n}";
- var meltRequestMultisigParsed = JsonSerializer.Deserialize(meltRequestMultisig);
- Assert.True(SigAllHandler.VerifySigAllWitness(meltRequestMultisigParsed.Inputs, meltRequestMultisigParsed.Outputs, meltRequestMultisigParsed.Quote));
- var witness10 = JsonSerializer.Deserialize(meltRequestMultisigParsed.Inputs[0].Witness);
- Assert.True(SigAllHandler.VerifySigAllWitness(meltRequestMultisigParsed.Inputs, meltRequestMultisigParsed.Outputs, witness10, meltRequestMultisigParsed.Quote));
+ var meltRequestMultisigParsed = JsonSerializer.Deserialize(
+ meltRequestMultisig
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ meltRequestMultisigParsed.Inputs,
+ meltRequestMultisigParsed.Outputs,
+ meltRequestMultisigParsed.Quote
+ )
+ );
+ var witness10 = JsonSerializer.Deserialize(
+ meltRequestMultisigParsed.Inputs[0].Witness
+ );
+ Assert.True(
+ SigAllHandler.VerifySigAllWitness(
+ meltRequestMultisigParsed.Inputs,
+ meltRequestMultisigParsed.Outputs,
+ witness10,
+ meltRequestMultisigParsed.Quote
+ )
+ );
}
-
+
[Fact]
public void Nut12Tests_Hash_e()
{
@@ -500,7 +781,8 @@ public void Nut12Tests_BlindSignaturesDLEQ()
var A = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".ToPubKey();
var B_ = "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2".ToPubKey();
var blindSig = JsonSerializer.Deserialize(
- "{\n \"amount\": 8,\n \"id\": \"00882760bfa2eb41\",\n \"C_\": \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\",\n \"dleq\": {\n \"e\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9\",\n \"s\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da\"\n }\n}");
+ "{\n \"amount\": 8,\n \"id\": \"00882760bfa2eb41\",\n \"C_\": \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\",\n \"dleq\": {\n \"e\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9\",\n \"s\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da\"\n }\n}"
+ );
Assert.NotNull(blindSig?.DLEQ);
blindSig.Verify(A, B_);
@@ -511,10 +793,13 @@ public void Nut12Tests_ProofDLEQ()
{
var A = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".ToPubKey();
var proof = JsonSerializer.Deserialize(
- "{\"amount\": 1,\"id\": \"00882760bfa2eb41\",\"secret\": \"daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9\",\"C\": \"024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc\",\"dleq\": {\"e\": \"b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4\",\"s\": \"8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8\",\"r\": \"a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861\"}}");
+ "{\"amount\": 1,\"id\": \"00882760bfa2eb41\",\"secret\": \"daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9\",\"C\": \"024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc\",\"dleq\": {\"e\": \"b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4\",\"s\": \"8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8\",\"r\": \"a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861\"}}"
+ );
Assert.NotNull(proof?.DLEQ);
- Assert.Equal("024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc".ToPubKey(),
- proof.Secret.ToCurve());
+ Assert.Equal(
+ "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc".ToPubKey(),
+ proof.Secret.ToCurve()
+ );
Assert.True(proof.Verify(A));
}
@@ -540,46 +825,58 @@ public void Nut18Tests()
var creqA =
"creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=";
var pr = PaymentRequest.Parse(creqA);
- Assert.Equal("https://nofees.testnut.cashu.space", Assert.Single( pr.Mints));
+ Assert.Equal("https://nofees.testnut.cashu.space", Assert.Single(pr.Mints));
Assert.Equal((ulong)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[0] );
+ Assert.Equal(
+ "nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5",
+ t.Target
+ );
+ Assert.Equal("n", Assert.Single(t.Tags).Key);
+ Assert.Equal("17", Assert.Single(t.Tags).Value[0]);
// Assert.Equal(creqA, pr.ToString());
-
}
-
+
internal readonly struct TestCase(in string path, in string keyHex, in string ccHex)
{
internal static readonly ReadOnlyMemory Seed = Convert.FromHexString(
- "e4a964f4973ce5750a6a5a5126e8258442c197b2e71b683ccba58688f21242eae1b0f12bee21d6e983d4a5c61f081bf3f0669546eb576dec1b22ec8d481b00fb");
+ "e4a964f4973ce5750a6a5a5126e8258442c197b2e71b683ccba58688f21242eae1b0f12bee21d6e983d4a5c61f081bf3f0669546eb576dec1b22ec8d481b00fb"
+ );
internal readonly ReadOnlyMemory Key = Convert.FromHexString(keyHex);
internal readonly ReadOnlyMemory ChainCode = Convert.FromHexString(ccHex);
internal readonly KeyPath Path = path;
}
-
+
private static readonly TestCase Case1SecP256K1 = new(
"m/0'/0/0",
"6144c1daf8222d6dab77e7a20c2f338519b83bd1423602c56c7dfb5e9ea99c02",
- "55b36970e7ab8434f9b04f1c2e52da7422d2bce7e284ca353419dddfa2e34bdb");
+ "55b36970e7ab8434f9b04f1c2e52da7422d2bce7e284ca353419dddfa2e34bdb"
+ );
[Fact]
public void Bip32Test()
{
var masterKeyFromSeed = BIP32.Instance.GetMasterKeyFromSeed(TestCase.Seed.Span);
-
- Assert.Equal("5A876CC4B4AB2F6717951AEE7F97AB69844DBFFFF7074E6E6F71D2BA04BD6EC9", Convert.ToHexString( masterKeyFromSeed.ChainCode));
- Assert.Equal("8D18D3F0CF9D74B53A935D97E8DE85955ED9F6EEFC6D6D45F0C169031A11B669", Convert.ToHexString( masterKeyFromSeed.PrivateKey));
-
-
- Assert.Equal("026cf0d14fcfa930347e7da26281319ac5959d02f1b6331812261efdb7e347788b",ECPrivKey.Create(masterKeyFromSeed.PrivateKey).CreatePubKey().ToHex());
-
+
+ Assert.Equal(
+ "5A876CC4B4AB2F6717951AEE7F97AB69844DBFFFF7074E6E6F71D2BA04BD6EC9",
+ Convert.ToHexString(masterKeyFromSeed.ChainCode)
+ );
+ Assert.Equal(
+ "8D18D3F0CF9D74B53A935D97E8DE85955ED9F6EEFC6D6D45F0C169031A11B669",
+ Convert.ToHexString(masterKeyFromSeed.PrivateKey)
+ );
+
+ Assert.Equal(
+ "026cf0d14fcfa930347e7da26281319ac5959d02f1b6331812261efdb7e347788b",
+ ECPrivKey.Create(masterKeyFromSeed.PrivateKey).CreatePubKey().ToHex()
+ );
+
var der1 = BIP32.Instance.DerivePath(Case1SecP256K1.Path, TestCase.Seed.Span);
Assert.True(der1.PrivateKey.SequenceEqual(Case1SecP256K1.Key.Span));
Assert.True(der1.ChainCode.SequenceEqual(Case1SecP256K1.ChainCode.Span));
@@ -592,57 +889,119 @@ public void Nut13Tests()
Assert.Equal(864559728, Nut13.GetKeysetIdInt(keysetId));
var path = "m/129372'/0'/864559728'/{counter}'";
- var mnemonicPhrase = "half depart obvious quality work element tank gorilla view sugar picture humble";
+ var mnemonicPhrase =
+ "half depart obvious quality work element tank gorilla view sugar picture humble";
var mnemonic = new Mnemonic(mnemonicPhrase);
- Assert.Equal("dd44ee516b0647e80b488e8dcc56d736a148f15276bef588b37057476d4b2b25780d3688a32b37353d6995997842c0fd8b412475c891c16310471fbc86dcbda8",
- Convert.ToHexString(mnemonic.DeriveSeed()).ToLowerInvariant());
-
- Assert.Equal("m/129372'/0'/864559728'/0'/0", Nut13.GetNut13DerivationPath(keysetId, 0, true));
- Assert.Equal("m/129372'/0'/864559728'/0'/1", Nut13.GetNut13DerivationPath(keysetId, 0, false));
-
- Assert.Equal("485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
- mnemonic.DeriveSecret(keysetId, 0).Secret);
- Assert.Equal("8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
- mnemonic.DeriveSecret(keysetId, 1).Secret);
- Assert.Equal("bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
- mnemonic.DeriveSecret(keysetId, 2).Secret);
- Assert.Equal("59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
- mnemonic.DeriveSecret(keysetId, 3).Secret);
- Assert.Equal("576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
- mnemonic.DeriveSecret(keysetId, 4).Secret);
-
- Assert.Equal("ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
- Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 0)).ToLowerInvariant());
- Assert.Equal("967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
- Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 1)).ToLowerInvariant());
- Assert.Equal("b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
- Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 2)).ToLowerInvariant());
- Assert.Equal("fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
- Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 3)).ToLowerInvariant());
- Assert.Equal("5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
- Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 4)).ToLowerInvariant());
+ Assert.Equal(
+ "dd44ee516b0647e80b488e8dcc56d736a148f15276bef588b37057476d4b2b25780d3688a32b37353d6995997842c0fd8b412475c891c16310471fbc86dcbda8",
+ Convert.ToHexString(mnemonic.DeriveSeed()).ToLowerInvariant()
+ );
+
+ Assert.Equal(
+ "m/129372'/0'/864559728'/0'/0",
+ Nut13.GetNut13DerivationPath(keysetId, 0, true)
+ );
+ Assert.Equal(
+ "m/129372'/0'/864559728'/0'/1",
+ Nut13.GetNut13DerivationPath(keysetId, 0, false)
+ );
+
+ Assert.Equal(
+ "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
+ mnemonic.DeriveSecret(keysetId, 0).Secret
+ );
+ Assert.Equal(
+ "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
+ mnemonic.DeriveSecret(keysetId, 1).Secret
+ );
+ Assert.Equal(
+ "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
+ mnemonic.DeriveSecret(keysetId, 2).Secret
+ );
+ Assert.Equal(
+ "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
+ mnemonic.DeriveSecret(keysetId, 3).Secret
+ );
+ Assert.Equal(
+ "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
+ mnemonic.DeriveSecret(keysetId, 4).Secret
+ );
+
+ Assert.Equal(
+ "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 0)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 1)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 2)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 3)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 4)).ToLowerInvariant()
+ );
}
[Fact]
public void Nut13HMACTests()
{
- KeysetId keysetId = new KeysetId("015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a");
- Mnemonic mnemonic =
- new Mnemonic("half depart obvious quality work element tank gorilla view sugar picture humble");
-
- Assert.Equal("db5561a07a6e6490f8dadeef5be4e92f7cebaecf2f245356b5b2a4ec40687298", mnemonic.DeriveSecret(keysetId, 0).Secret);
- Assert.Equal("b70e7b10683da3bf1cdf0411206f8180c463faa16014663f39f2529b2fda922e", mnemonic.DeriveSecret(keysetId, 1).Secret);
- Assert.Equal("78a7ac32ccecc6b83311c6081b89d84bb4128f5a0d0c5e1af081f301c7a513f5", mnemonic.DeriveSecret(keysetId, 2).Secret);
- Assert.Equal("094a2b6c63bfa7970bc09cda0e1cfc9cd3d7c619b8e98fabcfc60aea9e4963e5", mnemonic.DeriveSecret(keysetId, 3).Secret);
- Assert.Equal("5e89fc5d30d0bf307ddf0a3ac34aa7a8ee3702169dafa3d3fe1d0cae70ecd5ef", mnemonic.DeriveSecret(keysetId, 4).Secret);
-
-
- Assert.Equal("6d26181a3695e32e9f88b80f039ba1ae2ab5a200ad4ce9dbc72c6d3769f2b035", Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 0)).ToLowerInvariant());
- Assert.Equal("bde4354cee75545bea1a2eee035a34f2d524cee2bb01613823636e998386952e", Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 1)).ToLowerInvariant());
- Assert.Equal("f40cc1218f085b395c8e1e5aaa25dccc851be3c6c7526a0f4e57108f12d6dac4", Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 2)).ToLowerInvariant());
- Assert.Equal("099ed70fc2f7ac769bc20b2a75cb662e80779827b7cc358981318643030577d0", Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 3)).ToLowerInvariant());
- Assert.Equal("5550337312d223ba62e3f75cfe2ab70477b046d98e3e71804eade3956c7b98cf", Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 4)).ToLowerInvariant());
+ KeysetId keysetId = new KeysetId(
+ "015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a"
+ );
+ Mnemonic mnemonic = new Mnemonic(
+ "half depart obvious quality work element tank gorilla view sugar picture humble"
+ );
+
+ Assert.Equal(
+ "db5561a07a6e6490f8dadeef5be4e92f7cebaecf2f245356b5b2a4ec40687298",
+ mnemonic.DeriveSecret(keysetId, 0).Secret
+ );
+ Assert.Equal(
+ "b70e7b10683da3bf1cdf0411206f8180c463faa16014663f39f2529b2fda922e",
+ mnemonic.DeriveSecret(keysetId, 1).Secret
+ );
+ Assert.Equal(
+ "78a7ac32ccecc6b83311c6081b89d84bb4128f5a0d0c5e1af081f301c7a513f5",
+ mnemonic.DeriveSecret(keysetId, 2).Secret
+ );
+ Assert.Equal(
+ "094a2b6c63bfa7970bc09cda0e1cfc9cd3d7c619b8e98fabcfc60aea9e4963e5",
+ mnemonic.DeriveSecret(keysetId, 3).Secret
+ );
+ Assert.Equal(
+ "5e89fc5d30d0bf307ddf0a3ac34aa7a8ee3702169dafa3d3fe1d0cae70ecd5ef",
+ mnemonic.DeriveSecret(keysetId, 4).Secret
+ );
+
+ Assert.Equal(
+ "6d26181a3695e32e9f88b80f039ba1ae2ab5a200ad4ce9dbc72c6d3769f2b035",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 0)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "bde4354cee75545bea1a2eee035a34f2d524cee2bb01613823636e998386952e",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 1)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "f40cc1218f085b395c8e1e5aaa25dccc851be3c6c7526a0f4e57108f12d6dac4",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 2)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "099ed70fc2f7ac769bc20b2a75cb662e80779827b7cc358981318643030577d0",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 3)).ToLowerInvariant()
+ );
+ Assert.Equal(
+ "5550337312d223ba62e3f75cfe2ab70477b046d98e3e71804eade3956c7b98cf",
+ Convert.ToHexString(mnemonic.DeriveBlindingFactor(keysetId, 4)).ToLowerInvariant()
+ );
}
+
[Fact]
public void NullExpiryTests_PostMintQuoteBolt11Response()
{
@@ -659,7 +1018,7 @@ public void NullExpiryTests_PostMintQuoteBolt11Response()
""";
var response = JsonSerializer.Deserialize(jsonWithNullExpiry);
-
+
Assert.NotNull(response);
Assert.Equal("test-quote-id", response.Quote);
Assert.Equal("test-request", response.Request);
@@ -680,7 +1039,7 @@ public void NullExpiryTests_PostMintQuoteBolt11Response()
""";
var response2 = JsonSerializer.Deserialize(jsonWithoutExpiry);
-
+
Assert.NotNull(response2);
Assert.Equal("test-quote-id-2", response2.Quote);
Assert.Equal("test-request-2", response2.Request);
@@ -702,7 +1061,7 @@ public void NullExpiryTests_PostMintQuoteBolt11Response()
""";
var response3 = JsonSerializer.Deserialize(jsonWithExpiry);
-
+
Assert.NotNull(response3);
Assert.Equal("test-quote-id-3", response3.Quote);
Assert.Equal("test-request-3", response3.Request);
@@ -728,7 +1087,7 @@ public void NullExpiryTests_PostMeltQuoteBolt11Response()
""";
var response = JsonSerializer.Deserialize(jsonWithNullExpiry);
-
+
Assert.NotNull(response);
Assert.Equal("melt-quote-id", response.Quote);
Assert.Equal((ulong)1000, response.Amount);
@@ -748,7 +1107,7 @@ public void NullExpiryTests_PostMeltQuoteBolt11Response()
""";
var response2 = JsonSerializer.Deserialize(jsonWithoutExpiry);
-
+
Assert.NotNull(response2);
Assert.Equal("melt-quote-id-2", response2.Quote);
Assert.Equal((ulong)500, response2.Amount);
@@ -769,7 +1128,7 @@ public void NullExpiryTests_PostMeltQuoteBolt11Response()
""";
var response3 = JsonSerializer.Deserialize(jsonWithExpiry);
-
+
Assert.NotNull(response3);
Assert.Equal("melt-quote-id-3", response3.Quote);
Assert.Equal((ulong)2000, response3.Amount);
@@ -778,7 +1137,6 @@ public void NullExpiryTests_PostMeltQuoteBolt11Response()
Assert.Equal(1640995200, response3.Expiry);
Assert.Null(response3.PaymentPreimage);
}
- private static readonly byte[] P2BK_PREFIX = "Cashu_P2BK_v1"u8.ToArray();
[Fact]
public void Nut28_P2BK_Tests()
@@ -786,15 +1144,16 @@ public void Nut28_P2BK_Tests()
// sender ephemeral keypair
var e = new PrivKey("1cedb9df0c6872188b560ace9e35fd55c2532d53e19ae65b46159073886482ca");
var E = new PubKey("02a8cda4cf448bfce9a9e46e588c06ea1780fcb94e3bbdf3277f42995d403a8b0c");
- Assert.Equal(E.Key.ToString()?.ToLowerInvariant(), e.Key.CreatePubKey().ToString()?.ToLowerInvariant());
+ Assert.Equal(
+ E.Key.ToString()?.ToLowerInvariant(),
+ e.Key.CreatePubKey().ToString()?.ToLowerInvariant()
+ );
// receiver keypair
var p = new PrivKey("ad37e8abd800be3e8272b14045873f4353327eedeb702b72ddcc5c5adff5129c");
var P = new PubKey("02771fed6cb88aaac38b8b32104a942bf4b8f4696bc361171b3c7d06fa2ebddf06");
Assert.Equal(P.Key.ToString()?.ToLowerInvariant(), p.Key.CreatePubKey().ToString()?.ToLowerInvariant());
-
- // var kid = new KeysetId("009a1f293253e41e");
var zx = "40d6ba4430a6dfa915bb441579b0f4dee032307434e9957a092bbca73151df8b";
Assert.Equal(zx, Convert.ToHexString(Cashu.ComputeZx(e, P)).ToLowerInvariant());
@@ -821,7 +1180,6 @@ public void Nut28_P2BK_Tests()
Assert.Equal(rs[i], ri.ToString());
}
-
string[] blindedPublicKeys =
[
"03b7c03eb05a0a539cfc438e81bcf38b65b7bb8685e8790f9b853bfe3d77ad5315",
@@ -839,7 +1197,10 @@ public void Nut28_P2BK_Tests()
//it's the same blinding as with computeB_
for (int i = 0; i <= 10; i++)
{
- Assert.Equal(blindedPublicKeys[i], ((PubKey)Cashu.ComputeB_(P, new PrivKey(rs[i]))).ToString());
+ Assert.Equal(
+ blindedPublicKeys[i],
+ ((PubKey)Cashu.ComputeB_(P, new PrivKey(rs[i]))).ToString()
+ );
}
string[] skStd =
@@ -887,7 +1248,6 @@ public void Nut28_P2BK_Tests()
Assert.Equal(skNeg[i], Convert.ToHexString(derivedKeyNeg.ToBytes()).ToLowerInvariant());
}
-
}
[Fact]
@@ -896,26 +1256,36 @@ public void Nut28_P2BK_Flow()
// sender generates ephermal keypair
var e = new PrivKey("1cedb9df0c6872188b560ace9e35fd55c2532d53e19ae65b46159073886482ca");
var E = new PubKey("02a8cda4cf448bfce9a9e46e588c06ea1780fcb94e3bbdf3277f42995d403a8b0c");
-
-
+
// receiver privkeys, with corresponding pubkeys that will get blinded
- var signing_key =
- ECPrivKey.Create(Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"));
- var signing_key_two =
- ECPrivKey.Create(Convert.FromHexString("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"));
-
- var refundPubkey =
- ECPrivKey.Create(Convert.FromHexString("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")).CreatePubKey();
+ var signing_key = ECPrivKey.Create(
+ Convert.FromHexString(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ )
+ );
+ var signing_key_two = ECPrivKey.Create(
+ Convert.FromHexString(
+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
+ )
+ );
+
+ var refundPubkey = ECPrivKey
+ .Create(
+ Convert.FromHexString(
+ "99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37"
+ )
+ )
+ .CreatePubKey();
var keysetId = new KeysetId("009a1f293253e41e");
-
- var conditions = new P2PKBuilder()
+
+ var conditions = new P2PkBuilder()
{
Lock = DateTimeOffset.FromUnixTimeSeconds(21000000000),
- Pubkeys = new[] {signing_key.CreatePubKey(), signing_key_two.CreatePubKey()},
- RefundPubkeys = new[] {refundPubkey},
+ Pubkeys = new[] { signing_key.CreatePubKey(), signing_key_two.CreatePubKey() },
+ RefundPubkeys = new[] { refundPubkey },
SignatureThreshold = 2,
- SigFlag = "SIG_INPUTS"
+ SigFlag = "SIG_INPUTS",
};
var p2pkProofSecret = conditions.BuildBlinded(e);
@@ -927,33 +1297,48 @@ public void Nut28_P2BK_Flow()
Amount = 0,
Secret = secret,
C = "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904".ToPubKey(),
- P2PkE = E
+ P2PkE = E,
};
- var witness = p2pkProofSecret.GenerateBlindWitness(proof, new[] {signing_key, signing_key_two}, keysetId, E);
-
+ var witness = p2pkProofSecret.GenerateBlindWitness(
+ proof,
+ new[] { signing_key, signing_key_two },
+ E
+ );
+
Assert.True(p2pkProofSecret.VerifyWitness(secret, witness));
}
[Fact]
public void Nut28_Flow_WithRandomE()
{
- var signing_key =
- ECPrivKey.Create(Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"));
- var signing_key_two =
- ECPrivKey.Create(Convert.FromHexString("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"));
-
- var refundPubkey =
- ECPrivKey.Create(Convert.FromHexString("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")).CreatePubKey();
+ var signing_key = ECPrivKey.Create(
+ Convert.FromHexString(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ )
+ );
+ var signing_key_two = ECPrivKey.Create(
+ Convert.FromHexString(
+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
+ )
+ );
+
+ var refundPubkey = ECPrivKey
+ .Create(
+ Convert.FromHexString(
+ "99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37"
+ )
+ )
+ .CreatePubKey();
var keysetId = new KeysetId("009a1f293253e41e");
-
- var conditions = new P2PKBuilder()
+
+ var conditions = new P2PkBuilder()
{
Lock = DateTimeOffset.FromUnixTimeSeconds(21000000000),
- Pubkeys = new[] {signing_key.CreatePubKey(), signing_key_two.CreatePubKey()},
- RefundPubkeys = new[] {refundPubkey},
+ Pubkeys = new[] { signing_key.CreatePubKey(), signing_key_two.CreatePubKey() },
+ RefundPubkeys = new[] { refundPubkey },
SignatureThreshold = 2,
- SigFlag = "SIG_INPUTS"
+ SigFlag = "SIG_INPUTS",
};
var p2pkProofSecret = conditions.BuildBlinded(out var E);
@@ -965,10 +1350,14 @@ public void Nut28_Flow_WithRandomE()
Amount = 0,
Secret = secret,
C = "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904".ToPubKey(),
- P2PkE = E
+ P2PkE = E,
};
- var witness = p2pkProofSecret.GenerateBlindWitness(proof, new[] {signing_key, signing_key_two}, keysetId, E);
-
+ var witness = p2pkProofSecret.GenerateBlindWitness(
+ proof,
+ new[] { signing_key, signing_key_two },
+ E
+ );
+
Assert.True(p2pkProofSecret.VerifyWitness(secret, witness));
}
diff --git a/DotNut.Tests/UnitTests2.cs b/DotNut.Tests/UnitTests2.cs
new file mode 100644
index 0000000..e374aa8
--- /dev/null
+++ b/DotNut.Tests/UnitTests2.cs
@@ -0,0 +1,690 @@
+using System.Text.Json;
+using DotNut.Abstractions;
+using DotNut.ApiModels;
+using DotNut.NBitcoin.BIP39;
+using NBitcoin.Secp256k1;
+
+namespace DotNut.Tests;
+
+public class UnitTests2
+{
+ private static string MintUrl = "http://localhost:3338";
+
+ [Fact]
+ public void CreatesWalletSuccesfully()
+ {
+ var wallet = Wallet.Create();
+ Assert.NotNull(wallet);
+ }
+
+ [Fact]
+ public async Task ThrowsWhenMintNotFound()
+ {
+ var wallet = Wallet.Create();
+ await Assert.ThrowsAsync(async () => await wallet.GetInfo());
+ await Assert.ThrowsAsync(async () => wallet.Restore());
+ await Assert.ThrowsAsync(async () => wallet.Swap());
+ await Assert.ThrowsAsync(async () => wallet.CreateMeltQuote());
+ await Assert.ThrowsAsync(async () => wallet.CreateMintQuote());
+ }
+
+ [Fact]
+ public void BuilderChainingPreservesAllSettings()
+ {
+ var counter = new InMemoryCounter();
+ var info = new MintInfo(new GetInfoResponse { Version = "0.15.0" });
+ var keysets = new GetKeysetsResponse { Keysets = [] };
+ var keys = new GetKeysResponse { Keysets = [] };
+ var selector = new ProofSelector(new Dictionary());
+ var mnemonic =
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
+
+ var wallet = Wallet
+ .Create()
+ .WithMint(MintUrl)
+ .WithInfo(info)
+ .WithKeysets(keysets)
+ .WithKeys(keys)
+ .WithSelector(selector)
+ .WithMnemonic(mnemonic)
+ .WithCounter(counter)
+ .WithKeysetSync(true)
+ .ShouldBumpCounter(false);
+
+ var mnemonicField = wallet
+ .GetType()
+ .GetField(
+ "_mnemonic",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
+ );
+ var mnemonicRef = (Mnemonic?)mnemonicField?.GetValue(wallet);
+
+ var counterField = wallet
+ .GetType()
+ .GetField(
+ "_counter",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
+ );
+ var counterRef = (InMemoryCounter?)counterField?.GetValue(wallet);
+
+ Assert.Equal(mnemonic, mnemonicRef.ToString());
+ Assert.Same(counter, counterRef);
+ }
+
+ [Fact]
+ public void WithMintStringVariantCreatesHttpClient()
+ {
+ var wallet = Wallet.Create().WithMint(MintUrl);
+ var api = wallet.GetMintApi().Result;
+ Assert.NotNull(api);
+ }
+
+ [Fact]
+ public async Task InMemoryCounter()
+ {
+ var ctr = new InMemoryCounter();
+ Assert.NotNull(ctr);
+ var testId1 = new KeysetId("00qwertyuiopasdf");
+ var ctrNum = await ctr.GetCounterForId(testId1);
+ Assert.Equal((uint)0, ctrNum);
+
+ await ctr.IncrementCounter(testId1);
+ Assert.Equal((uint)0, ctrNum);
+ ctrNum = await ctr.GetCounterForId(testId1);
+ Assert.Equal((uint)1, ctrNum);
+
+ await ctr.IncrementCounter(testId1, 5);
+ ctrNum = await ctr.GetCounterForId(testId1);
+ Assert.Equal((uint)6, ctrNum);
+
+ await ctr.SetCounter(testId1, 1337);
+ ctrNum = await ctr.GetCounterForId(testId1);
+ Assert.Equal((uint)1337, ctrNum);
+ }
+
+ [Fact]
+ public void SplitAmountsForPayment_ExactAmount_ReturnsCorrectSplit()
+ {
+ var amounts = Utils.SplitToProofsAmounts(30, _testKeyset);
+ Assert.Equal(new List() { 16, 8, 4, 2 }, amounts);
+ }
+
+ private Keyset? _testKeyset = JsonSerializer.Deserialize(
+ "{\n \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}"
+ );
+
+ private static KeysetId _testKeysetId = new KeysetId("000f01df73ea149a");
+
+ [Fact]
+ public void SumProofs_EmptyList_ReturnsZero()
+ {
+ var proofs = new List();
+ var sum = Utils.SumProofs(proofs);
+ Assert.Equal(0UL, sum);
+ }
+
+ [Fact]
+ public void SumProofs_SingleProof_ReturnsAmount()
+ {
+ var proofs = new List { new Proof { Amount = 64 } };
+ var sum = Utils.SumProofs(proofs);
+ Assert.Equal(64UL, sum);
+ }
+
+ [Fact]
+ public void SumProofs_MultipleProofs_ReturnsCorrectSum()
+ {
+ var proofs = new List
+ {
+ new Proof { Amount = 1 },
+ new Proof { Amount = 2 },
+ new Proof { Amount = 4 },
+ new Proof { Amount = 8 },
+ new Proof { Amount = 16 },
+ };
+ var sum = Utils.SumProofs(proofs);
+ Assert.Equal(31UL, sum);
+ }
+
+ [Theory]
+ [InlineData(1UL, new ulong[] { 1 })]
+ [InlineData(2UL, new ulong[] { 2 })]
+ [InlineData(3UL, new ulong[] { 2, 1 })]
+ [InlineData(7UL, new ulong[] { 4, 2, 1 })]
+ [InlineData(15UL, new ulong[] { 8, 4, 2, 1 })]
+ [InlineData(63UL, new ulong[] { 32, 16, 8, 4, 2, 1 })]
+ [InlineData(64UL, new ulong[] { 64 })]
+ [InlineData(100UL, new ulong[] { 64, 32, 4 })]
+ [InlineData(1337UL, new ulong[] { 1024, 256, 32, 16, 8, 1 })]
+ public void SplitToProofsAmounts_VariousAmounts_ReturnsCorrectSplit(
+ ulong amount,
+ ulong[] expected
+ )
+ {
+ var result = Utils.SplitToProofsAmounts(amount, _testKeyset!);
+ Assert.Equal(expected.ToList(), result);
+ }
+
+ [Fact]
+ public void SplitToProofsAmounts_ZeroAmount_ReturnsEmptyList()
+ {
+ var result = Utils.SplitToProofsAmounts(0, _testKeyset!);
+ Assert.Empty(result);
+ }
+
+ [Theory]
+ [InlineData(0UL, 0)]
+ [InlineData(1UL, 1)]
+ [InlineData(2UL, 1)]
+ [InlineData(3UL, 2)]
+ [InlineData(4UL, 2)]
+ [InlineData(7UL, 3)]
+ [InlineData(8UL, 3)]
+ [InlineData(15UL, 4)]
+ [InlineData(16UL, 4)]
+ [InlineData(100UL, 7)]
+ [InlineData(1000UL, 10)]
+ public void CalculateNumberOfBlankOutputs_VariousAmounts(ulong amount, int expected)
+ {
+ var result = Utils.CalculateNumberOfBlankOutputs(amount);
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void CreateOutputs_ValidAmounts_ReturnsCorrectOutputData()
+ {
+ var amounts = new List { 1, 2, 4 };
+ var outputs = Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!);
+
+ Assert.Equal(3, outputs.Count);
+
+ Assert.Equal(1UL, outputs[0].BlindedMessage.Amount);
+ Assert.Equal(2UL, outputs[1].BlindedMessage.Amount);
+ Assert.Equal(4UL, outputs[2].BlindedMessage.Amount);
+
+ Assert.All(outputs, o => Assert.Equal(_testKeysetId, o.BlindedMessage.Id));
+ }
+
+ [Fact]
+ public void CreateOutputs_InvalidAmount_ThrowsException()
+ {
+ var amounts = new List { 1, 3 }; // 3 is not a valid amount
+ Assert.Throws(() =>
+ Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!)
+ );
+ }
+
+ [Fact]
+ public void CreateOutputs_DeterministicWithMnemonic()
+ {
+ var mnemonic = new Mnemonic(
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
+ );
+ var amounts = new List { 1, 2, 4 };
+
+ var outputs1 = Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!, mnemonic, 0);
+ var outputs2 = Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!, mnemonic, 0);
+
+ // Same mnemonic and counter should produce same outputs
+ for (int i = 0; i < outputs1.Count; i++)
+ {
+ Assert.Equal(
+ ((StringSecret)outputs1[i].Secret).Secret,
+ ((StringSecret)outputs2[i].Secret).Secret
+ );
+ }
+ }
+
+ [Fact]
+ public void CreateOutputs_RandomWithoutMnemonic()
+ {
+ var amounts = new List { 1 };
+
+ var outputs1 = Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!);
+ var outputs2 = Utils.CreateOutputs(amounts, _testKeysetId, _testKeyset!);
+
+ // without mnemonic, outputs should be random (different)
+ Assert.NotEqual(
+ ((StringSecret)outputs1[0].Secret).Secret,
+ ((StringSecret)outputs2[0].Secret).Secret
+ );
+ }
+
+ private static PubKey CreateTestPubKey(int seed)
+ {
+ var seedBytes = new byte[32];
+ BitConverter.GetBytes(seed).CopyTo(seedBytes, 0);
+ seedBytes[31] = 1;
+ var privKey = ECPrivKey.Create(seedBytes);
+ ECPubKey ecPubKey = privKey.CreatePubKey();
+ return ecPubKey;
+ }
+
+ [Fact]
+ public async Task ProofSelector_ExactMatch_SelectsCorrectProofs()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 0 } };
+ var selector = new ProofSelector(fees);
+
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 1,
+ Id = keysetId,
+ C = CreateTestPubKey(1),
+ },
+ new Proof
+ {
+ Amount = 2,
+ Id = keysetId,
+ C = CreateTestPubKey(2),
+ },
+ new Proof
+ {
+ Amount = 4,
+ Id = keysetId,
+ C = CreateTestPubKey(3),
+ },
+ new Proof
+ {
+ Amount = 8,
+ Id = keysetId,
+ C = CreateTestPubKey(4),
+ },
+ };
+
+ var result = await selector.SelectProofsToSend(proofs, 7, false);
+
+ Assert.Equal(7UL, Utils.SumProofs(result.Send));
+ Assert.Equal(8UL, Utils.SumProofs(result.Keep));
+ }
+
+ [Fact]
+ public async Task ProofSelector_InsufficientFunds_ReturnsEmptySend()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 0 } };
+ var selector = new ProofSelector(fees);
+
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 1,
+ Id = keysetId,
+ C = CreateTestPubKey(1),
+ },
+ new Proof
+ {
+ Amount = 2,
+ Id = keysetId,
+ C = CreateTestPubKey(2),
+ },
+ };
+
+ var result = await selector.SelectProofsToSend(proofs, 100, false);
+
+ Assert.Empty(result.Send);
+ Assert.Equal(2, result.Keep.Count);
+ }
+
+ [Fact]
+ public async Task ProofSelector_ZeroAmount_ReturnsEmptySend()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 0 } };
+ var selector = new ProofSelector(fees);
+
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 8,
+ Id = keysetId,
+ C = CreateTestPubKey(1),
+ },
+ };
+
+ var result = await selector.SelectProofsToSend(proofs, 0, false);
+
+ Assert.Empty(result.Send);
+ }
+
+ [Fact]
+ public async Task ProofSelector_WithFees_AccountsForFees()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 1000 } }; // 1 sat per proof
+ var selector = new ProofSelector(fees);
+
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 1,
+ Id = keysetId,
+ C = CreateTestPubKey(1),
+ },
+ new Proof
+ {
+ Amount = 2,
+ Id = keysetId,
+ C = CreateTestPubKey(2),
+ },
+ new Proof
+ {
+ Amount = 4,
+ Id = keysetId,
+ C = CreateTestPubKey(3),
+ },
+ new Proof
+ {
+ Amount = 8,
+ Id = keysetId,
+ C = CreateTestPubKey(4),
+ },
+ new Proof
+ {
+ Amount = 16,
+ Id = keysetId,
+ C = CreateTestPubKey(5),
+ },
+ };
+
+ var result = await selector.SelectProofsToSend(proofs, 10, true);
+
+ Assert.True(Utils.SumProofs(result.Send) >= 10);
+ }
+
+ [Fact]
+ public async Task ProofSelector_SingleLargeProof_SelectsIt()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 0 } };
+ var selector = new ProofSelector(fees);
+
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 100,
+ Id = keysetId,
+ C = CreateTestPubKey(1),
+ },
+ };
+
+ var result = await selector.SelectProofsToSend(proofs, 50, false);
+
+ Assert.Single(result.Send);
+ Assert.Equal(100UL, result.Send[0].Amount);
+ Assert.Empty(result.Keep);
+ }
+
+ [Fact]
+ public void TokenEncode_V4_RoundTrip()
+ {
+ var keysetId = new KeysetId("00ffd48b8f5ecf80");
+ var proofs = new List
+ {
+ new Proof
+ {
+ Amount = 1,
+ Id = keysetId,
+ Secret = new StringSecret(
+ "acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388"
+ ),
+ C = "0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf".ToPubKey(),
+ },
+ };
+
+ var token = new CashuToken
+ {
+ Unit = "sat",
+ Tokens = new List
+ {
+ new CashuToken.Token { Mint = "http://localhost:3338", Proofs = proofs },
+ },
+ };
+
+ var encoded = token.Encode("B", false);
+ Assert.StartsWith("cashuB", encoded);
+
+ var decoded = CashuTokenHelper.Decode(encoded, out var version);
+ Assert.Equal("B", version);
+ Assert.Equal("sat", decoded.Unit);
+ Assert.Single(decoded.Tokens);
+ Assert.Equal("http://localhost:3338", decoded.Tokens[0].Mint);
+ Assert.Single(decoded.Tokens[0].Proofs);
+ Assert.Equal(1UL, decoded.Tokens[0].Proofs[0].Amount);
+ }
+
+ [Fact]
+ public void TokenDecode_InvalidPrefix_ThrowsException()
+ {
+ var invalidToken = "invalidTokenString123";
+ Assert.Throws(() => CashuTokenHelper.Decode(invalidToken, out _));
+ }
+
+ [Fact]
+ public void KeysetId_Equality()
+ {
+ var id1 = new KeysetId("009a1f293253e41e");
+ var id2 = new KeysetId("009a1f293253e41e");
+ var id3 = new KeysetId("000f01df73ea149a");
+
+ Assert.Equal(id1, id2);
+ Assert.NotEqual(id1, id3);
+ Assert.True(id1 == id2);
+ Assert.False(id1 == id3);
+ }
+
+ [Fact]
+ public void KeysetId_GetVersion()
+ {
+ var v0Id = new KeysetId("009a1f293253e41e");
+ var v1Id = new KeysetId(
+ "01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035"
+ );
+
+ Assert.Equal(0x00, v0Id.GetVersion());
+ Assert.Equal(0x01, v1Id.GetVersion());
+ }
+
+ [Fact]
+ public void ComputeFee_NoFees_ReturnsZero()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 0 } };
+
+ var proofs = new List
+ {
+ new Proof { Amount = 1, Id = keysetId },
+ new Proof { Amount = 2, Id = keysetId },
+ };
+
+ var fee = proofs.ComputeFee(fees);
+ Assert.Equal(0UL, fee);
+ }
+
+ [Fact]
+ public void ComputeFee_WithFees_ReturnsCorrectFee()
+ {
+ var keysetId = _testKeysetId;
+ var fees = new Dictionary { { keysetId, 1000 } }; // 1 sat per proof (1000 ppk)
+
+ var proofs = new List
+ {
+ new Proof { Amount = 1, Id = keysetId },
+ new Proof { Amount = 2, Id = keysetId },
+ new Proof { Amount = 4, Id = keysetId },
+ };
+
+ var fee = proofs.ComputeFee(fees);
+ Assert.Equal(3UL, fee); // 3 proofs * 1 sat
+ }
+
+ [Fact]
+ public void SendResponse_DefaultsToEmptyLists()
+ {
+ var response = new SendResponse();
+ Assert.NotNull(response.Keep);
+ Assert.NotNull(response.Send);
+ Assert.Empty(response.Keep);
+ Assert.Empty(response.Send);
+ }
+
+ [Fact]
+ public void Wallet_WithKeysetSyncThreshold_SetsCorrectly()
+ {
+ var wallet = Wallet
+ .Create()
+ .WithMint(MintUrl)
+ .WithKeysetSync(true, TimeSpan.FromMinutes(30));
+
+ Assert.NotNull(wallet);
+ }
+
+ [Fact]
+ public void Wallet_ShouldBumpCounter_Default()
+ {
+ var counter = new InMemoryCounter();
+ var wallet = Wallet.Create().WithMint(MintUrl).WithCounter(counter);
+
+ Assert.NotNull(wallet);
+ }
+
+ [Fact]
+ public void Wallet_ShouldBumpCounter_Disabled()
+ {
+ var counter = new InMemoryCounter();
+ var wallet = Wallet
+ .Create()
+ .WithMint(MintUrl)
+ .WithCounter(counter)
+ .ShouldBumpCounter(false);
+
+ Assert.NotNull(wallet);
+ }
+
+ [Fact]
+ public void MintInfo_FromGetInfoResponse()
+ {
+ var response = new GetInfoResponse
+ {
+ Version = "0.15.0",
+ Name = "Test Mint",
+ Description = "A test mint",
+ };
+
+ var info = new MintInfo(response);
+ Assert.NotNull(info);
+ }
+
+ [Fact]
+ public void P2PkBuilder_Build_CreatesValidSecret()
+ {
+ var privKey = new PrivKey(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ );
+ var builder = new P2PkBuilder
+ {
+ Pubkeys = [privKey.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ SigFlag = "SIG_INPUTS",
+ };
+
+ var secret = builder.Build();
+ Assert.NotNull(secret);
+
+ var allowedPubkeys = secret.GetAllowedPubkeys(out var threshold);
+ Assert.Single(allowedPubkeys);
+ Assert.Equal(1, threshold);
+ }
+
+ [Fact]
+ public void P2PkBuilder_WithMultisig_Build()
+ {
+ var privKey1 = new PrivKey(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ );
+ var privKey2 = new PrivKey(
+ "0000000000000000000000000000000000000000000000000000000000000002"
+ );
+
+ var builder = new P2PkBuilder
+ {
+ Pubkeys = [privKey1.Key.CreatePubKey(), privKey2.Key.CreatePubKey()],
+ SignatureThreshold = 2,
+ SigFlag = "SIG_INPUTS",
+ };
+
+ var secret = builder.Build();
+ var allowedPubkeys = secret.GetAllowedPubkeys(out var threshold);
+
+ Assert.Equal(2, allowedPubkeys.Count());
+ Assert.Equal(2, threshold);
+ }
+
+ [Fact]
+ public void HTLCBuilder_Build_CreatesValidSecret()
+ {
+ var preimage = "0000000000000000000000000000000000000000000000000000000000000001";
+ using var sha = System.Security.Cryptography.SHA256.Create();
+ var hashLockBytes = sha.ComputeHash(Convert.FromHexString(preimage));
+ var hashLock = Convert.ToHexString(hashLockBytes).ToLower();
+ var privKey = new PrivKey(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ );
+
+ var builder = new HTLCBuilder
+ {
+ HashLock = hashLock,
+ Pubkeys = [privKey.Key.CreatePubKey()],
+ SignatureThreshold = 1,
+ };
+
+ var secret = builder.Build();
+ Assert.NotNull(secret);
+
+ var allowedPubkeys = secret.GetAllowedPubkeys(out var threshold);
+ Assert.Single(allowedPubkeys);
+ }
+
+ [Fact]
+ public async Task Wallet_ThrowsOnMissingMint_ForAllOperations()
+ {
+ var wallet = Wallet.Create();
+
+ await Assert.ThrowsAsync(() => wallet.GetInfo());
+ Assert.Throws(() => wallet.CreateMintQuote());
+ Assert.Throws(() => wallet.CreateMeltQuote());
+ Assert.Throws(() => wallet.Swap());
+ Assert.Throws(() => wallet.Restore());
+ }
+
+ [Fact]
+ public async Task Counter_ReturnsZeroForUnknownKeysetId()
+ {
+ var counter = new InMemoryCounter();
+ var unknownKeysetId = new KeysetId("00unknown1234567");
+
+ var value = await counter.GetCounterForId(unknownKeysetId);
+ Assert.Equal((uint)0, value);
+ }
+
+ [Fact]
+ public async Task Counter_MultipleKeysets_IndependentCounters()
+ {
+ var counter = new InMemoryCounter();
+ var keysetId1 = new KeysetId("00keyset11234567");
+ var keysetId2 = new KeysetId("00keyset21234567");
+
+ await counter.IncrementCounter(keysetId1, 10);
+ await counter.IncrementCounter(keysetId2, 20);
+
+ Assert.Equal((uint)10, await counter.GetCounterForId(keysetId1));
+ Assert.Equal((uint)20, await counter.GetCounterForId(keysetId2));
+ }
+}
diff --git a/DotNut.sln.DotSettings.user b/DotNut.sln.DotSettings.user
index 47f8615..e4ea523 100644
--- a/DotNut.sln.DotSettings.user
+++ b/DotNut.sln.DotSettings.user
@@ -2,20 +2,50 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
+ ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
- C:\Users\evilk\AppData\Local\JetBrains\Rider2024.2\resharper-host\temp\Rider\vAny\CoverageData\_DotNut.1481064820\Snapshot\snapshot.utdcvr
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="UnitTest1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1</TestId>
+ </TestAncestor>
+</SessionState>
+
- <SessionState ContinuousTestingMode="0" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <TestAncestor>
- <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1.Nut00Tests_TokenSerialization</TestId>
- </TestAncestor>
+ <SessionState ContinuousTestingMode="0" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.Integration</TestId>
+ </TestAncestor>
+</SessionState>
+ <SessionState ContinuousTestingMode="0" Name="Nut11_Signatures" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTests2.BuilderChainingPreservesAllSettings</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTests2.WithMintStringVariantCreatesHttpClient</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.Integration</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTests2</TestId>
+ </TestAncestor>
+</SessionState>
+ <SessionState ContinuousTestingMode="0" Name="Integration" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.Integration</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTests2</TestId>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1</TestId>
+ </TestAncestor>
</SessionState>
- <SessionState ContinuousTestingMode="0" IsActive="True" Name="Nut11_Signatures" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <Project Location="C:\Git\DotNuts\DotNut.Tests" Presentation="<DotNut.Tests>" />
+ <SessionState ContinuousTestingMode="0" Name="Nut11_SIG_ALL" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>xUnit::0AAAA85C-9FDC-4FD5-9CC2-ED8385B40106::net8.0::DotNut.Tests.UnitTest1.Nut11_SIG_ALL</TestId>
+ </TestAncestor>
</SessionState>
\ No newline at end of file
diff --git a/DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs b/DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs
new file mode 100644
index 0000000..4570f4f
--- /dev/null
+++ b/DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs
@@ -0,0 +1,64 @@
+using DotNut.ApiModels;
+
+namespace DotNut.Abstractions.Handlers;
+
+public class MeltHandlerBolt11(
+ IWalletBuilder wallet,
+ PostMeltQuoteBolt11Response quote,
+ List blankOutputs,
+ List? privKeys = null,
+ string? htlcPreimage = null
+) : IMeltHandler>
+{
+ public PostMeltQuoteBolt11Response GetQuote() => quote;
+ public List GetBlankOutputs() => blankOutputs;
+
+ public async Task> Melt(IEnumerable inputs, CancellationToken ct = default)
+ {
+ //we're operating on copy here since later the proof state is mutated in stripFingerprints
+ var proofs = inputs.DeepCopyList();
+
+ Nut10Helper.MaybeProcessNut10(
+ privKeys ?? [],
+ proofs,
+ blankOutputs,
+ htlcPreimage,
+ quote.Quote
+ );
+ //since nut10 (with p2bk) is aleady processed, now it's safe to strip P2PkE
+ proofs.ForEach(i => i.StripFingerprints());
+
+ var client = await wallet.GetMintApi(ct);
+ var req = new PostMeltRequest
+ {
+ Quote = quote.Quote,
+ Inputs = proofs.ToArray(),
+ Outputs = blankOutputs.Select(bo => bo.BlindedMessage).ToArray(),
+ };
+
+ var res = await client.Melt(
+ "bolt11",
+ req,
+ ct
+ );
+ if (res.Change == null || res.Change.Length == 0)
+ {
+ return [];
+ }
+
+ var keysetIds = res.Change.Select(sig => sig.Id).Distinct().ToList();
+ var changeProofs = new List();
+ foreach (var keysetId in keysetIds)
+ {
+ var keyset = await wallet.GetKeys(keysetId, true, false, ct);
+ if (keyset == null)
+ {
+ continue;
+ }
+ changeProofs.AddRange(
+ Utils.ConstructProofsFromPromises(res.Change.ToList(), blankOutputs, keyset.Keys)
+ );
+ }
+ return changeProofs;
+ }
+}
diff --git a/DotNut/Abstractions/Handlers/MeltHandlerBolt12.cs b/DotNut/Abstractions/Handlers/MeltHandlerBolt12.cs
new file mode 100644
index 0000000..c115830
--- /dev/null
+++ b/DotNut/Abstractions/Handlers/MeltHandlerBolt12.cs
@@ -0,0 +1,63 @@
+using DotNut.ApiModels;
+using DotNut.ApiModels.Melt.bolt12;
+
+namespace DotNut.Abstractions.Handlers;
+
+public class MeltHandlerBolt12(
+ IWalletBuilder wallet,
+ PostMeltQuoteBolt12Response quote,
+ List blankOutputs,
+ List? privKeys = null,
+ string? htlcPreimage = null
+) : IMeltHandler>
+{
+ public PostMeltQuoteBolt12Response GetQuote() => quote;
+
+ public async Task> Melt(IEnumerable inputs, CancellationToken ct = default)
+ {
+ //we're operating on copy here since later the proof state is mutated in stripFingerprints
+ var proofs = inputs.DeepCopyList();
+
+ Nut10Helper.MaybeProcessNut10(
+ privKeys ?? [],
+ proofs,
+ blankOutputs,
+ htlcPreimage,
+ quote.Quote
+ );
+ proofs.ForEach(i => i.StripFingerprints());
+
+ var client = await wallet.GetMintApi(ct);
+ var req = new PostMeltRequest
+ {
+ Quote = quote.Quote,
+ Inputs = proofs.ToArray(),
+ Outputs = blankOutputs.Select(bo => bo.BlindedMessage).ToArray(),
+ };
+
+ var res = await client.Melt(
+ "bolt12",
+ req,
+ ct
+ );
+ if (res.Change == null || res.Change.Length == 0)
+ {
+ return [];
+ }
+
+ var keysetIds = res.Change.Select(sig => sig.Id).Distinct().ToList();
+ var changeProofs = new List();
+ foreach (var keysetId in keysetIds)
+ {
+ var keyset = await wallet.GetKeys(keysetId, true, false, ct);
+ if (keyset == null)
+ {
+ continue;
+ }
+ changeProofs.AddRange(
+ Utils.ConstructProofsFromPromises(res.Change.ToList(), blankOutputs, keyset.Keys)
+ );
+ }
+ return changeProofs;
+ }
+}
diff --git a/DotNut/Abstractions/Handlers/MintHandlerBolt11.cs b/DotNut/Abstractions/Handlers/MintHandlerBolt11.cs
new file mode 100644
index 0000000..b8db212
--- /dev/null
+++ b/DotNut/Abstractions/Handlers/MintHandlerBolt11.cs
@@ -0,0 +1,63 @@
+using DotNut.ApiModels;
+
+namespace DotNut.Abstractions.Handlers;
+
+public class MintHandlerBolt11(
+ IWalletBuilder wallet,
+ PostMintQuoteBolt11Response postMintQuoteBolt11Response,
+ GetKeysResponse.KeysetItemResponse keyset,
+ List outputs
+) : IMintHandler>
+{
+ private string? _signature;
+
+ public IMintHandler> WithSignature(string signature)
+ {
+ _signature = signature;
+ return this;
+ }
+
+ public IMintHandler> SignWithPrivkey(string privKeyHex)
+ {
+ return this.SignWithPrivkey(new PrivKey(privKeyHex));
+ }
+
+ public IMintHandler> SignWithPrivkey(PrivKey privkey)
+ {
+ this._signature = privkey.SignMintQuote(
+ postMintQuoteBolt11Response.Quote,
+ outputs.Select(o => o.BlindedMessage).ToList()
+ );
+ return this;
+ }
+
+ public PostMintQuoteBolt11Response GetQuote() => postMintQuoteBolt11Response;
+
+ public List GetOutputs() => outputs;
+
+ public async Task> Mint(CancellationToken ct = default)
+ {
+ if (postMintQuoteBolt11Response.PubKey is not null && this._signature is null)
+ {
+ throw new ArgumentNullException(
+ nameof(_signature),
+ $"Signature for mint quote {postMintQuoteBolt11Response.Quote} is required!"
+ );
+ }
+ var client = await wallet.GetMintApi(ct);
+
+ var req = new PostMintRequest
+ {
+ Outputs = outputs.Select(o => o.BlindedMessage).ToArray(),
+ Quote = postMintQuoteBolt11Response.Quote,
+ Signature = _signature,
+ };
+
+ var promises = await client.Mint("bolt11", req, ct);
+ return Utils.ConstructProofsFromPromises(
+ promises.Signatures.ToList(),
+ outputs,
+ keyset.Keys
+ );
+ }
+}
diff --git a/DotNut/Abstractions/Handlers/MintHandlerBolt12.cs b/DotNut/Abstractions/Handlers/MintHandlerBolt12.cs
new file mode 100644
index 0000000..1b66790
--- /dev/null
+++ b/DotNut/Abstractions/Handlers/MintHandlerBolt12.cs
@@ -0,0 +1,64 @@
+using DotNut.ApiModels;
+using DotNut.ApiModels.Mint.bolt12;
+
+namespace DotNut.Abstractions.Handlers;
+
+public class MintHandlerBolt12(
+ IWalletBuilder wallet,
+ PostMintQuoteBolt12Response quote,
+ GetKeysResponse.KeysetItemResponse keyset,
+ List outputs
+) : IMintHandler>
+{
+ private string? _signature;
+
+ public IMintHandler> WithSignature(string signature)
+ {
+ _signature = signature;
+ return this;
+ }
+
+ public IMintHandler> SignWithPrivkey(string privKeyHex)
+ {
+ return this.SignWithPrivkey(new PrivKey(privKeyHex));
+ }
+
+ public IMintHandler> SignWithPrivkey(PrivKey privkey)
+ {
+ this._signature = privkey.SignMintQuote(
+ quote.Quote,
+ outputs.Select(o => o.BlindedMessage).ToList()
+ );
+ return this;
+ }
+
+ public PostMintQuoteBolt12Response GetQuote() => quote;
+
+ public List GetOutputs() => outputs;
+
+ public async Task> Mint(CancellationToken ct = default)
+ {
+ if (this._signature is null)
+ {
+ throw new ArgumentNullException(
+ nameof(this._signature),
+ $"Signature for mint quote {quote.Quote} is required!"
+ );
+ }
+
+ var client = await wallet.GetMintApi(ct);
+ var req = new PostMintRequest
+ {
+ Outputs = outputs.Select(o => o.BlindedMessage).ToArray(),
+ Quote = quote.Quote,
+ Signature = _signature,
+ };
+
+ var promises = await client.Mint("bolt12", req, ct);
+ return Utils.ConstructProofsFromPromises(
+ promises.Signatures.ToList(),
+ outputs,
+ keyset.Keys
+ );
+ }
+}
diff --git a/DotNut/Abstractions/InMemoryCounter.cs b/DotNut/Abstractions/InMemoryCounter.cs
new file mode 100644
index 0000000..ed2a3c9
--- /dev/null
+++ b/DotNut/Abstractions/InMemoryCounter.cs
@@ -0,0 +1,64 @@
+using System.Collections.Concurrent;
+
+namespace DotNut.Abstractions;
+
+public class InMemoryCounter : ICounter
+{
+ private readonly ConcurrentDictionary _counter;
+
+ public InMemoryCounter(IDictionary counter)
+ {
+ this._counter = new ConcurrentDictionary(counter);
+ }
+
+ public InMemoryCounter()
+ {
+ this._counter = new ConcurrentDictionary();
+ }
+
+ public Task GetCounterForId(KeysetId keysetId, CancellationToken ct = default)
+ {
+ return Task.FromResult(_counter.GetOrAdd(keysetId, 0));
+ }
+
+ public Task IncrementCounter(
+ KeysetId keysetId,
+ uint bumpBy = 1,
+ CancellationToken ct = default
+ )
+ {
+ var next = _counter.AddOrUpdate(keysetId, bumpBy, (_, current) => current + bumpBy);
+ return Task.FromResult(next);
+ }
+
+ public Task<(uint oldValue, uint newValue)> FetchAndIncrement(
+ KeysetId keysetId,
+ uint bumpBy = 1,
+ CancellationToken ct = default
+ )
+ {
+ uint oldValue = 0;
+ uint newValue = _counter.AddOrUpdate(
+ keysetId,
+ bumpBy,
+ (_, current) =>
+ {
+ oldValue = current;
+ return current + bumpBy;
+ }
+ );
+
+ return Task.FromResult((oldValue, newValue));
+ }
+
+ public Task SetCounter(KeysetId keysetId, uint counter, CancellationToken ct = default)
+ {
+ _counter[keysetId] = counter;
+ return Task.CompletedTask;
+ }
+
+ public async Task> Export()
+ {
+ return new Dictionary(_counter);
+ }
+}
diff --git a/DotNut/Abstractions/Interfaces/ICounter.cs b/DotNut/Abstractions/Interfaces/ICounter.cs
new file mode 100644
index 0000000..fdf362e
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/ICounter.cs
@@ -0,0 +1,25 @@
+namespace DotNut.Abstractions;
+
+public interface ICounter
+{
+ ///
+ /// Gets counter for current keysetID. This counter will be used for next proof generation, so make sure it's
+ /// always set to last used proof + 1
+ ///
+ ///
+ ///
+ ///
+ public Task GetCounterForId(KeysetId keysetId, CancellationToken ct = default);
+ public Task IncrementCounter(
+ KeysetId keysetId,
+ uint bumpBy = 1,
+ CancellationToken ct = default
+ );
+ public Task<(uint oldValue, uint newValue)> FetchAndIncrement(
+ KeysetId keysetId,
+ uint bumpBy = 1,
+ CancellationToken ct = default
+ );
+ public Task SetCounter(KeysetId keysetId, uint counter, CancellationToken ct = default);
+ public Task> Export();
+}
diff --git a/DotNut/Abstractions/Interfaces/IMeltHandler.cs b/DotNut/Abstractions/Interfaces/IMeltHandler.cs
new file mode 100644
index 0000000..f65cfb1
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IMeltHandler.cs
@@ -0,0 +1,9 @@
+namespace DotNut.Abstractions;
+
+public interface IMeltHandler;
+
+public interface IMeltHandler : IMeltHandler
+{
+ TQuote GetQuote();
+ Task Melt(IEnumerable inputs, CancellationToken ct = default);
+}
diff --git a/DotNut/Abstractions/Interfaces/IMeltQuoteBuilder.cs b/DotNut/Abstractions/Interfaces/IMeltQuoteBuilder.cs
new file mode 100644
index 0000000..d67ef53
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IMeltQuoteBuilder.cs
@@ -0,0 +1,56 @@
+using DotNut.ApiModels;
+using DotNut.ApiModels.Melt.bolt12;
+
+namespace DotNut.Abstractions;
+
+///
+/// Melt operation builder (pay invoices)
+///
+public interface IMeltQuoteBuilder
+{
+ ///
+ /// Optional. Sets the base unit for the quote; defaults to "sat".
+ ///
+ IMeltQuoteBuilder WithUnit(string unit);
+
+ ///
+ /// Mandatory. A bolt11 invoice is required to create a melt quote.
+ ///
+ IMeltQuoteBuilder WithInvoice(string bolt11Invoice);
+
+ ///
+ /// Optional. Supply previously generated blank outputs instead of deriving them.
+ ///
+ IMeltQuoteBuilder WithBlankOutputs(IEnumerable blankOutputs);
+
+ ///
+ /// Optional. Provide private keys for P2PK proofs associated with the inputs.
+ ///
+ IMeltQuoteBuilder WithPrivKeys(IEnumerable privKeys);
+
+ ///
+ /// Optional and mandatory if amountless invoice provided.
+ ///
+ /// Melt quote amount in millisatoshis
+ ///
+ IMeltQuoteBuilder WithAmount(ulong msat);
+
+ ///
+ /// Optional. Supply HTLC preimage to sign HTLC-based proofs.
+ ///
+ IMeltQuoteBuilder WithHTLCPreimage(string preimage);
+
+ ///
+ /// Create a bolt11 melt handler.
+ ///
+ Task>> ProcessAsyncBolt11(
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Create a bolt12 melt handler.
+ ///
+ Task>> ProcessAsyncBolt12(
+ CancellationToken ct = default
+ );
+}
diff --git a/DotNut/Abstractions/Interfaces/IMintHandler.cs b/DotNut/Abstractions/Interfaces/IMintHandler.cs
new file mode 100644
index 0000000..243ebaf
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IMintHandler.cs
@@ -0,0 +1,15 @@
+namespace DotNut.Abstractions;
+
+public interface IMintHandler;
+
+public interface IMintHandler : IMintHandler
+{
+ public IMintHandler WithSignature(string signature);
+ public IMintHandler SignWithPrivkey(PrivKey privkey);
+ public IMintHandler SignWithPrivkey(string privKeyHex);
+
+ TQuote GetQuote();
+ List GetOutputs();
+
+ Task Mint(CancellationToken ct = default);
+}
diff --git a/DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs b/DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs
new file mode 100644
index 0000000..81a5341
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs
@@ -0,0 +1,73 @@
+using DotNut.ApiModels;
+using DotNut.ApiModels.Mint.bolt12;
+
+namespace DotNut.Abstractions;
+
+///
+/// Mint operation builder (receive from invoice)
+///
+public interface IMintQuoteBuilder
+{
+ ///
+ /// Optional. Sets unit of tokens being minted; defaults to satoshi.
+ ///
+ IMintQuoteBuilder WithUnit(string unit);
+
+ ///
+ /// Mandatory. Amount of tokens to mint in the current unit.
+ ///
+ IMintQuoteBuilder WithAmount(ulong amount);
+
+ ///
+ /// Optional for bolt11 and mandatory for bolt12.
+ ///
+ ///
+ ///
+ IMintQuoteBuilder WithPubkey(string pubkey);
+
+ ///
+ /// Optional for bolt11 and mandatory for bolt12.
+ ///
+ IMintQuoteBuilder WithPubkey(PubKey pubkey);
+
+ ///
+ /// Optional. Provide precomputed outputs so blinding factors and secrets are reused safely.
+ ///
+ IMintQuoteBuilder WithOutputs(IEnumerable outputs);
+
+ ///
+ /// Optional. Provide description for the mint invoice.
+ ///
+ IMintQuoteBuilder WithDescription(string description);
+
+ ///
+ /// Optional. Allows providing a P2PK builder when a signature is required for minting.
+ ///
+ IMintQuoteBuilder WithP2PkLock(P2PkBuilder p2pkBuilder);
+
+ ///
+ /// Optional. When minting P2Pk / HTLC Proofs allows to blind the pubkeys.
+ ///
+ ///
+ ///
+ IMintQuoteBuilder BlindPubkeys(bool withBlinding = true);
+
+ ///
+ /// Optional. Allows adding HTLC-based outputs.
+ ///
+ IMintQuoteBuilder WithHTLCLock(HTLCBuilder htlcBuilder);
+
+ ///
+ /// Creates a bolt11 mint quote and handler.
+ ///
+ Task>> ProcessAsyncBolt11(
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Creates a bolt12 mint quote and handler.
+ ///
+ Task>> ProcessAsyncBolt12(
+ CancellationToken ct = default
+ );
+}
diff --git a/DotNut/Abstractions/Interfaces/IProofSelector.cs b/DotNut/Abstractions/Interfaces/IProofSelector.cs
new file mode 100644
index 0000000..9b9f370
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IProofSelector.cs
@@ -0,0 +1,11 @@
+namespace DotNut.Abstractions;
+
+public interface IProofSelector
+{
+ Task SelectProofsToSend(
+ IEnumerable proofsToSelectFrom,
+ ulong amountToSend,
+ bool includeFees = false,
+ CancellationToken ct = default
+ );
+}
diff --git a/DotNut/Abstractions/Interfaces/IRestoreBuilder.cs b/DotNut/Abstractions/Interfaces/IRestoreBuilder.cs
new file mode 100644
index 0000000..71e4e2f
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IRestoreBuilder.cs
@@ -0,0 +1,17 @@
+namespace DotNut.Abstractions;
+
+///
+/// Restore operation builder
+///
+public interface IRestoreBuilder
+{
+ ///
+ /// Optional and usually not-advised. Allows to specify keysets that we want to restore.
+ /// If not set, every keyset is grinded.
+ ///
+ ///
+ ///
+ IRestoreBuilder FromKeysetIds(IEnumerable keysetIds);
+
+ Task> ProcessAsync(CancellationToken ct = default);
+}
diff --git a/DotNut/Abstractions/Interfaces/ISwapBuilder.cs b/DotNut/Abstractions/Interfaces/ISwapBuilder.cs
new file mode 100644
index 0000000..4cfa4a7
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/ISwapBuilder.cs
@@ -0,0 +1,74 @@
+namespace DotNut.Abstractions;
+
+///
+/// Swap operation builder
+///
+public interface ISwapBuilder
+{
+ ///
+ /// Optional. Sets wallet unit for the swap; defaults to "sat".
+ ///
+ ISwapBuilder WithUnit(string unit);
+
+ ///
+ /// Optional. Choose target keyset for the swapped proofs.
+ ///
+ ISwapBuilder ForKeyset(KeysetId targetKeysetId);
+
+ ///
+ /// Provide proofs that will be used as inputs for the swap.
+ ///
+ ISwapBuilder FromInputs(IEnumerable inputs);
+
+ ///
+ /// Optional. Supply custom blank outputs instead of deriving them automatically.
+ ///
+ ISwapBuilder ForOutputs(IEnumerable outputs);
+
+ ///
+ /// Optional. Toggle DLEQ verification for incoming proofs.
+ ///
+ ISwapBuilder WithDLEQVerification(bool verify = true);
+
+ ///
+ /// Optional. Include or skip fee calculations when creating outputs.
+ ///
+ ISwapBuilder WithFeeCalculation(bool includeFees = true);
+
+ ///
+ /// Optional. Explicitly select output amounts.
+ ///
+ ISwapBuilder WithAmounts(IEnumerable amounts);
+
+ ///
+ /// Optional. Provide private keys associated with the proofs.
+ ///
+ ISwapBuilder WithPrivkeys(IEnumerable privKeys);
+
+ ///
+ /// Optional. Generate outputs guarded by P2PK locking.
+ ///
+ ISwapBuilder ToP2PK(P2PkBuilder p2pkBuilder);
+
+ ///
+ /// Optional. Blind P2Pk / HTLC proofs.
+ ///
+ ///
+ ///
+ ISwapBuilder BlindPubkeys(bool withBlinding = true);
+
+ ///
+ /// Optional. Supply preimage for HTLC-based proofs.
+ ///
+ ISwapBuilder WithHtlcPreimage(string preimage);
+
+ ///
+ /// Optional. Generate HTLC outputs.
+ ///
+ ISwapBuilder ToHTLC(HTLCBuilder htlcBuilder);
+
+ ///
+ /// Executes the swap flow and returns newly minted proofs.
+ ///
+ Task> ProcessAsync(CancellationToken ct = default);
+}
diff --git a/DotNut/Abstractions/Interfaces/IWalletBuilder.cs b/DotNut/Abstractions/Interfaces/IWalletBuilder.cs
new file mode 100644
index 0000000..b6faccd
--- /dev/null
+++ b/DotNut/Abstractions/Interfaces/IWalletBuilder.cs
@@ -0,0 +1,308 @@
+using DotNut.Api;
+using DotNut.ApiModels;
+using DotNut.NBitcoin.BIP39;
+
+namespace DotNut.Abstractions;
+
+///
+/// Fluent builder interface for Cashu Wallet operations
+///
+public interface IWalletBuilder : IDisposable
+{
+ ///
+ /// Mandatory. Sets a mint in a wallet object
+ ///
+ /// Mint API object.
+ IWalletBuilder WithMint(ICashuApi mintApi, bool canDispose = false);
+
+ ///
+ /// Mandatory. Sets a mint in a wallet object (with default CashuHttpClient)
+ ///
+ /// Mint URL string.
+ IWalletBuilder WithMint(string mintUrl);
+
+ ///
+ /// Mandatory. Sets a mint in a wallet object (with default CashuHttpClient)
+ ///
+ /// Mint URI.
+ IWalletBuilder WithMint(Uri mintUri);
+
+ ///
+ /// Optional. Import Mint Info to CashuWallet. Otherwise, it will be fetched from /v1/info endpoint.
+ ///
+ /// MintInfo object
+ IWalletBuilder WithInfo(MintInfo info);
+
+ ///
+ /// Optional. Import Mint Info to CashuWallet. Otherwise, it will be fetched from /v1/info endpoint.
+ ///
+ /// GetInfoResponse payload returned from mints API
+ IWalletBuilder WithInfo(GetInfoResponse info);
+
+ ///
+ /// Optional. Import Keysets into CashuWallet class. Otherwise, they will be fetched from /v1/keysets endpoint.
+ ///
+ /// List of Keysets
+ IWalletBuilder WithKeysets(IEnumerable keysets);
+
+ ///
+ /// Optional. Import Keysets into CashuWallet class. Otherwise, they will be fetched from /v1/keysets endpoint.
+ ///
+ /// GetKeysetsResponse payload returned from mints API
+ IWalletBuilder WithKeysets(GetKeysetsResponse keysets);
+
+ ///
+ /// Optional. Import Keys into CashuWallet class. Otherwise, they will be fetched from /v1/keys endpoint.
+ ///
+ /// List of mints Keys
+ IWalletBuilder WithKeys(IEnumerable keys);
+
+ ///
+ /// Optional. Import Keys into CashuWallet class. Otherwise, they will be fetched from /v1/keys endpoint.
+ ///
+ /// GetKeysResponse payload returned from mints API
+ IWalletBuilder WithKeys(GetKeysResponse keys);
+
+ ///
+ /// Optional. Flag suggesting if CashuWallet should sync provided Keys and Keysets with actual mints state.
+ /// Very useful if wallet stores keys in storage.
+ ///
+ /// boolean, true by default
+ IWalletBuilder WithKeysetSync(bool syncKeyset = true);
+
+ ///
+ /// Optional. Flag suggesting if CashuWallet should sync provided Keys and Keysets with actual mints state.
+ /// Has an additional field limiting how often keysets can be refetched. If not set, keysets will be synced only single time,
+ /// with first operation requiring keysets. (I'd go for like, 60 minutes)
+ ///
+ ///
+ ///
+ ///
+ IWalletBuilder WithKeysetSync(bool syncKeyset, TimeSpan syncThreshold);
+
+ ///
+ /// Optional. Proof selecting algorithm. If not set, defaults to RGLI proof selector.
+ ///
+ ///
+ IWalletBuilder WithSelector(IProofSelector selector);
+
+ ///
+ /// Optional. BIP39 seed for secret and blinding factors derivation. All proofs generated by CashuWallet will be recoverable.
+ ///
+ /// Mnemonic object
+ IWalletBuilder WithMnemonic(Mnemonic mnemonic);
+
+ ///
+ /// Optional. BIP39 seed for secret and blinding factors derivation. All proofs generated by CashuWallet will be recoverable.
+ ///
+ /// Bip39 seed string separated by spaces.
+ IWalletBuilder WithMnemonic(string mnemonic);
+
+ ///
+ /// Optional and mandatory if Mnemonic provided. Counter for each Keyset Id for derivation purposes.
+ ///
+ /// Counter object
+ IWalletBuilder WithCounter(ICounter counter);
+
+ ///
+ /// Optional and mandatory if Mnemonic provided. Counter for each Keyset Id for derivation purposes.
+ ///
+ /// Counter dictionary
+ ///
+ public IWalletBuilder WithCounter(IDictionary counter);
+
+ ///
+ /// Optional and if not set, always true. Controls automatic counter incrementation for secret generation.
+ ///
+ /// If true, counter increments automatically. If false, requires manual management.
+ ///
+ /// WARNING: Disabling auto-increment is potentially dangerous. Manual counter management is required
+ /// to prevent secret reuse, which will cause mint rejection and operation failures.
+ ///
+ IWalletBuilder ShouldBumpCounter(bool shouldBumpCounter = true);
+
+ ///
+ /// Optional.
+ /// Adds websocket service. You should use single websocket service (singleton at best) for multiple wallets, in order to handle everything in nice manner.
+ /// If not set, but requested it'll be created automatically (which won't be so optimal).
+ ///
+ ///
+ ///
+ IWalletBuilder WithWebsocketService(IWebsocketService websocketService);
+
+ ///
+ /// Get Mints info, supported methods etc.
+ ///
+ /// Refetch flag
+ ///
+ /// MintInfo object
+ Task GetInfo(bool forceRefresh = false, CancellationToken ct = default);
+
+ ///
+ /// Create Outputs (BlindedMessags, Blinding Factors, Secrets), for given keysetId.
+ /// Deterministic if Mnemonic and Counter set up.
+ ///
+ /// List of amounts in Outputs.
+ /// Keyset ID
+ ///
+ /// Outputs
+ /// If keys not set. If Mnemonic set, but no Counter.
+ Task> CreateOutputs(
+ IEnumerable amounts,
+ KeysetId id,
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Create Outputs for active KeysetId for given unit. Fetches a keyset for given unit automatically.
+ ///
+ /// List of amounts.
+ ///
+ ///
+ /// Outputs
+ /// If no keysetID stored in wallet.
+ Task> CreateOutputs(
+ IEnumerable amounts,
+ string unit,
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Set Last sync date to DateTime.MinValue - keysets will be synced before next operation
+ ///
+ public void InvalidateCache();
+
+ ///
+ /// Get active keyset id for chosen unit.
+ ///
+ /// keyset unit, e.g. sat
+ ///
+ /// Active keysetId
+ Task GetActiveKeysetId(string unit, CancellationToken ct = default);
+
+ ///
+ /// Get all keysets with units
+ ///
+ /// Dictionary of (unit, KeysetId)
+ Task>> GetKeysetIdsWithUnits(CancellationToken ct = default);
+
+ ///
+ /// Get active keyset ids for each supported unit
+ ///
+ /// Dictionary of (unit, KeysetId)
+ Task> GetActiveKeysetIdsWithUnits(CancellationToken ct = default);
+
+ Task GetMintApi(CancellationToken ct = default);
+
+ ///
+ /// Get keys of current mint stored in wallet.
+ ///
+ /// Refetch flag
+ ///
+ /// Mints keys
+ Task> GetKeys(
+ bool forceRefresh = false,
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Get Keys for given KeysetID. At first it tries to find corresponding keys, if allowFetch is true, will try to
+ /// fetch keys if not present in wallet.
+ ///
+ /// KeysetId
+ /// If keyset not present not in db, it can be fetched
+ /// Refetch flag
+ ///
+ /// Keys for given keyset
+ Task GetKeys(
+ KeysetId id,
+ bool allowFetch,
+ bool forceRefresh = false,
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Get Keysets stored in wallet
+ ///
+ /// Refetch flag
+ ///
+ /// List of Keysets
+ Task> GetKeysets(
+ bool forceRefresh = false,
+ CancellationToken ct = default
+ );
+
+ ///
+ /// Select proofs for sending purposes. By default uses RGLI algorithm, unless another one provided.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SelectProofsToSend(
+ IEnumerable proofs,
+ ulong amount,
+ bool includeFees,
+ CancellationToken ct = default
+ );
+
+ ///