Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

suggestion for address type #11

Merged
merged 13 commits into from
Jul 29, 2021
99 changes: 95 additions & 4 deletions CardanoSharp.Wallet.Test/AddressTests.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
using System;
using CardanoSharp.Wallet.Encoding;
using CardanoSharp.Wallet.Enums;
using CardanoSharp.Wallet.Models.Addresses;
using CardanoSharp.Wallet.Extensions.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System.Buffers.Text;

namespace CardanoSharp.Wallet.Test
{
public class AddressTests
{
private readonly IAddressService _addressService;
private readonly IKeyService _keyService;
const string __mnemonic = "art forum devote street sure rather head chuckle guard poverty release quote oak craft enemy";

public AddressTests()
{
_addressService = new AddressService();
_keyService = new KeyService();
}

[Theory]
//Delegation Addresses
//Delegation Addresses
[InlineData("addr_test", "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp")]
[InlineData("addr", "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwqfjkjv7")]
[InlineData("addr_test", "addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w")]
Expand All @@ -36,10 +44,93 @@ public AddressTests()
[InlineData("stake", "stake1uyevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqxdekzz")]
public void EncodeDecodeTest(string prefix, string addr)
{
var addrByte = _addressService.GetAddressBytes(addr);
var addr2 = _addressService.GetAddress(addrByte, prefix);
var addrByte = Bech32.Decode(addr, out _, out _);
var addr2 = Bech32.Encode(addrByte, prefix);

Assert.Equal(addr, addr2);
}

//28
//addr1q8d9pcrn38veygv638ftw0f82gm4h6rmrs599pkr3qfxx073eyjrmj0hnx6xz8emx03l6hszjclm8fmnlaewe4adp7dqsd8pa6
//28
[Theory]
[InlineData("addr", "addr1q8d9pcrn38veygv638ftw0f82gm4h6rmrs599pkr3qfxx073eyjrmj0hnx6xz8emx03l6hszjclm8fmnlaewe4adp7dqsd8pa6")]
[InlineData("addr_test", "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp")]
public void FromStringTest(string prefix, string addr)
{
var addrByte = Bech32.Decode(addr, out _, out _);
var address = new Address(addr);
var hex = address.ToStringHex();

Assert.Equal(addrByte, address.GetBytes());
Assert.Equal(prefix, address.Prefix);
Assert.Equal(prefix == "addr" ? NetworkType.Mainnet : NetworkType.Testnet, address.NetworkType);
Assert.Equal(AddressType.Base, address.AddressType);

}

/// <summary>
/// Verifies components of adresses
/// Illustrating the fact that addresses generated with paymentPub & stakePub have multiple parts,
/// that addresses consist of "header part", "payment address part" and "reward address part"
/// that "payment address part" for different paths (CIP1852) differ
/// that "reward address part" for different paths are equal
///
/// inspired by Andrew Westberg (NerdOut) Addresses Video
/// https://www.youtube.com/watch?v=NjPf_b9UQNs&t=396)
///
/// 0 0 79467c69a9ac66280174d09d62575ba955748b21dec3b483a9469a65 cc339a35f9e0fe039cf510c761d4dd29040c48e9657fdac7e9c01d94
/// 0 0 1fd57d18565e3a17cd194f190d349c2b7309eaf70f3f3bf884b0eada cc339a35f9e0fe039cf510c761d4dd29040c48e9657fdac7e9c01d94
/// 0 0 f36b29ceede650f850ee705a3a89ec041e24397d1a0d803d6af7e3f2 cc339a35f9e0fe039cf510c761d4dd29040c48e9657fdac7e9c01d94
/// ╎ ╎ ╎ ╎
/// ╎ ╎ ╰╌ Payment Address ╰╌ Reward Address
/// ╎ ╰╌╌╌ NetworkType 0 = Testnet
/// ╰╌╌╌╌╌ AddressType 0 = Base
/// </summary>
[Theory]
[InlineData(__mnemonic, "cc339a35f9e0fe039cf510c761d4dd29040c48e9657fdac7e9c01d94")]
public void VerifyRewardAddress(string mnemonic, string stakingAddr)
{
// create two payment addresses from same root key
//arrange
var entropy = _keyService.Restore(mnemonic);
var rootKey = _keyService.GetRootKey(entropy);

////get payment keys
(var paymentPrv1, var paymentPub1) = getKeyPairFromPath("m/1852'/1815'/0'/0/0", rootKey);
(var paymentPrv2, var paymentPub2) = getKeyPairFromPath("m/1852'/1815'/0'/0/1", rootKey);
(var paymentPrv3, var paymentPub3) = getKeyPairFromPath("m/1852'/1815'/0'/0/2", rootKey);

////get stake keys
(var stakePrv, var stakePub) = getKeyPairFromPath("m/1852'/1815'/0'/2/0", rootKey);

var baseAddr1 = _addressService.GetAddress(paymentPub1, stakePub, NetworkType.Testnet, AddressType.Base);
var baseAddr2 = _addressService.GetAddress(paymentPub2, stakePub, NetworkType.Testnet, AddressType.Base);
var baseAddr3 = _addressService.GetAddress(paymentPub3, stakePub, NetworkType.Testnet, AddressType.Base);

//act
var hex1 = baseAddr1.ToStringHex();
var hex2 = baseAddr2.ToStringHex();
var hex3 = baseAddr3.ToStringHex();

// assert
Assert.EndsWith(stakingAddr, hex1);
Assert.EndsWith(stakingAddr, hex2);
Assert.EndsWith(stakingAddr, hex3);
}

/// <summary>
/// Getting the key from path as descibed in https://github.com/cardano-foundation/CIPs/blob/master/CIP-1852/CIP-1852.md
/// </summary>
/// <param name="path"></param>
/// <param name="rootKey"></param>
/// <returns></returns>
private (byte[], byte[]) getKeyPairFromPath(string path, (byte[], byte[]) rootKey)
{
var privateKey = _keyService.DerivePath(path, rootKey.Item1, rootKey.Item2);
var publicKey = _keyService.GetPublicKey(privateKey.Item1, false);
return (privateKey.Item1, publicKey);
}

}
}
106 changes: 106 additions & 0 deletions CardanoSharp.Wallet.Test/Bech32Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using CardanoSharp.Wallet.Encoding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using CardanoSharp.Wallet.Extensions;

namespace CardanoSharp.Wallet.Test
{
/// <summary>
/// Bech32 Test vectors from BIP-0173
/// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#test-vectors
/// </summary>
public class Bech32Tests
tweakch marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
///
/// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#test-vectors
/// </summary>
/// <param name="addr"></param>
/// <param name="expectedHex"></param>
/// <param name="expectedVer"></param>
/// <param name="expectedHrp"></param>
[Theory]
//@TODO Fails [InlineData("A12UEL5L", "", 0x00, "")] //@TODO verify
//@TODO Fails [InlineData("a12uel5l", "", 0x00, "")] //@TODO verify
//@TODO Fails [InlineData("an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", "", 0x00, "")]
[InlineData("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", "00443214c74254b635cf84653a56d7c675be77df", 0x00, "abcdef")]
[InlineData("11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 0x00, "1")]
//@TODO Fails [InlineData("split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", "c5f38b70305f5fb6cf03058f3dde463ecd7918f2dc743918f2d", 0x00, "split")]
//@TODO Fails [InlineData("?1ezyfcl", "", 0x00, "")]
//@TODO Fails [InlineData("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6", 0x00, "BC")]
//@TODO Fails [InlineData("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", 0x00, "tb")]
//@TODO Fails [InlineData("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", 0x00, "tb")]
//@TODO Fails [InlineData("BC1SW50QA3JX3S", "6002751e", 0x00, "tb")]
//@TODO Fails [InlineData("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323", 0x00, "tb")]
//@TODO Fails [InlineData("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", 0x00, "tb")]
public void TestValidInputs(string addr, string expectedHex, byte expectedVer, string expectedHrp)
{
// Act
var decoded = Bech32.Decode(addr, out var actualVer, out var actualHrp);
var actualHex = decoded.ToStringHex();

// Assert
Assert.Equal(expectedVer, actualVer);
Assert.Equal(expectedHrp, actualHrp);
Assert.Equal(expectedHex, actualHex);
}

/// <summary>
///
/// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#test-vectors
/// </summary>
/// <param name="addr"></param>
/// <param name="expectedError"></param>
[Theory]
[InlineData("1nwldj5", "HRP character out of range")]
[InlineData("1axkwrx", "HRP character out of range")]
[InlineData("1eym55h", "HRP character out of range")]
//@TODO Fails [InlineData("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", "overall max length exceeded")]
[InlineData("pzry9x0s0muk", "no separator")]
[InlineData("1pzry9x0s0muk", "empty hrp")]
[InlineData("x1b4n0q5v", "Invalid data format.")]
[InlineData("li1dgmt3", "too short checksum")]
[InlineData("de1lg7wt", "Invalid character in checksum.")]
[InlineData("A1G7SGD8", "Invalid checksum.")]
[InlineData("A1G7SGD8", "empty HRP")]
[InlineData("1qzzfhee", "empty HRP")]
public void TestInvalidInputs(string addr, string expectedError)
{
// arrange
if(addr == "delig7wt")
{
addr += 0xff;
}
if (addr == "1nwldj5")
{
addr = 0x20 + addr;
}
if (addr == "1axkwrx")
{
addr = 0x7f + addr;
}
if (addr == "1eym55h")
{
addr = 0x80 + addr;
}

bool raised = false;
try
{
var decoded = Bech32.Decode(addr, out var actualVer, out var actualHrp);
var hex = decoded.ToStringHex();
}
catch (Exception ex)
{
raised = true;
Console.Error.WriteLine(ex);
}

Assert.True(raised);
}
}
}
76 changes: 76 additions & 0 deletions CardanoSharp.Wallet.Test/MiscTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace CardanoSharp.Wallet.Test
{
public class MiscTests
{
/// <summary>
/// Check if 1111 0111 1011 1101
/// </summary>
/// <param name="data"></param>
/// <param name="result"></param>
[Theory]
[InlineData(new byte[] { 0xf7, 0xbd }, new byte[] { 0x1e, 0x1e, 0x1e, 0x10 })]
[InlineData(new byte[] { 0xf7, 0xbd, 0xe0 }, new byte[] { 0x1e, 0x1e, 0x1e, 0x1e, 0x0 })]
[InlineData(new byte[] { 0xf7, 0xbd, 0xef, 0x7b, 0xde }, new byte[] { 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e })]
public void TestConvert(byte[] data, byte[] result)
{
//arrange
int fromBits = 8;
int toBits = 5;
var view = data.Select(b => Convert.ToString(b, 2));
//act
var convert = ConvertBitsFast(data, fromBits, toBits, true);

//assert
Assert.Equal(result, convert);
}

static byte[] ConvertBitsFast(ReadOnlySpan<byte> data, int fromBits, int toBits, bool pad = true)
{
// TODO: Optimize Looping
// We can use a method similar to BIP39 here to avoid the nested loop, usage of List, increase the speed,
// and shorten this function to 3 lines.
// Or convert to ulong[], loop through it (3 times) take 5 bits at a time or 8 bits at a time...
int acc = 0;
int bits = 0;
int maxv = (1 << toBits) - 1;
int maxacc = (1 << (fromBits + toBits - 1)) - 1;

List<byte> result = new List<byte>();
foreach (var b in data)
{
// Speed doesn't matter for this class but we can skip this check for 8 to 5 conversion.
if ((b >> fromBits) > 0)
{
return null;
}
acc = ((acc << fromBits) | b) & maxacc;
bits += fromBits;
while (bits >= toBits)
{
bits -= toBits;
result.Add((byte)((acc >> bits) & maxv));
}
}
if (pad)
{
if (bits > 0)
{
result.Add((byte)((acc << (toBits - bits)) & maxv));
}
}
else if (bits >= fromBits || (byte)((acc << (toBits - bits)) & maxv) != 0)
{
return null;
}
return result.ToArray();

}
}
}
12 changes: 6 additions & 6 deletions CardanoSharp.Wallet.Test/TransactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ public void BuildTxWithChange()
{
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(baseAddr),
Address = baseAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 10
}
},
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(changeAddr),
Address = changeAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 856488
Expand Down Expand Up @@ -118,7 +118,7 @@ public void MnemonicToTransaction()
{
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(baseAddr),
Address = baseAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 1
Expand Down Expand Up @@ -186,7 +186,7 @@ public void CertificateTest()
{
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(changeAddr),
Address = changeAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 3786498
Expand Down Expand Up @@ -250,7 +250,7 @@ public void MultiAssetTest()
{
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(baseAddr),
Address = baseAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 1,
Expand All @@ -271,7 +271,7 @@ public void MultiAssetTest()
},
new TransactionOutput()
{
Address = _addressService.GetAddressBytes(changeAddr),
Address = changeAddr.GetBytes(),
Value = new TransactionOutputValue()
{
Coin = 18,
Expand Down
7 changes: 7 additions & 0 deletions CardanoSharp.Wallet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CardanoSharp.Wallet", "Card
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CardanoSharp.Wallet.Test", "CardanoSharp.Wallet.Test\CardanoSharp.Wallet.Test.csproj", "{79243311-E3E2-4731-BB89-663B720C5576}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4010CE2-B269-4DC7-816A-5BA4CD18D414}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
appveyor.yml = appveyor.yml
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
Loading