Skip to content

Commit

Permalink
Revert "Remove ECRecover implementation"
Browse files Browse the repository at this point in the history
This reverts commit d5334fb.
  • Loading branch information
rowandh committed Oct 21, 2021
1 parent eb26a29 commit 18fba74
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,51 @@ namespace Stratis.SCL.Crypto
{
public static class ECRecover
{
/// <summary>
/// Retrieves the address of the signer of an ECDSA signature.
/// </summary>
/// <param name="message"></param>
/// <param name="signature">The ECDSA signature prepended with header information specifying the correct value of recId.</param>
/// <param name="address">The Address for the signer of a signature.</param>
/// <returns>A bool representing whether or not the signer was retrieved successfully.</returns>
public static bool TryGetSigner(byte[] message, byte[] signature, out Address address)
{
address = Address.Zero;

if (message == null || signature == null)
return false;

// NBitcoin is very throwy
try
{
uint256 hashedUint256 = GetUint256FromMessage(message);

PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature);

address = CreateAddress(pubKey.Hash.ToBytes());

return true;
}
catch
{
return false;
}
}

private static uint256 GetUint256FromMessage(byte[] message)
{
return new uint256(SHA3.Keccak256(message));
}

private static Address CreateAddress(byte[] bytes)
{
uint pn0 = BitConverter.ToUInt32(bytes, 0);
uint pn1 = BitConverter.ToUInt32(bytes, 4);
uint pn2 = BitConverter.ToUInt32(bytes, 8);
uint pn3 = BitConverter.ToUInt32(bytes, 12);
uint pn4 = BitConverter.ToUInt32(bytes, 16);

return new Address(pn0, pn1, pn2, pn3, pn4);
}
}
}
179 changes: 179 additions & 0 deletions src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System.Threading.Tasks;
using NBitcoin;
using Stratis.Bitcoin.Features.SmartContracts.Models;
using Stratis.SCL.Crypto;
using Stratis.SmartContracts.CLR;
using Stratis.SmartContracts.CLR.Compilation;
using Stratis.SmartContracts.CLR.Serialization;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Networks;
using Stratis.SmartContracts.Tests.Common.MockChain;
using Xunit;
using EcRecoverProvider = Stratis.SCL.Crypto.ECRecover;
using Key = NBitcoin.Key;

namespace Stratis.SmartContracts.IntegrationTests
{
public class ECRecoverTests
{
// 2 things to test:

// 1) That we have the ECDSA code and can make it available.

[Fact]
public void CanSignAndRetrieveSender()
{
var network = new SmartContractsRegTest();
var privateKey = new Key();
Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network);
byte[] message = new byte[] { 0x69, 0x76, 0xAA };

// Sign a message
byte[] offChainSignature = SignMessage(privateKey, message);

// Get the address out of the signature
EcRecoverProvider.TryGetSigner(message, offChainSignature, out Address recoveredAddress);

// Check that the address matches that generated from the private key.
Assert.Equal(address, recoveredAddress);
}

[Fact]
public void GetSigner_Returns_Address_Zero_When_Message_Or_Signature_Null()
{
var network = new SmartContractsRegTest();
var privateKey = new Key();
Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network);
byte[] message = new byte[] { 0x69, 0x76, 0xAA };

// Sign a message
byte[] offChainSignature = SignMessage(privateKey, message);

// Get the address out of the signature
Assert.False(EcRecoverProvider.TryGetSigner(null, offChainSignature, out Address recoveredAddress));

Assert.Equal(Address.Zero, recoveredAddress);

Assert.False(EcRecoverProvider.TryGetSigner(message, null, out Address recoveredAddress2));

Assert.Equal(Address.Zero, recoveredAddress2);
}

/// <summary>
/// Signs a message, returning an ECDSA signature.
/// </summary>
/// <param name="privateKey">The private key used to sign the message.</param>
/// <param name="message">The complete message to be signed.</param>
/// <returns>The ECDSA signature prepended with header information specifying the correct value of recId.</returns>
private static byte[] SignMessage(Key privateKey, byte[] message)
{
uint256 hashedUint256 = new uint256(SHA3.Keccak256(message));

return privateKey.SignCompact(hashedUint256);
}

[Fact]
public void CanCallEcRecoverContractWithValidSignatureAsync()
{
using (PoWMockChain chain = new PoWMockChain(2))
{
var node1 = chain.Nodes[0];

node1.MineBlocks(1);

var network = chain.Nodes[0].CoreNode.FullNode.Network;

var privateKey = new Key();
string address = privateKey.PubKey.GetAddress(network).ToString();
byte[] message = new byte[] { 0x69, 0x76, 0xAA };
byte[] signature = SignMessage(privateKey, message);

// TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned?

byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation;
string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) };
BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters);

Assert.NotNull(createResult);
Assert.True(createResult.Success);

node1.WaitMempoolCount(1);
node1.MineBlocks(1);

string[] callParameters = new string[]
{
string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()),
string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString())
};

BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters);
Assert.NotNull(response);
Assert.True(response.Success);

node1.WaitMempoolCount(1);
node1.MineBlocks(1);

ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString());

Assert.NotNull(receipt);
Assert.True(receipt.Success);
Assert.Equal("True", receipt.ReturnValue);
}
}

[Fact]
public void CanCallEcRecoverContractWithInvalidSignatureAsync()
{
using (PoWMockChain chain = new PoWMockChain(2))
{
var node1 = chain.Nodes[0];

node1.MineBlocks(1);

var network = chain.Nodes[0].CoreNode.FullNode.Network;

var privateKey = new Key();
string address = privateKey.PubKey.GetAddress(network).ToString();
byte[] message = new byte[] { 0x69, 0x76, 0xAA };

// Make the signature with a key unrelated to the third party signer for the contract.
byte[] signature = SignMessage(new Key(), message);

// TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned?

byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation;
string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) };
BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters);

Assert.NotNull(createResult);
Assert.True(createResult.Success);

node1.WaitMempoolCount(1);
node1.MineBlocks(1);

string[] callParameters = new string[]
{
string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()),
string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString())
};

BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters);
Assert.NotNull(response);
Assert.True(response.Success);

node1.WaitMempoolCount(1);
node1.MineBlocks(1);

ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString());

Assert.NotNull(receipt);
Assert.True(receipt.Success);
Assert.Equal("False", receipt.ReturnValue);
}
}

// 2) That we can enable the method in new contracts without affecting the older contracts

// TODO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Stratis.SmartContracts;
using EcRecover = Stratis.SCL.Crypto.ECRecover;

public class EcRecoverContract : SmartContract
{
public Address ThirdPartySigner
{
get
{
return this.State.GetAddress(nameof(this.ThirdPartySigner));
}
set
{
this.State.SetAddress(nameof(this.ThirdPartySigner), value);
}
}

public EcRecoverContract(ISmartContractState state, Address thirdPartySigner) : base(state)
{
this.ThirdPartySigner = thirdPartySigner;
}

public bool CheckThirdPartySignature(byte[] message, byte[] signature)
{
EcRecover.TryGetSigner(message, signature, out Address signerOfMessage);
return (signerOfMessage == this.ThirdPartySigner);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
<Compile Update="SmartContracts\CreationTransfer.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Compile>
<Compile Update="SmartContracts\EcRecoverContract.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Update="SmartContracts\EmptyContract.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Compile>
Expand Down

0 comments on commit 18fba74

Please sign in to comment.