mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-10 14:48:14 -05:00
Merge branch 'onchain-rename' into secp256k1_solidity
This commit is contained in:
114
ethereum/contracts/SwapOnChain.sol
Normal file
114
ethereum/contracts/SwapOnChain.sol
Normal 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" */
|
||||
/* ); */
|
||||
/* } */
|
||||
}
|
||||
@@ -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
|
||||
|
||||
925
swapOnChain-contract/swapOnChain.go
Normal file
925
swapOnChain-contract/swapOnChain.go
Normal file
File diff suppressed because one or more lines are too long
261
swapOnChain-contract/swapOnChain_test.go
Normal file
261
swapOnChain-contract/swapOnChain_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user