// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"fmt"
	"math/big"
	"testing"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	testifyassert "github.com/stretchr/testify/assert"
)

// run all the tests in this file
// $> go test $(go list ./...) -run TestSignQuorum

// private key material to test both 0 and 1 bit for the recoveryId (v).
// key with v sign == 28 (Homestead)
var k0v, _ = new(big.Int).SetString("25807260602402504536675820444142779248993100028628438487502323668296269534891", 10)

// key with v sign == 27 (Homestead)
var k1v, _ = new(big.Int).SetString("10148397294747000913768625849546502595195728826990639993137198410557736548965", 10)

// helper to deterministically create an ECDSA key from an int.
func createKey(c elliptic.Curve, k *big.Int) (*ecdsa.PrivateKey, error) {
	sk := new(ecdsa.PrivateKey)
	sk.PublicKey.Curve = c
	sk.D = k
	sk.PublicKey.X, sk.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
	return sk, nil
}

func signTx(key *ecdsa.PrivateKey, signer Signer) (*Transaction, common.Address, error) {
	addr := crypto.PubkeyToAddress(key.PublicKey)
	tx := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil)
	signedTx, err := SignTx(tx, signer, key)
	//fmt.Printf("\ntx.data.V signTx after sign [%v] \n", signedTx.data.V)
	return signedTx, addr, err
}

/**
 * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
 *
 * Test public transactions signed by homestead Signer. Homestead sets the v param on a signed transaction to
 * either 27 or 28. The v parameter is used for recovering the sender of the signed transaction.
 *
 *  1. Homestead: should be 27, 28
 * $> go test -run TestSignQuorumHomesteadPublic
 */
func TestSignQuorumHomesteadPublic(t *testing.T) {
	assert := testifyassert.New(t)

	k0, _ := createKey(crypto.S256(), k0v)
	k1, _ := createKey(crypto.S256(), k1v)

	homeSinger := HomesteadSigner{}

	// odd parity should be 27 for Homestead
	signedTx, addr, _ := signTx(k1, homeSinger)

	v, _, _ := signedTx.RawSignatureValues()
	assert.True(v.Cmp(big.NewInt(27)) == 0, fmt.Sprintf("v wasn't 27 it was [%v]", v))

	// recover address from signed TX
	from, _ := Sender(homeSinger, signedTx)
	//fmt.Printf("from [%v] == addr [%v]\n\n", from, from == addr)
	assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr))

	// even parity should be 28 for Homestead
	signedTx, addr, _ = signTx(k0, homeSinger)
	v, _, _ = signedTx.RawSignatureValues()
	assert.True(v.Cmp(big.NewInt(28)) == 0, fmt.Sprintf("v wasn't 28 it was [%v]\n", v))

	// recover address from signed TX
	from, _ = Sender(homeSinger, signedTx)
	//fmt.Printf("from [%v] == addr [%v]\n", from, from == addr)
	assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr))
}

/**
 * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
 *
 * Test the public transactions signed by the EIP155Signer.
 * The EIP155Signer was introduced to protect against replay
 * attacks https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md and stores
 * the CHAINID in the signed transaction's `v` parameter as `v = chainId * 2 + 35`.
 *
 * The EthEIP155Signer change breaks private quorum transactions when the chainId == 1 (mainnet chainId),
 * as the v parameter on a public transaction and on a private transaction will both be 37, 38.
 *
 *  $> go test -run TestSignQuorumEIP155Public
 */
func TestSignQuorumEIP155Public(t *testing.T) {
	assert := testifyassert.New(t)

	k0, _ := createKey(crypto.S256(), k0v)
	k1, _ := createKey(crypto.S256(), k1v)

	// chainId 1 even EIP155Signer should be 37 conflicts with private transaction
	var chainId int64 = 2 // 7 2 10

	v0 := chainId*2 + 35 // sig[64] + 35 .. where sig[64] == 0
	v1 := chainId*2 + 36 // sig[64] + 35 .. where sig[64] == 1

	// Will calculate v to be `v = CHAINID * 2 + 35`
	// To compute V:
	// 	2 * 2 + 35 == 39
	// 	2 * 2 + 36 == 40
	// To retrieve Sender, pull out 27, 28 Eth Frontier / Homestead values.
	// 	39 - (2 * 2) - 8 == 27
	// 	40 - (2 * 2) - 8 == 28
	EIPsigner := NewEIP155Signer(big.NewInt(chainId))

	signedTx, addr, _ := signTx(k0, EIPsigner)

	v, _, _ := signedTx.RawSignatureValues()
	//fmt.Printf("After signing V is [%v] \n", signedTx.data.V)
	assert.True(v.Cmp(big.NewInt(v0)) == 0, fmt.Sprintf("v wasn't [%v] it was [%v]\n", v0, v))
	from, _ := Sender(EIPsigner, signedTx)

	assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr))

	// chainId 1 even  EIP155Signer should be 38 conflicts with private transaction
	assert.False(signedTx.IsPrivate(), fmt.Sprintf("Public transaction is set to a private transition v == [%v]", v))

	signedTx, addr, _ = signTx(k1, EIPsigner)

	v, _, _ = signedTx.RawSignatureValues()
	assert.True(v.Cmp(big.NewInt(v1)) == 0, fmt.Sprintf("v wasn't [%v], it was [%v]\n", v1, v))
	from, _ = Sender(EIPsigner, signedTx)

	assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr))
}

/**
 *  As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
 *
 * When the signer is EIP155Signer, chainId == 1 cannot be used because the EIP155 computed `v` value conflicts
 * with the private `v` value that quorum uses to indicate a private transaction: v == 37 and v == 38.
 *
 *  $> go test -run TestSignQuorumEIP155FailPublicChain1
 */
func TestSignQuorumEIP155FailPublicChain1(t *testing.T) {
	assert := testifyassert.New(t)

	k0, _ := createKey(crypto.S256(), k0v)
	k1, _ := createKey(crypto.S256(), k1v)

	// chainId 1 even  EIP155Signer should be 37.38 which conflicts with private transaction
	var chainId int64 = 1

	v0 := chainId*2 + 35 // sig[64] + 35 .. where sig[64] == 0
	v1 := chainId*2 + 36 // sig[64] + 35 .. where sig[64] == 1

	// Will calculate v to be `v = CHAINID * 2 + 35`
	// To compute V:
	// 	2 * 1 + 35 == 37
	// 	2 * 1 + 36 == 38
	// To retrieve Sender, pull out 27, 28 Eth Frontier / Homestead values.
	// 	37 - (1 * 2) - 8 == 27
	// 	38 - (1 * 2) - 8 == 28
	EIPsigner := NewEIP155Signer(big.NewInt(chainId))

	signedTx, addr, _ := signTx(k0, EIPsigner)

	v, _, _ := signedTx.RawSignatureValues()
	// the calculated v value should equal `chainId * 2 + 35 `
	assert.True(v.Cmp(big.NewInt(v0)) == 0, fmt.Sprintf("v wasn't [%v] it was "+
		"[%v]\n", v0, v))
	// the sender will not be equal as HomesteadSigner{}.Sender(tx) is used because IsPrivate() will be true
	// although it is a public tx.
	// This is test to catch when / if this behavior changes.
	assert.True(signedTx.IsPrivate(), "A public transaction with EIP155 and chainID 1 is expected to be "+
		"considered private, as its v param conflict with a private transaction. signedTx.IsPrivate() == [%v]", signedTx.IsPrivate())
	from, _ := Sender(EIPsigner, signedTx)

	assert.False(from == addr, fmt.Sprintf("Expected the sender of a public TX from chainId 1, \n "+
		"should not be recoverable from [%x] addr [%v] ", from, addr))

	signedTx, addr, _ = signTx(k1, EIPsigner)

	v, _, _ = signedTx.RawSignatureValues()
	// the calculated v value should equal `chainId * 2 + 35`
	assert.True(v.Cmp(big.NewInt(v1)) == 0,
		fmt.Sprintf("v wasn't [%v] it was [%v]", v1, v))

	// the sender will not be equal as HomesteadSigner{}.Sender(tx) is used because IsPrivate() will be true
	// although it is a public tx.
	// This is test to catch when / if this behavior changes.
	// we are signing the data with EIPsigner and chainID 1, so this would be considered a private tx.
	assert.True(signedTx.IsPrivate(), "A public transaction with EIP155 and chainID 1 is expected to "+
		"to be considered private, as its v param conflict with a private transaction. "+
		"signedTx.IsPrivate() == [%v]", signedTx.IsPrivate())
	from, _ = Sender(EIPsigner, signedTx)

	assert.False(from == addr, fmt.Sprintf("Expected the sender of a public TX from chainId 1, "+
		"should not be recoverable from [%x] addr [%v] ", from, addr))
}

/*
*
*  As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
*
*  Use Homestead to sign and EIPSigner to recover.
*
*  SendTransaction creates a transaction for the given argument, signs it and submit it to the transaction pool.
*  func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
*  Current implementation in `internal/ethapi/api.go`
*
*  accounts/keystore/keystore.SignTx(): would hash and sign with homestead
*
*  When a private tx (obtained from json params PrivateFor) is submitted `internal/ethapi/api.go`:
*
*  1. sign with HomesteadSigner, this will set the v parameter to
*     27 or 28. // there is no indication that this is a private tx yet.
*
  - 2. when submitting a transaction `submitTransaction(ctx context.Context, b Backend, tx *types.Transaction, isPrivate bool)`
    check isPrivate param, and call `tx.SetPrivate()`, this will update the `v` signature param (recoveryID)
  - from 27 -> 37, 28 -> 38. // this is now considered a private tx.

*
*  $> go test -run TestSignQuorumHomesteadEIP155SigningPrivateQuorum
*/
func TestSignQuorumHomesteadEIP155SigningPrivateQuorum(t *testing.T) {
	assert := testifyassert.New(t)

	keys := []*big.Int{k0v, k1v}

	homeSinger := HomesteadSigner{}
	recoverySigner := NewEIP155Signer(big.NewInt(18))

	// check for both sig[64] == 0, and sig[64] == 1
	for i := 0; i < len(keys); i++ {
		key, _ := createKey(crypto.S256(), keys[i])
		signedTx, addr, err := signTx(key, homeSinger)

		assert.Nil(err, err)
		// set to privateTX after the initial signing, this explicitly sets the v param.
		// Note: only works when the tx was signed with the homesteadSinger (v==27 | 28).
		signedTx.SetPrivate()

		assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be private [%v]", signedTx.IsPrivate()))
		// Try to recover Sender
		from, err := Sender(recoverySigner, signedTx)

		assert.Nil(err, err)
		assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr))
	}
}

/*
 * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
 * Use Homestead to sign and Homestead to recover.
 *
 * Signing private transactions with HomesteadSigner, and recovering a private transaction with
 * HomesteadSigner works, but the transaction has to be set to private `signedTx.SetPrivate()` after
 * the signature and before recovering the address.
 *
 *  $> go test -run TestSignQuorumHomesteadOnlyPrivateQuorum
 */
func TestSignQuorumHomesteadOnlyPrivateQuorum(t *testing.T) {
	assert := testifyassert.New(t)

	// check even and odd parity
	keys := []*big.Int{k0v, k1v}

	homeSinger := HomesteadSigner{}
	recoverySigner := HomesteadSigner{}

	for i := 0; i < len(keys); i++ {
		key, _ := createKey(crypto.S256(), keys[i])
		signedTx, addr, err := signTx(key, homeSinger)

		assert.Nil(err, err)

		//fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V)
		// set to privateTX after the initial signing.
		signedTx.SetPrivate()
		assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be "+
			"private [%v]", signedTx.IsPrivate()))
		//fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V)

		// Try to recover Sender
		from, err := Sender(recoverySigner, signedTx)

		assert.Nil(err, err)
		assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. "+
			" Got %x want %x", from, addr))
	}
}

/*
 * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior
 *
 * Use EIP155 to sign and EIP155 to recover (This is not a valid combination and does **not** work).
 *
 * Signing private transactions with EIP155Signer, and recovering a private transaction with
 * EIP155Signer does **not** work.
 * note: deriveChainId only checks for 27, 28 when using EIP155
 * note: In the case where the v param is not 27 or 28 when setting private it will always be set to 37
 *
 *  $> go test -run TestSignQuorumEIP155OnlyPrivateQuorum
 */
func TestSignQuorumEIP155OnlyPrivateQuorum(t *testing.T) {
	assert := testifyassert.New(t)

	// check even and odd parity
	keys := []*big.Int{k0v, k1v}

	EIP155Signer := NewEIP155Signer(big.NewInt(0))

	for i := 0; i < len(keys); i++ {
		key, _ := createKey(crypto.S256(), keys[i])
		signedTx, addr, err := signTx(key, EIP155Signer)

		assert.Nil(err, err)
		//fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V)

		// set to privateTX after the initial signing.
		signedTx.SetPrivate()

		assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be private [%v]", signedTx.IsPrivate()))
		//fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V)

		// Try to recover Sender
		from, err := Sender(EIP155Signer, signedTx)

		assert.Nil(err, err)
		assert.False(from == addr, fmt.Sprintf("Expected recovery to fail. from [%x] should not equal "+
			"addr [%x]", from, addr))
	}
}
