Merge branch 'onchain-rename' into secp256k1_solidity

This commit is contained in:
Robert Hambrock
2021-10-24 15:57:24 +02:00
4 changed files with 1304 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.5;
// import "./Ed25519.sol";
import "./Ed25519_alt.sol";
import "./EC.sol";
contract SwapDLEQ {
// EC library
EC immutable ec;
// contract creator, Alice
address payable immutable owner;
// the expected public key derived from the secret `s_b`.
// this public key is a point on the ed25519 curve
uint256 public immutable pubKeyClaimX;
uint256 public immutable pubKeyClaimY;
// the expected public key derived from the secret `s_a`.
// this public key is a point on the ed25519 curve
uint256 public immutable pubKeyRefundX;
uint256 public immutable pubKeyRefundY;
// time period from contract creation
// during which Alice can call either set_ready or refund
uint256 public immutable timeout_0;
// time period from the moment Alice calls `set_ready`
// during which Bob can claim. After this, Alice can refund again
uint256 public timeout_1;
// Alice sets ready to true when she sees the funds locked on the other chain.
// this prevents Bob from withdrawing funds without locking funds on the other chain first
bool isReady = false;
event Constructed(uint256 p, uint256 q);
event IsReady(bool b);
event Claimed(uint256 s);
event Refunded(uint256 s);
constructor(uint256 _pubKeyClaimX, uint256 _pubKeyClaimY, uint256 _pubKeyRefundX, uint256 _pubKeyRefundY) payable {
owner = payable(msg.sender);
pubKeyClaimX = _pubKeyClaimX;
pubKeyClaimY = _pubKeyClaimY;
pubKeyRefundX = _pubKeyRefundX;
pubKeyRefundY = _pubKeyRefundY;
timeout_0 = block.timestamp + 1 days;
/* ed25519 = new Ed25519(); */
ec = new EC();
emit Constructed(_pubKeyRefundX, _pubKeyRefundY);
}
// Alice must call set_ready() within t_0 once she verifies the XMR has been locked
function set_ready() external {
require(!isReady && msg.sender == owner && block.timestamp < timeout_0);
isReady = true;
timeout_1 = block.timestamp + 1 days;
emit IsReady(true);
}
// Bob can claim if:
// - Alice doesn't call set_ready or refund within t_0, or
// - Alice calls ready within t_0, in which case Bob can call claim until t_1
function claim(uint256 _s) external {
if (isReady == true) {
require(block.timestamp < timeout_1, "Too late to claim!");
} else {
require(
block.timestamp >= timeout_0,
"'isReady == false' cannot claim yet!"
);
}
require(ec.publicKeyVerify(_s, pubKeyClaimX, pubKeyClaimY),
"provided secret does not match the expected pubKey");
emit Claimed(_s);
// send eth to caller (Bob)
selfdestruct(payable(msg.sender));
}
// Alice can claim a refund:
// - Until t_0 unless she calls set_ready
// - After t_1, if she called set_ready
function refund(uint256 _s) external {
if (isReady == true) {
require(
block.timestamp >= timeout_1,
"It's Bob's turn now, please wait!"
);
} else {
require(block.timestamp < timeout_0, "Missed your chance!");
}
require(ec.publicKeyVerify(_s, pubKeyRefundX, pubKeyRefundY),
"provided secret does not match the expected pubKey");
emit Refunded(_s);
// send eth back to owner==caller (Alice)
selfdestruct(owner);
}
/* function verifySecret(uint256 _s, bytes32 pubKey) internal view { */
/* // (uint256 px, uint256 py) = ed25519.derivePubKey(_s); */
/* (uint256 px, uint256 py) = ed25519.scalarMultBase(_s); */
/* uint256 canonical_p = py | ((px % 2) << 255); */
/* require( */
/* bytes32(canonical_p) == pubKey, */
/* "provided secret does not match the expected pubKey" */
/* ); */
/* } */
}

View File

@@ -1,5 +1,9 @@
#!/bin/bash
$SOLC_BIN --abi ethereum/contracts/SwapOnChain.sol -o ethereum/abi/ --overwrite
$SOLC_BIN --bin ethereum/contracts/SwapOnChain.sol -o ethereum/bin/ --overwrite
abigen --abi ethereum/abi/SwapOnChain.abi --pkg swapOnChain --type SwapOnChain --out swapOnChain.go --bin ethereum/bin/SwapOnChain.bin
mv swapOnChain.go ./swapOnChain-contract
$SOLC_BIN --abi ethereum/contracts/SwapDLEQ.sol -o ethereum/abi/ --overwrite
$SOLC_BIN --bin ethereum/contracts/SwapDLEQ.sol -o ethereum/bin/ --overwrite
abigen --abi ethereum/abi/SwapDLEQ.abi --pkg swapDLEQ --type SwapDLEQ --out swapDLEQ.go --bin ethereum/bin/SwapDLEQ.bin

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,261 @@
package swapOnChain
import (
"context"
"encoding/hex"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"github.com/noot/atomic-swap/monero"
)
const (
keyAlice = "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"
keyBob = "6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"
)
func reverse(s []byte) []byte {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func setBigIntLE(s []byte) *big.Int {
s = reverse(s)
return big.NewInt(0).SetBytes(s)
}
func TestDeploySwapOnChain(t *testing.T) {
conn, err := ethclient.Dial("http://127.0.0.1:8545")
require.NoError(t, err)
pk_a, err := crypto.HexToECDSA(keyAlice)
require.NoError(t, err)
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
require.NoError(t, err)
address, tx, swapContract, err := DeploySwapOnChain(authAlice, conn, [32]byte{}, [32]byte{})
require.NoError(t, err)
t.Log(address)
t.Log(tx)
t.Log(swapContract)
}
func TestSwap_Claim(t *testing.T) {
// Alice generates key
keyPairAlice, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyAlice := keyPairAlice.PublicKeyPair().SpendKey().Bytes()
// Bob generates key
keyPairBob, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyBob := keyPairBob.PublicKeyPair().SpendKey().Bytes()
secretBob := keyPairBob.SpendKeyBytes()
// setup
conn, err := ethclient.Dial("ws://127.0.0.1:8545")
require.NoError(t, err)
pk_a, err := crypto.HexToECDSA(keyAlice)
require.NoError(t, err)
pk_b, err := crypto.HexToECDSA(keyBob)
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
authAlice.Value = big.NewInt(10)
require.NoError(t, err)
authBob, err := bind.NewKeyedTransactorWithChainID(pk_b, big.NewInt(1337)) // ganache chainID
require.NoError(t, err)
aliceBalanceBefore, err := conn.BalanceAt(context.Background(), authAlice.From, nil)
fmt.Println("AliceBalanceBefore: ", aliceBalanceBefore)
// check whether Bob had nothing before the Tx
bobBalanceBefore, err := conn.BalanceAt(context.Background(), authBob.From, nil)
fmt.Println("BobBalanceBefore: ", bobBalanceBefore)
require.NoError(t, err)
var pkAliceFixed [32]byte
copy(pkAliceFixed[:], reverse(pubKeyAlice))
var pkBobFixed [32]byte
copy(pkBobFixed[:], reverse(pubKeyBob))
contractAddress, deployTx, swap, err := DeploySwapOnChain(authAlice, conn, pkBobFixed, pkAliceFixed)
fmt.Println("Deploy Tx Gas Cost:", deployTx.Gas())
aliceBalanceAfter, err := conn.BalanceAt(context.Background(), authAlice.From, nil)
fmt.Println("AliceBalanceAfter: ", aliceBalanceAfter)
contractBalance, err := conn.BalanceAt(context.Background(), contractAddress, nil)
require.Equal(t, contractBalance, big.NewInt(10))
require.NoError(t, err)
txOpts := &bind.TransactOpts{
From: authAlice.From,
Signer: authAlice.Signer,
}
txOptsBob := &bind.TransactOpts{
From: authBob.From,
Signer: authBob.Signer,
}
// Bob tries to claim before Alice has called ready, should fail
s := big.NewInt(0).SetBytes(reverse(secretBob))
fmt.Println("Secret:", hex.EncodeToString(reverse(secretBob)))
fmt.Println("PubKey:", hex.EncodeToString(reverse(pubKeyBob)))
_, err = swap.Claim(txOptsBob, s)
require.Regexp(t, ".*'isReady == false' cannot claim yet!", err)
// Alice calls set_ready on the contract
setReadyTx, err := swap.SetReady(txOpts)
fmt.Println("setReady Tx Gas Cost:", setReadyTx.Gas())
require.NoError(t, err)
// The main transaction that we're testing. Should work
tx, err := swap.Claim(txOptsBob, s)
require.NoError(t, err)
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
contractBalance, err = conn.BalanceAt(context.Background(), contractAddress, nil)
require.NoError(t, err)
require.Equal(t, contractBalance.Uint64(), big.NewInt(0).Uint64())
bytecode, err := conn.CodeAt(context.Background(), contractAddress, nil) // nil is latest block
require.NoError(t, err)
require.Empty(t, bytecode)
fmt.Println("Tx details are:", tx.Gas())
// check whether Bob's account balance has increased now
// bobBalanceAfter, err := conn.BalanceAt(context.Background(), authBob.From, nil)
// fmt.Println("BobBalanceBefore: ", bobBalanceAfter)
// require.NoError(t, err)
// require.Equal(t, big.NewInt(10), big.NewInt(0).Sub(bobBalanceAfter, bobBalanceBefore))
}
func TestSwap_Refund_Within_T0(t *testing.T) {
// Alice generates key
keyPairAlice, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyAlice := keyPairAlice.PublicKeyPair().SpendKey().Bytes()
// Bob generates key
keyPairBob, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyBob := keyPairBob.PublicKeyPair().SpendKey().Bytes()
secretAlice := keyPairAlice.SpendKeyBytes()
// setup
conn, err := ethclient.Dial("ws://127.0.0.1:8545")
require.NoError(t, err)
pk_a, err := crypto.HexToECDSA(keyAlice)
require.NoError(t, err)
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
authAlice.Value = big.NewInt(10)
require.NoError(t, err)
aliceBalanceBefore, err := conn.BalanceAt(context.Background(), authAlice.From, nil)
fmt.Println("AliceBalanceBefore: ", aliceBalanceBefore)
var pkAliceFixed [32]byte
copy(pkAliceFixed[:], reverse(pubKeyAlice))
var pkBobFixed [32]byte
copy(pkBobFixed[:], reverse(pubKeyBob))
contractAddress, _, swap, err := DeploySwapOnChain(authAlice, conn, pkBobFixed, pkAliceFixed)
txOpts := &bind.TransactOpts{
From: authAlice.From,
Signer: authAlice.Signer,
}
// Alice never calls set_ready on the contract, instead she just tries to Refund immidiately
s := big.NewInt(0).SetBytes(reverse(secretAlice))
_, err = swap.Refund(txOpts, s)
require.NoError(t, err)
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
contractBalance, err := conn.BalanceAt(context.Background(), contractAddress, nil)
require.NoError(t, err)
require.Equal(t, contractBalance.Uint64(), big.NewInt(0).Uint64())
bytecode, err := conn.CodeAt(context.Background(), contractAddress, nil) // nil is latest block
require.NoError(t, err)
require.Empty(t, bytecode)
}
func TestSwap_Refund_After_T1(t *testing.T) {
// Alice generates key
keyPairAlice, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyAlice := keyPairAlice.PublicKeyPair().SpendKey().Bytes()
// Bob generates key
keyPairBob, err := monero.GenerateKeys()
require.NoError(t, err)
pubKeyBob := keyPairBob.PublicKeyPair().SpendKey().Bytes()
secretAlice := keyPairAlice.SpendKeyBytes()
// setup
conn, err := ethclient.Dial("ws://127.0.0.1:8545")
require.NoError(t, err)
pk_a, err := crypto.HexToECDSA(keyAlice)
require.NoError(t, err)
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
authAlice.Value = big.NewInt(10)
require.NoError(t, err)
aliceBalanceBefore, err := conn.BalanceAt(context.Background(), authAlice.From, nil)
fmt.Println("AliceBalanceBefore: ", aliceBalanceBefore)
var pkAliceFixed [32]byte
copy(pkAliceFixed[:], reverse(pubKeyAlice))
var pkBobFixed [32]byte
copy(pkBobFixed[:], reverse(pubKeyBob))
contractAddress, _, swap, err := DeploySwapOnChain(authAlice, conn, pkBobFixed, pkAliceFixed)
txOpts := &bind.TransactOpts{
From: authAlice.From,
Signer: authAlice.Signer,
}
// Alice calls set_ready on the contract, and immediately tries to Refund
// After waiting T1, Alice should be able to refund now
s := big.NewInt(0).SetBytes(reverse(secretAlice))
_, err = swap.SetReady(txOpts)
require.NoError(t, err)
_, err = swap.Refund(txOpts, s)
require.Regexp(t, ".*It's Bob's turn now, please wait!", err)
// wait some, then try again
var result int64
rpcClient, err := rpc.Dial("http://127.0.0.1:8545")
ret := rpcClient.Call(&result, "evm_increaseTime", 3600*25)
require.NoError(t, ret)
_, err = swap.Refund(txOpts, s)
require.NoError(t, err)
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
contractBalance, err := conn.BalanceAt(context.Background(), contractAddress, nil)
require.NoError(t, err)
require.Equal(t, contractBalance.Uint64(), big.NewInt(0).Uint64())
bytecode, err := conn.CodeAt(context.Background(), contractAddress, nil) // nil is latest block
require.NoError(t, err)
require.Empty(t, bytecode)
}