Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

Commit

Permalink
[3.0.8.0] Added ledger support for claim tool (#4220)
Browse files Browse the repository at this point in the history
* Added ledger support

* Fixed block explorer

* Added CL args

* Added a flag to ignore balance check

* Fixed explorer test

* Minor changes as per feedback

* Minor changes as per feedback

* Added global.json to lock the version

* DOwngraded to 2.1

* Removed unused tests

Co-authored-by: Igor Goldobin <[email protected]>
  • Loading branch information
fenix2222 and fenix2222 authored Feb 16, 2021
1 parent b8e53fc commit c4d5176
Show file tree
Hide file tree
Showing 42 changed files with 2,988 additions and 6 deletions.
23 changes: 22 additions & 1 deletion src/AddressOwnershipTool/AddressOwnershipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Flurl;
using Flurl.Http;
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using Stratis.Bitcoin.Configuration;
using Stratis.Bitcoin.Consensus;
Expand Down Expand Up @@ -113,6 +114,8 @@ public void Validate(string sigFileFolder)
if (!file.EndsWith(".csv"))
continue;

var isLedger = Path.GetFileName(file).StartsWith("L-");

Console.WriteLine($"Validating signature file '{file}'...");

foreach (string line in File.ReadLines(file))
Expand Down Expand Up @@ -146,7 +149,7 @@ public void Validate(string sigFileFolder)
}

// The address string is the actual message in this case.
var pubKey = PubKey.RecoverFromMessage(address, signature);
var pubKey = isLedger ? RecoverLedgerPubKey(address, signature) : PubKey.RecoverFromMessage(address, signature);

if (pubKey.Hash.ScriptPubKey.GetDestinationAddress(this.network).ToString() != address)
{
Expand Down Expand Up @@ -201,6 +204,24 @@ public void Validate(string sigFileFolder)
Console.WriteLine($"There are {Money.Satoshis(this.ownershipTransactions.Sum(s => s.SenderAmount)).ToUnit(MoneyUnit.BTC)} STRAT with ownership proved.");
}

private PubKey RecoverLedgerPubKey(string address, string signature)
{
var specialBytes = 0x18;
var prefixBytes = Encoding.UTF8.GetBytes("Stratis Signed Message:\n");
var lengthBytes = BitConverter.GetBytes((char)address.Length).Take(1).ToArray();
var addressBytes = Encoding.UTF8.GetBytes(address);

byte[] dataBytes = new byte[1 + prefixBytes.Length + lengthBytes.Length + addressBytes.Length];
dataBytes[0] = (byte)specialBytes;
Buffer.BlockCopy(prefixBytes, 0, dataBytes, 1, prefixBytes.Length);
Buffer.BlockCopy(lengthBytes, 0, dataBytes, prefixBytes.Length + 1, lengthBytes.Length);
Buffer.BlockCopy(addressBytes, 0, dataBytes, prefixBytes.Length + lengthBytes.Length + 1, addressBytes.Length);

uint256 messageHash = NBitcoin.Crypto.Hashes.Hash256(dataBytes);
var recovered = PubKey.RecoverCompact(messageHash, Encoders.Base64.DecodeData(signature));
return recovered;
}

public void StratisXExport(string privKeyFile, string destinationAddress)
{
var lines = File.ReadLines(privKeyFile);
Expand Down
7 changes: 6 additions & 1 deletion src/AddressOwnershipTool/AddressOwnershipTool.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -12,10 +12,15 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LedgerWallet\LedgerWallet.csproj" />
<ProjectReference Include="..\Stratis.Bitcoin.Features.Wallet\Stratis.Bitcoin.Features.Wallet.csproj" />
<ProjectReference Include="..\Stratis.Bitcoin.Networks\Stratis.Bitcoin.Networks.csproj" />
<ProjectReference Include="..\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
<ProjectReference Include="..\Stratis.Features.SQLiteWalletRepository\Stratis.Features.SQLiteWalletRepository.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions src/AddressOwnershipTool/BlockExplorerClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace AddressOwnershipTool
{
public class BlockExplorerClient
{
private const string ExplorerBaseUrl = "https://stratissnapshotapi.stratisplatform.com/";

public bool HasBalance(string address)
{
var stratisApiClient = new NodeApiClient($"{ExplorerBaseUrl}api");
var balance = stratisApiClient.GetAddressBalance(address);

return balance > 0;
}
}
}
138 changes: 138 additions & 0 deletions src/AddressOwnershipTool/LedgerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LedgerWallet;
using NBitcoin;
using NBitcoin.DataEncoders;
using Stratis.Bitcoin.Networks;

namespace AddressOwnershipTool
{
public class LedgerService
{
private readonly Network network;

private readonly BlockExplorerClient blockExplorerClient;

public LedgerService(bool testnet)
{
this.network = testnet ? new StratisTest() : new StratisMain();
this.blockExplorerClient = new BlockExplorerClient();
}

public async Task ExportAddressesAsync(int numberOfAddressesToScan, string destinationAddress, bool ignoreBalance)
{
LedgerClient ledger = (await LedgerClient.GetHIDLedgersAsync()).First();

// m / purpose' / coin_type' / account' / change / address_index
for (int index = 0; index < numberOfAddressesToScan; index++)
{
var key = new KeyPath($"m/44'/105'/0'/0/{index}");

GetWalletPubKeyResponse walletPubKey = await ledger.GetWalletPubKeyAsync(key);

PubKey pubKey = walletPubKey.ExtendedPublicKey.PubKey;
var address = walletPubKey.Address;

if (!ignoreBalance)
{
Console.WriteLine($"Checking balance for {address}");
if (!this.blockExplorerClient.HasBalance(address))
continue;
}

await ledger.PrepareMessage(key, address);
var resp = await ledger.SignMessage();

var signature = GetSignature(address, resp, pubKey);
if (signature == null)
continue;

this.OutputToFile(address, destinationAddress, signature);
}
}

public void OutputToFile(string address, string destinationAddress, string signature)
{
string export = $"{address};{destinationAddress};{signature}";

Console.WriteLine(export);

using (StreamWriter sw = File.AppendText($"L-{destinationAddress}.csv"))
{
sw.WriteLine(export);
}
}

private string GetSignature(string address, byte[] resp, PubKey pubKey)
{
// Convert the ASN.1 signature into the proper format for NBitcoin/NStratis to validate in the ownership tool
// This is a very quick and nasty conversion that doesn't use much of the internal BC functionality
// 31 - SET
// <LEN>
// 02 - INTEGER (R)
// <LEN>
// 02 - INTEGER (S)
for (int i = 0; i < 2; i++)
{
int recId = i;

if (resp[0] != (byte)(48 + recId))
continue; //throw new Exception("Unexpected signature encoding - outer type not SET");

if (resp[2] != 0x02)
throw new Exception("Invalid signature encoding - type not integer");

int rLength = resp[3];

var rBytes = new byte[32];
Array.Resize(ref rBytes, rLength); // can be 33
Array.Copy(resp, 4, rBytes, 0, rLength);

if (resp[4 + rLength] != 0x02)
throw new Exception("Invalid signature encoding - type not integer");

int sLength = resp[5 + rLength];

var sBytes = new byte[32];
Array.Copy(resp, 6 + rLength, sBytes, 0, sLength);

// Now we have to work backwards to figure out the recId needed to recover the signature.

int headerByte = recId + 27 + (pubKey.IsCompressed ? 4 : 0);

var sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S

sigData[0] = (byte)headerByte;

Array.Copy(rBytes, rLength == 33 ? 1 : 0, sigData, 1, 32);
Array.Copy(sBytes, sLength == 33 ? 1 : 0, sigData, 33, 32);

var specialBytes = 0x18;
var prefixBytes = Encoding.UTF8.GetBytes("Stratis Signed Message:\n");
var lengthBytes = BitConverter.GetBytes((char)address.Length).Take(1).ToArray();
var addressBytes = Encoding.UTF8.GetBytes(address);

byte[] dataBytes = new byte[1 + prefixBytes.Length + lengthBytes.Length + addressBytes.Length];
dataBytes[0] = (byte)specialBytes;
Buffer.BlockCopy(prefixBytes, 0, dataBytes, 1, prefixBytes.Length);
Buffer.BlockCopy(lengthBytes, 0, dataBytes, prefixBytes.Length + 1, lengthBytes.Length);
Buffer.BlockCopy(addressBytes, 0, dataBytes, prefixBytes.Length + lengthBytes.Length + 1, addressBytes.Length);

uint256 messageHash = NBitcoin.Crypto.Hashes.Hash256(dataBytes);
var recovered = PubKey.RecoverCompact(messageHash, sigData);
var recoveredAddress = recovered.Hash.ScriptPubKey.GetDestinationAddress(this.network).ToString();
bool foundMatch = recoveredAddress == address;

if (foundMatch)
return Encoders.Base64.EncodeData(sigData);
}

Console.WriteLine($"Failed to validate signature for address {address}");

return null;
}
}
}
53 changes: 50 additions & 3 deletions src/AddressOwnershipTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NLog.Targets;

namespace AddressOwnershipTool
{
partial class Program
{
static void Main(string[] args)
static async Task Main(string[] args)
{
string arg = null;
bool validate;
bool distribute;
bool testnet;
bool ledger;
bool ignoreBalance;
string destinationAddress = null;

// Settings common between all modes
testnet = args.Contains("-testnet");
ledger = args.Contains("-ledger");
ignoreBalance = args.Contains("-ignorebalance");

validate = args.Contains("-validate");
if (validate)
Expand Down Expand Up @@ -92,14 +97,56 @@ static void Main(string[] args)

Console.WriteLine("Address;Destination;Signature");

if (!File.Exists(destinationAddress + ".csv"))
var fileName = $"{(ledger ? "L-" : string.Empty)}{destinationAddress}.csv";

if (!File.Exists(fileName))
{
using (StreamWriter sw = File.AppendText(destinationAddress + ".csv"))
using (StreamWriter sw = File.AppendText(fileName))
{
sw.WriteLine("Address;Destination;Signature");
}
}

if (ledger)
{
var numberOfAddressesToScan = 100;

arg = args.FirstOrDefault(a => a.StartsWith("-addresscount"));
if (arg != null)
{
var numberOfAddressesToScanSetting = arg.Split('=')[1];
if (!int.TryParse(numberOfAddressesToScanSetting, out numberOfAddressesToScan))
{
Console.WriteLine($"Unable to parse '-addresscount' setting with a value of {numberOfAddressesToScanSetting}. Please use whole number.");

return;
}
}

var ledgerService = new LedgerService(testnet);

try
{
await ledgerService.ExportAddressesAsync(numberOfAddressesToScan, destinationAddress, ignoreBalance);
}
catch (InvalidOperationException)
{
Console.WriteLine("Make sure your ledger device is unlocked and Stratis wallet is open.");
}
catch (LedgerWallet.LedgerWalletException)
{
Console.WriteLine("Make sure your ledger device is unlocked and Stratis wallet is open.");
}
catch (ApplicationException ex)
{
Console.WriteLine(ex.Message);
}

Console.WriteLine("Finished");

return;
}

// Settings related to a stratisX wallet export.
string privKeyFile = null;

Expand Down
27 changes: 27 additions & 0 deletions src/LedgerWallet/Bip32EncodedKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NBitcoin.DataEncoders;
using System;
using System.Linq;

namespace LedgerWallet
{
public class Bip32EncodedKey
{
readonly byte[] _Key;
public Bip32EncodedKey(byte[] bytes)
{
if(bytes == null)
throw new ArgumentNullException("bytes");
_Key = bytes.ToArray();
}

public byte[] ToBytes()
{
return _Key.ToArray();
}

public string ToHex()
{
return Encoders.Hex.EncodeData(_Key);
}
}
}
29 changes: 29 additions & 0 deletions src/LedgerWallet/BufferUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NBitcoin;

namespace LedgerWallet
{
class BufferUtils
{
internal static void WriteUint32BE(System.IO.Stream data, long index)
{
var bytes = Utils.ToBytes((uint)index, false);
data.Write(bytes, 0, bytes.Length);
}

internal static void WriteBuffer(System.IO.Stream data, byte[] buffer)
{
data.Write(buffer, 0, buffer.Length);
}

internal static void WriteBuffer(System.IO.Stream data, IBitcoinSerializable serializable)
{
WriteBuffer(data, serializable.ToBytes());
}

internal static void WriteBuffer(System.IO.Stream data, uint value)
{
var bytes = Utils.ToBytes(value, true);
data.Write(bytes, 0, bytes.Length);
}
}
}
Loading

0 comments on commit c4d5176

Please sign in to comment.