Skip to content

Commit

Permalink
Add Ethereum address check to InterFluxOpReturnEncoder (stratisprojec…
Browse files Browse the repository at this point in the history
  • Loading branch information
fassadlr committed Jul 6, 2021
1 parent 0ab72b2 commit abd6144
Show file tree
Hide file tree
Showing 15 changed files with 89 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NBitcoin;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.P2P.Protocol.Payloads;

namespace Stratis.Bitcoin.Features.Interop.Payloads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public void CanEncodeAndDecode()
int chain = 3;
string address = "0x51EC92A3aB8cfcA412Ea43766A9259523fC81501";

string encoded = InterFluxOpReturnEncoder.Encode(chain, address);
string encoded = InterFluxOpReturnEncoder.Encode((DestinationChain)chain, address);

bool success = InterFluxOpReturnEncoder.TryDecode(encoded, out int resultChain, out string resultAddress);

Expand All @@ -19,13 +19,26 @@ public void CanEncodeAndDecode()
Assert.Equal(address, resultAddress);
}

[Fact]
public void EncodeAndDecodeETHAddress()
{
string address = "0xd2390da742872294BE05dc7359D7249d7C79460E";
string encoded = InterFluxOpReturnEncoder.Encode(DestinationChain.ETH, address);
bool success = InterFluxOpReturnEncoder.TryDecode(encoded, out int resultChain, out string resultAddress);

Assert.True(success);
Assert.Equal(DestinationChain.ETH, (DestinationChain)resultChain);
Assert.Equal(address, resultAddress);
}

[Fact]
public void DecodeDoesntThrowWhenFormatIsIncorrect()
{
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER_3_345345", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER3_", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTERefsdvsdvdsvsdv", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("xvev456545cwsdfFSXVB365", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER1_aaaa", out int _, out string _));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ public async Task<IActionResult> GetHistoryAsync([FromQuery] WalletHistoryReques
public async Task<IActionResult> BuildInterFluxTransaction([FromBody] BuildInterFluxTransactionRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
request.OpReturnData = InterFluxOpReturnEncoder.Encode(request.DestinationChain, request.DestinationAddress);
request.OpReturnData = InterFluxOpReturnEncoder.Encode((DestinationChain)request.DestinationChain, request.DestinationAddress);

return await this.Execute(request, cancellationToken,
async (req, token) => Json(await this.walletService.BuildTransaction(req, token)));
Expand Down
11 changes: 9 additions & 2 deletions src/Stratis.Bitcoin.Features.Wallet/DepositValidationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,17 @@ public static bool TryGetTarget(Transaction transaction, IOpReturnDataReader opR
conversion = false;
targetChain = 0 /* DestinationChain.STRAX */;

// Check the common case first.
// First check cross chain transfers from the STRAX to Cirrus network or vice versa.
if (!opReturnDataReader.TryGetTargetAddress(transaction, out targetAddress))
{
if (!opReturnDataReader.TryGetTargetETHAddress(transaction, out targetAddress))
// Else try and validate the destination adress by the destination chain.
byte[] opReturnBytes = OpReturnDataReader.SelectBytesContentFromOpReturn(transaction).FirstOrDefault();

if (opReturnBytes != null && InterFluxOpReturnEncoder.TryDecode(opReturnBytes, out int destinationChain, out targetAddress))
{
targetChain = destinationChain;
}
else
return false;

conversion = true;
Expand Down
54 changes: 48 additions & 6 deletions src/Stratis.Bitcoin.Features.Wallet/InterFluxOpReturnEncoder.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
namespace Stratis.Bitcoin.Features.Wallet
using System.Text;
using System.Text.RegularExpressions;

namespace Stratis.Bitcoin.Features.Wallet
{
/// <summary>Encodes or decodes InterFlux related data to and from OP_RETURN data.</summary>
public static class InterFluxOpReturnEncoder
{
private static string InterFluxPrefix = "INTER";

public static string Encode(int destinationChain, string address)
public static string Encode(DestinationChain destinationChain, string address)
{
return InterFluxPrefix + destinationChain + "_" + address;
return InterFluxPrefix + (int)destinationChain + "_" + address;
}

public static bool TryDecode(string opReturnData, out int destinationChain, out string address)
Expand All @@ -21,15 +24,54 @@ public static bool TryDecode(string opReturnData, out int destinationChain, out
if (prefixIndex == -1 || separatorIndex == -1)
return false;

// Try and extract the destination chain.
if (!int.TryParse(opReturnData.Substring(InterFluxPrefix.Length, separatorIndex - InterFluxPrefix.Length), out destinationChain))
return false;

address = opReturnData.Substring(separatorIndex + 1);

if (string.IsNullOrEmpty(address))
return false;
// If the destination chain is Ethereum, try and validate.
// Once conversions to other chains are implemented, we can add validation here.
if (destinationChain == (int)DestinationChain.ETH)
{
if (!TryConvertValidOpReturnDataToETHAddress(address))
return false;
}

return true;
return !string.IsNullOrEmpty(address);
}

/// <summary>
/// Try and validate an address on the Ethereum chain.
/// </summary>
/// <param name="potentialETHAddress">The address to validate.</param>
/// <returns><c>true</c> if valid, <c>false</c> otherwise.</returns>
private static bool TryConvertValidOpReturnDataToETHAddress(string potentialETHAddress)
{
// Attempt to parse the string. An Ethereum address is 42 characters:
// 0x - initial prefix
// <20 bytes> - rightmost 20 bytes of the Keccak hash of a public key, encoded as hex
Match match = Regex.Match(potentialETHAddress, @"^0x([A-Fa-f0-9]{40})$", RegexOptions.IgnoreCase);
return match.Success;
}

public static bool TryDecode(byte[] opReturnData, out int destinationChain, out string address)
{
string stringData = Encoding.UTF8.GetString(opReturnData);

return TryDecode(stringData, out destinationChain, out address);
}
}

/// <summary>Chains supported by InterFlux integration.</summary>
public enum DestinationChain
{
STRAX = 0, // Stratis
ETH, // Ethereum
BNB, // Binance

ETC, // Ethereum classic
AVAX, // Avalanche
ADA, // Cardano
}
}
37 changes: 0 additions & 37 deletions src/Stratis.Bitcoin/OpReturnDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NBitcoin;
using NLog;
using TracerAttributes;
Expand All @@ -26,8 +25,6 @@ public interface IOpReturnDataReader
/// <returns><c>true</c> if address was extracted; <c>false</c> otherwise.</returns>
bool TryGetTargetAddress(Transaction transaction, out string address);

bool TryGetTargetETHAddress(Transaction transaction, out string address);

/// <summary>
/// Tries to find a single OP_RETURN output that can be interpreted as a transaction id.
/// </summary>
Expand Down Expand Up @@ -68,24 +65,6 @@ public bool TryGetTargetAddress(Transaction transaction, out string address)
return true;
}

public bool TryGetTargetETHAddress(Transaction transaction, out string address)
{
var opReturnAddresses = SelectBytesContentFromOpReturn(transaction)
.Select(this.TryConvertValidOpReturnDataToETHAddress)
.Where(s => s != null)
.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();

// A standard OP_RETURN is not long enough to fit more than 1 Ethereum address, but a non-standard transaction could have multiple.
if (opReturnAddresses.Count != 1)
{
address = null;
return false;
}

address = opReturnAddresses[0];
return true;
}

/// <inheritdoc />
public bool TryGetTransactionId(Transaction transaction, out string txId)
{
Expand Down Expand Up @@ -134,22 +113,6 @@ private string TryConvertValidOpReturnDataToAddress(byte[] data)
}
}

private string TryConvertValidOpReturnDataToETHAddress(byte[] data)
{
// After removing the RETURN operator, convert the remaining bytes to our candidate address.
string destination = Encoding.UTF8.GetString(data);

// Attempt to parse the string. An Ethereum address is 42 characters:
// 0x - initial prefix
// <20 bytes> - rightmost 20 bytes of the Keccak hash of a public key, encoded as hex
Match match = Regex.Match(destination, @"^0x([A-Fa-f0-9]{40})$", RegexOptions.IgnoreCase);

if (!match.Success)
return null;

return destination;
}

private string TryConvertValidOpReturnDataToHash(byte[] data)
{
// Attempt to parse the hash. Validates the uint256 string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Controllers;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Features.Wallet.Models;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Features.Collateral.CounterChain;
Expand Down
16 changes: 6 additions & 10 deletions src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,11 +346,13 @@ public void ExtractLargeConversionDeposits_ReturnDeposits_AboveNormalThreshold()
// Set amount to be exactly the normal threshold amount.
CreateDepositTransaction(targetAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes);

byte[] ethOpReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode(DestinationChain.ETH, TargetETHAddress));

// Set amount to be equal to the normal threshold amount.
CreateConversionTransaction(TargetETHAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes);
CreateConversionTransaction(block, this.federationSettings.NormalDepositThresholdAmount, ethOpReturnBytes);

// Set amount to be greater than the normal threshold amount.
CreateConversionTransaction(TargetETHAddress, block, this.federationSettings.NormalDepositThresholdAmount + 1, opReturnBytes);
// Set amount to be greater than the conversion deposit minimum amount.
CreateConversionTransaction(block, Money.Coins(DepositExtractor.ConversionTransactionMinimum + 1), ethOpReturnBytes);

int blockHeight = 12345;
IReadOnlyList<IDeposit> extractedDeposits = this.depositExtractor.ExtractDepositsFromBlock(block, blockHeight, new[] { DepositRetrievalType.ConversionLarge });
Expand All @@ -364,20 +366,14 @@ public void ExtractLargeConversionDeposits_ReturnDeposits_AboveNormalThreshold()
}
}

private Transaction CreateConversionTransaction(string targetETHAddress, Block block, Money depositAmount, byte[] opReturnBytes)
private Transaction CreateConversionTransaction(Block block, Money depositAmount, byte[] opReturnBytes)
{
// Create the conversion transaction.
Transaction conversionTransaction = this.transactionBuilder.BuildOpReturnTransaction(this.addressHelper.SourceChainMultisigAddress, opReturnBytes, depositAmount);

// Add the conversion transaction to the block.
block.AddTransaction(conversionTransaction);

this.opReturnDataReader.TryGetTargetETHAddress(conversionTransaction, out string _).Returns(callInfo =>
{
callInfo[1] = targetETHAddress;
return true;
});

return conversionTransaction;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public MaturedBlocksProviderTests()

this.addressHelper = new MultisigAddressHelper(this.network, this.mainChainNetwork);
this.targetAddress = this.addressHelper.GetNewTargetChainPubKeyAddress();
this.opReturnBytes = Encoding.UTF8.GetBytes(this.targetAddress.ToString());
this.opReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode(DestinationChain.STRAX, this.targetAddress.ToString()));

this.federatedPegSettings = Substitute.For<IFederatedPegSettings>();
this.federatedPegSettings.MultiSigRedeemScript.Returns(this.addressHelper.PayToMultiSig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ public Task<bool> ExposedSyncBatchOfBlocksAsync()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Utilities;
using Stratis.Features.FederatedPeg.Interfaces;
using Stratis.Features.FederatedPeg.Models;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NBitcoin;
using Stratis.Bitcoin.Features.Wallet;

namespace Stratis.Features.FederatedPeg.Conversion
{
Expand Down
2 changes: 1 addition & 1 deletion src/Stratis.Features.FederatedPeg/Interfaces/IDeposit.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using NBitcoin;
using Newtonsoft.Json;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Utilities.JsonConverters;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Features.FederatedPeg.SourceChain;

namespace Stratis.Features.FederatedPeg.Interfaces
Expand Down
2 changes: 1 addition & 1 deletion src/Stratis.Features.FederatedPeg/SourceChain/Deposit.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using NBitcoin;
using Newtonsoft.Json;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Features.FederatedPeg.Interfaces;

namespace Stratis.Features.FederatedPeg.SourceChain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ public IReadOnlyList<IDeposit> ExtractDepositsFromBlock(Block block, int blockHe
/// <inheritdoc />
public IDeposit ExtractDepositFromTransaction(Transaction transaction, int blockHeight, uint256 blockHash)
{
// If there are no deposits to the multsig (i.e. cross chain transfers) do nothing.
if (!DepositValidationHelper.TryGetDepositsToMultisig(this.network, transaction, FederatedPegSettings.CrossChainTransferMinimum, out List<TxOut> depositsToMultisig))
return null;

// If there are deposits to the multsig (i.e. cross chain transfers), try and extract and validate the address by the specfied destination chain.
if (!DepositValidationHelper.TryGetTarget(transaction, this.opReturnDataReader, out bool conversionTransaction, out string targetAddress, out int targetChain))
return null;

Expand Down

0 comments on commit abd6144

Please sign in to comment.