Skip to content

Commit

Permalink
Merge branch 'master' into fix/ExtractUriFromInternetIdentifier-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Kukks authored Jun 28, 2024
2 parents 9a40356 + 061bcaa commit 3f08523
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 85 deletions.
2 changes: 2 additions & 0 deletions LNURL.Tests/LNURL.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NBitcoin" Version="7.0.30" />
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.20" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
250 changes: 171 additions & 79 deletions LNURL.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBitcoin.Altcoins.Elements;
using NBitcoin.Crypto;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using Xunit;
Expand All @@ -9,43 +13,40 @@ namespace LNURL.Tests
{
public class UnitTest1
{

[Fact]
public void CanHandlePayLinkEdgeCase()
{
// from https://github.com/btcpayserver/btcpayserver/issues/4393
var json =

"{"+
" \"callback\": \"https://coincorner.io/lnurl/withdrawreq/auth/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?picc_data=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\","+
" \"defaultDescription\": \"CoinCorner Withdrawal ⚡️\","+
" \"maxWithdrawable\": 363003000,"+
" \"minWithdrawable\": 1000,"+
" \"k1\": \"xxxxxxxxxx\","+
" \"tag\": \"withdrawRequest\","+
" \"payLink\": \"\"" +
var json =
"{" +
" \"callback\": \"https://coincorner.io/lnurl/withdrawreq/auth/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?picc_data=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"," +
" \"defaultDescription\": \"CoinCorner Withdrawal ⚡️\"," +
" \"maxWithdrawable\": 363003000," +
" \"minWithdrawable\": 1000," +
" \"k1\": \"xxxxxxxxxx\"," +
" \"tag\": \"withdrawRequest\"," +
" \"payLink\": \"\"" +
"}";

var req = JsonConvert.DeserializeObject<LNURLWithdrawRequest>(json);

Assert.Null(req.PayLink);

json =

"{"+
" \"callback\": \"https://coincorner.io/lnurl/withdrawreq/auth/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?picc_data=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\","+
" \"defaultDescription\": \"CoinCorner Withdrawal ⚡️\","+
" \"maxWithdrawable\": 363003000,"+
" \"minWithdrawable\": 1000,"+
" \"k1\": \"xxxxxxxxxx\","+
" \"tag\": \"withdrawRequest\""+

json =
"{" +
" \"callback\": \"https://coincorner.io/lnurl/withdrawreq/auth/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?picc_data=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"," +
" \"defaultDescription\": \"CoinCorner Withdrawal ⚡️\"," +
" \"maxWithdrawable\": 363003000," +
" \"minWithdrawable\": 1000," +
" \"k1\": \"xxxxxxxxxx\"," +
" \"tag\": \"withdrawRequest\"" +
"}";

req = JsonConvert.DeserializeObject<LNURLWithdrawRequest>(json);

Assert.Null(req.PayLink);
}

[Theory]
[InlineData("[email protected]", "https://btcpay.kukks.org/.well-known/lnurlp/kukks")]
[InlineData("[email protected]:4000", "https://btcpay.kukks.org:4000/.well-known/lnurlp/kukks")]
Expand All @@ -54,7 +55,6 @@ public void CanHandlePayLinkEdgeCase()
public void CanParseLightningAddress(string lightningAddress, string expectedUrl)
{
Assert.Equal(expectedUrl, LNURL.ExtractUriFromInternetIdentifier(lightningAddress).ToString());

}

[Fact]
Expand All @@ -71,21 +71,16 @@ public void CanEncodeDecodeLNUrl()
"LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS",
LNURL.EncodeBech32(uri), StringComparer.InvariantCultureIgnoreCase);

Assert.Equal("lightning:LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS",
Assert.Equal(
"lightning:LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS",
LNURL.EncodeUri(uri, null, true).ToString(), StringComparer.InvariantCultureIgnoreCase);

Assert.Throws<ArgumentNullException>(() =>
{
LNURL.EncodeUri(uri, null, false);
});
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
LNURL.EncodeUri(uri, "swddwdd", false);
});
Assert.Throws<ArgumentNullException>(() => { LNURL.EncodeUri(uri, null, false); });
Assert.Throws<ArgumentOutOfRangeException>(() => { LNURL.EncodeUri(uri, "swddwdd", false); });
var payRequestUri = LNURL.EncodeUri(uri, "payRequest", false);
Assert.Equal("lnurlp", payRequestUri.Scheme);


uri = LNURL.Parse(
"lnurlp://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df",
out tag);
Expand All @@ -98,11 +93,7 @@ public void CanEncodeDecodeLNUrl()
[Fact]
public async Task CanUseLNURLAUTH()
{

Assert.Throws<ArgumentException>(() =>
{
LNURL.EncodeUri(new Uri("https://kukks.org"), "login", true);
});
Assert.Throws<ArgumentException>(() => { LNURL.EncodeUri(new Uri("https://kukks.org"), "login", true); });
Assert.Throws<ArgumentException>(() =>
{
LNURL.EncodeUri(new Uri("https://kukks.org?tag=login"), "login", true);
Expand All @@ -116,16 +107,15 @@ public async Task CanUseLNURLAUTH()
var k1 = Encoders.Hex.EncodeData(RandomUtils.GetBytes(32));
LNURL.EncodeUri(new Uri($"https://kukks.org?tag=login&k1={k1}&action=xyz"), "login", true);
});

var k1 = Encoders.Hex.EncodeData(RandomUtils.GetBytes(32));
var lnurl = LNURL.EncodeUri(new Uri($"https://kukks.org?tag=login&k1={k1}"), "login", true);

var request = Assert.IsType<LNAuthRequest>(await LNURL.FetchInformation(lnurl, null));
var k1 = Encoders.Hex.EncodeData(RandomUtils.GetBytes(32));
var lnurl = LNURL.EncodeUri(new Uri($"https://kukks.org?tag=login&k1={k1}"), "login", true);

var linkingKey = new Key();
var sig = request.SignChallenge(linkingKey);
Assert.True( LNAuthRequest.VerifyChallenge(sig, linkingKey.PubKey, Encoders.Hex.DecodeData(k1)));
var request = Assert.IsType<LNAuthRequest>(await LNURL.FetchInformation(lnurl, null));

var linkingKey = new Key();
var sig = request.SignChallenge(linkingKey);
Assert.True(LNAuthRequest.VerifyChallenge(sig, linkingKey.PubKey, Encoders.Hex.DecodeData(k1)));
}

[Fact]
Expand All @@ -151,37 +141,139 @@ public async Task payerDataSerializerTest()
}
};

req = JsonConvert.DeserializeObject<LNURLPayRequest>(JsonConvert.SerializeObject(req));
Assert.NotNull(req.PayerData);
Assert.True(req.PayerData.Pubkey.Mandatory);
Assert.False(req.PayerData.Name.Mandatory);
Assert.False(req.PayerData.Auth.Mandatory);
Assert.Null(req.PayerData.Email);

var k = new Key();

var resp = new LNURLPayRequest.LUD18PayerDataResponse()
{
Auth = new LNURLPayRequest.LUD18AuthPayerDataResponse()
{
K1 = req.PayerData.Auth.K1,
Key = k.PubKey,
Sig = k.Sign(new uint256(Encoders.Hex.DecodeData(req.PayerData.Auth.K1)))
},
};

resp = JsonConvert.DeserializeObject<LNURLPayRequest.LUD18PayerDataResponse>(JsonConvert.SerializeObject(resp));
Assert.False(req.VerifyPayerData(resp));
resp.Pubkey = k.PubKey;
resp = JsonConvert.DeserializeObject<LNURLPayRequest.LUD18PayerDataResponse>(JsonConvert.SerializeObject(resp));
Assert.True(req.VerifyPayerData(resp));
resp.Email = "[email protected]";
Assert.False(req.VerifyPayerData(resp));

resp.Email = null;
resp.Name = "sdasds";
Assert.True(req.VerifyPayerData(resp));
req = JsonConvert.DeserializeObject<LNURLPayRequest>(JsonConvert.SerializeObject(req));
Assert.NotNull(req.PayerData);
Assert.True(req.PayerData.Pubkey.Mandatory);
Assert.False(req.PayerData.Name.Mandatory);
Assert.False(req.PayerData.Auth.Mandatory);
Assert.Null(req.PayerData.Email);

var k = new Key();

var resp = new LNURLPayRequest.LUD18PayerDataResponse()
{
Auth = new LNURLPayRequest.LUD18AuthPayerDataResponse()
{
K1 = req.PayerData.Auth.K1,
Key = k.PubKey,
Sig = k.Sign(new uint256(Encoders.Hex.DecodeData(req.PayerData.Auth.K1)))
},
};

resp = JsonConvert.DeserializeObject<LNURLPayRequest.LUD18PayerDataResponse>(
JsonConvert.SerializeObject(resp));
Assert.False(req.VerifyPayerData(resp));
resp.Pubkey = k.PubKey;
resp = JsonConvert.DeserializeObject<LNURLPayRequest.LUD18PayerDataResponse>(
JsonConvert.SerializeObject(resp));
Assert.True(req.VerifyPayerData(resp));
resp.Email = "[email protected]";
Assert.False(req.VerifyPayerData(resp));

resp.Email = null;
resp.Name = "sdasds";
Assert.True(req.VerifyPayerData(resp));
}

[Fact]
public async Task CanUseBoltCardHelper()

Check warning on line 179 in LNURL.Tests/UnitTest1.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
var key = Convert.FromHexString("0c3b25d92b38ae443229dd59ad34b85d");
var cmacKey = Convert.FromHexString("b45775776cb224c75bcde7ca3704e933");
var result = BoltCardHelper.ExtractBoltCardFromRequest(
new Uri("https://test.com?p=4E2E289D945A66BB13377A728884E867&c=E19CCB1FED8892CE"),
key, out var error);

Assert.Null(error);
Assert.NotNull(result);
Assert.Equal((uint) 3, result.Value.counter);
Assert.Equal("04996c6a926980", result.Value.uid);
Assert.True(BoltCardHelper.CheckCmac(result.Value.rawUid, result.Value.rawCtr, cmacKey, result.Value.c,
out error));

var manualP = BoltCardHelper.CreatePValue(key, result.Value.counter, result.Value.uid);
var manualPResult = BoltCardHelper.ExtractUidAndCounterFromP(manualP, key, out error);
Assert.Null(error);
Assert.NotNull(manualPResult);
Assert.Equal((uint) 3, manualPResult.Value.counter);
Assert.Equal("04996c6a926980", manualPResult.Value.uid);

var manualC = BoltCardHelper.CreateCValue(result.Value.rawUid, result.Value.rawCtr, cmacKey);
Assert.Equal(result.Value.c, manualC);
}

[Fact]
public async Task DeterministicCards()

Check warning on line 206 in LNURL.Tests/UnitTest1.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
var masterSeed = RandomUtils.GetBytes(64);
var masterSeedSlip21 = Slip21Node.FromSeed(masterSeed);

var i = Random.Shared.Next(0, 10000);
var k1 = masterSeedSlip21.DeriveChild(i + "k1").Key.ToBytes().Take(16).ToArray();
var k2 = masterSeedSlip21.DeriveChild(i + "k2").Key.ToBytes().Take(16).ToArray();

var counter = (uint) Random.Shared.Next(0, 1000);
var uid = Convert.ToHexString(RandomUtils.GetBytes(7));
var pParam = Convert.ToHexString(BoltCardHelper.CreatePValue(k1, counter, uid));
var cParam = Convert.ToHexString(BoltCardHelper.CreateCValue(uid, counter, k2));
var lnurlw = $"https://test.com?p={pParam}&c={cParam}";

var result = BoltCardHelper.ExtractBoltCardFromRequest(new Uri(lnurlw), k1, out var error);
Assert.Null(error);
Assert.NotNull(result);
Assert.Equal(uid.ToLowerInvariant(), result.Value.uid.ToLowerInvariant());
Assert.Equal(counter, result.Value.counter);
Assert.True(BoltCardHelper.CheckCmac(result.Value.rawUid, result.Value.rawCtr, k2, result.Value.c,
out error));
Assert.Null(error);


for (int j = 0; j <= 10000; j++)
{
var brutek1 = masterSeedSlip21.DeriveChild(j + "k1").Key.ToBytes().Take(16).ToArray();
var brutek2 = masterSeedSlip21.DeriveChild(j + "k2").Key.ToBytes().Take(16).ToArray();
try
{
var bruteResult = BoltCardHelper.ExtractBoltCardFromRequest(new Uri(lnurlw), brutek1, out error);
Assert.Null(error);
Assert.NotNull(bruteResult);
Assert.Equal(uid.ToLowerInvariant(), bruteResult.Value.uid.ToLowerInvariant());
Assert.Equal(counter, bruteResult.Value.counter);
Assert.True(BoltCardHelper.CheckCmac(bruteResult.Value.rawUid, bruteResult.Value.rawCtr, brutek2,
bruteResult.Value.c, out error));
Assert.Null(error);

break;
}
catch (Exception e)

Check warning on line 248 in LNURL.Tests/UnitTest1.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'e' is declared but never used
{
}
}
}
//from https://github.com/boltcard/boltcard/blob/7745c9f20d5ad0129cb4b3fc534441038e79f5e6/docs/TEST_VECTORS.md
[Theory]
[InlineData("4E2E289D945A66BB13377A728884E867", "E19CCB1FED8892CE", "04996c6a926980", 3)]
[InlineData("00F48C4F8E386DED06BCDC78FA92E2FE", "66B4826EA4C155B4", "04996c6a926980", 5)]
[InlineData("0DBF3C59B59B0638D60B5842A997D4D1", "CC61660C020B4D96", "04996c6a926980", 7)]
public void TestDecryptAndValidate(string pValueHex, string cValueHex, string expectedUidHex, uint expectedCtr)
{

var aesDecryptKey = Convert.FromHexString("0c3b25d92b38ae443229dd59ad34b85d");
var aesCmacKey = Convert.FromHexString("b45775776cb224c75bcde7ca3704e933");
byte[] pValue = Convert.FromHexString(pValueHex);
byte[] cValue = Convert.FromHexString(cValueHex);

// Decrypt p value
var res = BoltCardHelper.ExtractUidAndCounterFromP(pValue, aesDecryptKey, out _);

// Check UID and counter
Assert.Equal(expectedUidHex, res.Value.uid);
Assert.Equal(expectedCtr, res.Value.counter);

// Validate CMAC
var cmacIsValid = BoltCardHelper.CheckCmac(res.Value.rawUid, res.Value.rawCtr, aesCmacKey, cValue, out _);
Assert.True(cmacIsValid, "CMAC validation failed");
}

}
}
}
Loading

0 comments on commit 3f08523

Please sign in to comment.