mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
protocol update: update contract to verify hash of private spend key, saving on gas (#37)
This commit is contained in:
43
README.md
43
README.md
@@ -4,48 +4,7 @@ This is a WIP prototype of ETH<->XMR atomic swaps, currently in the early develo
|
||||
|
||||
## Protocol
|
||||
|
||||
Alice has ETH and wants XMR, Bob has XMR and wants ETH. They come to an agreement to do the swap and the amounts they will swap.
|
||||
|
||||
#### Initial (offchain) phase
|
||||
- Alice and Bob each generate Monero secret keys (which consist of secret spend and view keys): (`s_a`, `v_a`) and (`s_b`, `v_b`), which are used to construct valid points on the ed25519 curve (ie. public keys): `P_a` and `P_b` accordingly. Alice sends Bob her public key and Bob sends Alice his public spend key and private view key. Note: The XMR will be locked in the account with address corresponding to the public key `P_a + P_b`. Bob needs to send his private view key so Alice can check that Bob actually locked the amount of XMR he claims he will.
|
||||
|
||||
#### Step 1.
|
||||
Alice deploys a smart contract on Ethereum and locks her ETH in it. The contract has the following properties:
|
||||
- it is non-destructible
|
||||
|
||||
- it contains two timestamps, `t_0` and `t_1`, before and after which different actions are authorized.
|
||||
|
||||
- it is constructed containing `P_ed_a` & `P_ed_b`, so that if Alice or Bob reveals their secret by calling the contract, the contract will verify that the secret corresponds to the expected public key that it was initalized with.
|
||||
|
||||
- it has a `Ready()` function which can only be called by Alice. Once `Ready()` is invoked, Bob can proceed with redeeming his ether. Alice has until the `t_0` timestamp to call `Ready()` - once `t_0` passes, then the contract automatically allows Bob to claim his ether, up until some second timestamp `t_1`.
|
||||
|
||||
- it has a `Claim()` function which can only be called by Bob after `Ready()` is called or `t_0` passes, up until the timestamp `t_1`. After `t_1`, Bob can no longer claim the ETH.
|
||||
|
||||
- `Claim()` takes one parameter from Bob: `s_b`. Once `Claim()` is called, the ETH is transferred to Bob, and simultaneously Bob reveals his secret and thus Alice can claim her XMR by combining her and Bob's secrets.
|
||||
|
||||
- it has a `Refund()` function that can only be called by Alice and only before `Ready()` is called *or* `t_0` is reached. Once `Ready()` is invoked, Alice can no longer call `Refund()` until the next timestamp `t_1`. If Bob doesn't claim his ether by `t_1`, then `Refund()` can be called by Alice once again.
|
||||
|
||||
- `Refund()` takes one parameter from Alice: `s_a`. This allows Alice to get her ETH back in case Bob goes offline, but it simulteneously reveals her secret, allowing Bob to regain access to the XMR he locked.
|
||||
|
||||
#### Step 2.
|
||||
Bob sees the smart contract has been deployed with the correct parameters. He sends his XMR to an account address constructed from `P_a + P_b`. Thus, the funds can only be accessed by an entity having both `s_a` & `s_b`, as the secret spend key to that account is `s_a + s_b`. The funds are viewable by someone having `v_a + v_b`.
|
||||
|
||||
Note: `Refund()` and `Claim()` cannot be called at the same time. This is to prevent the case of front-running where, for example, Bob tries to claim, so his secret `s_b` is in the mempool, and then Alice tries to call `Refund()` with a higher priority while also transferring the XMR in the account controlled by `s_a + s_b`. If her call goes through before Bob's and Bob doesn't notice this happening in time, then Alice will now have *both* the ETH and the XMR. Due to this case, Alice and Bob should not call `Refund()` or `Claim()` when they are approaching `t_0` or `t_1` respectively, as their transaction may not go through in time.
|
||||
|
||||
#### Step 3.
|
||||
Alice sees that the XMR has been locked, and the amount is correct (as she knows `v_a` and Bob send her `v_b` in the first key exchange step). She calls `Ready()` on the smart contract if the XMR has been locked. If the amount of XMR locked is incorrect, Alice calls `Refund()` to abort the swap and reclaim her ETH.
|
||||
|
||||
From this point on, Bob can redeem his ether by calling `Claim(s_b)`, which transfers the ETH to him.
|
||||
|
||||
By redeeming, Bob reveals his secret. Now Alice is the only one that has both `s_a` & `s_b` and she can access the monero in the account created from `P_ed_a + P_ed_b`.
|
||||
|
||||
#### What could go wrong
|
||||
|
||||
- **Alice locked her ETH, but Bob doesn't lock his XMR**. Alice has until time `t_0` to call `Refund()` to reclaim her ETH, which she should do if `t_0` is soon.
|
||||
|
||||
- **Alice called `Ready()`, but Bob never redeems.** Deadlocks are prevented thanks to a second timelock `t_1`, which re-enables Alice to call refund after it, while disabling Bob's ability to claim.
|
||||
|
||||
- **Alice never calls `ready` within `t_0`**. Bob can still claim his ETH by waiting until after `t_0` has passed, as the contract automatically allows him to call `Claim()`.
|
||||
Please see the [protocol documentation](docs/protocol.md) for how it works.
|
||||
|
||||
## Instructions
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ func (s *swapState) generateKeys() (*monero.PublicKeyPair, error) {
|
||||
// setBobKeys sets Bob's public spend key (to be stored in the contract) and Bob's
|
||||
// private view key (used to check XMR balance before calling Ready())
|
||||
func (s *swapState) setBobKeys(sk *monero.PublicKey, vk *monero.PrivateViewKey) {
|
||||
s.bobSpendKey = sk
|
||||
s.bobViewKey = vk
|
||||
s.bobPublicSpendKey = sk
|
||||
s.bobPrivateViewKey = vk
|
||||
}
|
||||
|
||||
// deployAndLockETH deploys an instance of the Swap contract and locks `amount` ether in it.
|
||||
@@ -125,29 +125,33 @@ func (s *swapState) deployAndLockETH(amount uint64) (ethcommon.Address, error) {
|
||||
return ethcommon.Address{}, errors.New("public keys aren't set")
|
||||
}
|
||||
|
||||
if s.bobSpendKey == nil {
|
||||
if s.bobPublicSpendKey == nil || s.bobPrivateViewKey == nil {
|
||||
return ethcommon.Address{}, errors.New("bob's keys aren't set")
|
||||
}
|
||||
|
||||
pkAlice := s.pubkeys.SpendKey().Bytes()
|
||||
pkBob := s.bobSpendKey.Bytes()
|
||||
|
||||
var pka, pkb [32]byte
|
||||
copy(pka[:], pkAlice)
|
||||
copy(pkb[:], pkBob)
|
||||
hb := s.bobClaimHash
|
||||
ha := s.privkeys.SpendKey().Hash()
|
||||
|
||||
log.Debug("locking amount: ", amount)
|
||||
|
||||
// TODO: put auth in swapState
|
||||
s.alice.auth.Value = big.NewInt(int64(amount))
|
||||
defer func() {
|
||||
s.alice.auth.Value = nil
|
||||
}()
|
||||
|
||||
address, _, swap, err := swap.DeploySwap(s.alice.auth, s.alice.ethClient, pkb, pka, defaultTimeoutDuration)
|
||||
address, tx, swap, err := swap.DeploySwap(s.alice.auth, s.alice.ethClient, hb, ha, s.bobAddress, defaultTimeoutDuration)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
receipt, err := s.alice.ethClient.TransactionReceipt(s.ctx, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
log.Debugf("deployed Swap.sol, gas used=%d", receipt.CumulativeGasUsed)
|
||||
|
||||
balance, err := s.alice.ethClient.BalanceAt(s.ctx, address, nil)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
@@ -192,15 +196,12 @@ func (s *swapState) watchForClaim() (<-chan *monero.PrivateKeyPair, error) { //n
|
||||
for {
|
||||
select {
|
||||
case claim := <-ch:
|
||||
if claim == nil || claim.S == nil {
|
||||
if claim == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// got Bob's secret
|
||||
sbBytes := claim.S.Bytes()
|
||||
var sb [32]byte
|
||||
copy(sb[:], sbBytes)
|
||||
|
||||
sb := claim.S
|
||||
skB, err := monero.NewPrivateSpendKey(sb[:])
|
||||
if err != nil {
|
||||
log.Error("failed to convert Bob's secret into a key: ", err)
|
||||
@@ -233,7 +234,8 @@ func (s *swapState) watchForClaim() (<-chan *monero.PrivateKeyPair, error) { //n
|
||||
// If time t_1 passes and Claim() has not been called, Alice should call Refund().
|
||||
func (s *swapState) refund() (string, error) {
|
||||
secret := s.privkeys.SpendKeyBytes()
|
||||
sc := big.NewInt(0).SetBytes(secret)
|
||||
var sc [32]byte
|
||||
copy(sc[:], secret)
|
||||
|
||||
log.Infof("attempting to call Refund()...")
|
||||
tx, err := s.contract.Refund(s.alice.auth, sc)
|
||||
@@ -241,6 +243,12 @@ func (s *swapState) refund() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
receipt, err := s.alice.ethClient.TransactionReceipt(s.ctx, tx.Hash())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("called Refund(), gas used=%d", receipt.CumulativeGasUsed)
|
||||
return tx.Hash().String(), nil
|
||||
}
|
||||
|
||||
@@ -249,7 +257,6 @@ func (s *swapState) createMoneroWallet(kpAB *monero.PrivateKeyPair) (monero.Addr
|
||||
t := time.Now().Format("2006-Jan-2-15:04:05")
|
||||
walletName := fmt.Sprintf("alice-swap-wallet-%s", t)
|
||||
if err := s.alice.client.GenerateFromKeys(kpAB, walletName, ""); err != nil {
|
||||
// TODO: save the keypair on disk!!! otherwise we lose the keys
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -292,12 +299,9 @@ func (s *swapState) handleNotifyClaimed(txHash string) (monero.Address, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debug("got Bob's secret: ", hex.EncodeToString(res[0].(*big.Int).Bytes()))
|
||||
|
||||
// got Bob's secret
|
||||
sbBytes := res[0].(*big.Int).Bytes()
|
||||
var sb [32]byte
|
||||
copy(sb[:], sbBytes)
|
||||
sb := res[0].([32]byte)
|
||||
log.Debug("got Bob's secret: ", hex.EncodeToString(sb[:]))
|
||||
|
||||
skB, err := monero.NewPrivateSpendKey(sb[:])
|
||||
if err != nil {
|
||||
@@ -306,7 +310,7 @@ func (s *swapState) handleNotifyClaimed(txHash string) (monero.Address, error) {
|
||||
}
|
||||
|
||||
skAB := monero.SumPrivateSpendKeys(skB, s.privkeys.SpendKey())
|
||||
vkAB := monero.SumPrivateViewKeys(s.bobViewKey, s.privkeys.ViewKey())
|
||||
vkAB := monero.SumPrivateViewKeys(s.bobPrivateViewKey, s.privkeys.ViewKey())
|
||||
kpAB := monero.NewPrivateKeyPair(skAB, vkAB)
|
||||
|
||||
// write keys to file in case something goes wrong
|
||||
|
||||
@@ -2,6 +2,7 @@ package alice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -9,12 +10,16 @@ import (
|
||||
"github.com/noot/atomic-swap/monero"
|
||||
"github.com/noot/atomic-swap/net"
|
||||
"github.com/noot/atomic-swap/swap-contract"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var nextID uint64 = 0
|
||||
|
||||
var (
|
||||
errMissingKeys = errors.New("did not receive Bob's public spend or private view key")
|
||||
errMissingKeys = errors.New("did not receive Bob's public spend or private view key")
|
||||
errMissingSpendKeyHash = errors.New("did not receive Bob's spend key hash")
|
||||
errMissingAddress = errors.New("did not receive Bob's address")
|
||||
)
|
||||
|
||||
// swapState is an instance of a swap. it holds the info needed for the swap,
|
||||
@@ -33,8 +38,10 @@ type swapState struct {
|
||||
pubkeys *monero.PublicKeyPair
|
||||
|
||||
// Bob's keys for this session
|
||||
bobSpendKey *monero.PublicKey
|
||||
bobViewKey *monero.PrivateViewKey
|
||||
bobPublicSpendKey *monero.PublicKey
|
||||
bobPrivateViewKey *monero.PrivateViewKey
|
||||
bobClaimHash [32]byte
|
||||
bobAddress ethcommon.Address
|
||||
|
||||
// swap contract and timeouts in it; set once contract is deployed
|
||||
contract *swap.Swap
|
||||
@@ -76,9 +83,12 @@ func (s *swapState) SendKeysMessage() (*net.SendKeysMessage, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sh := s.privkeys.SpendKey().Hash()
|
||||
|
||||
return &net.SendKeysMessage{
|
||||
PublicSpendKey: kp.SpendKey().Hex(),
|
||||
PublicViewKey: kp.ViewKey().Hex(),
|
||||
PrivateViewKey: s.privkeys.ViewKey().Hex(),
|
||||
SpendKeyHash: hex.EncodeToString(sh[:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -212,7 +222,30 @@ func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) (net.Message
|
||||
return nil, errMissingKeys
|
||||
}
|
||||
|
||||
log.Debug("got Bob's keys")
|
||||
if msg.SpendKeyHash == "" {
|
||||
return nil, errMissingSpendKeyHash
|
||||
}
|
||||
|
||||
if msg.EthAddress == "" {
|
||||
return nil, errMissingAddress
|
||||
}
|
||||
|
||||
// TODO: check that hash can be derived to view key
|
||||
|
||||
hb, err := hex.DecodeString(msg.SpendKeyHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(hb) != 32 {
|
||||
return nil, errors.New("invalid spend key hash")
|
||||
}
|
||||
|
||||
copy(s.bobClaimHash[:], hb)
|
||||
|
||||
s.bobAddress = ethcommon.HexToAddress(msg.EthAddress)
|
||||
|
||||
log.Debug("got Bob's keys and address: address=%s", s.bobAddress)
|
||||
s.nextExpectedMessage = &net.NotifyXMRLock{}
|
||||
|
||||
sk, err := monero.NewPublicKeyFromHex(msg.PublicSpendKey)
|
||||
|
||||
@@ -10,9 +10,12 @@ import (
|
||||
"github.com/noot/atomic-swap/monero"
|
||||
"github.com/noot/atomic-swap/net"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ = logging.SetLogLevel("alice", "debug")
|
||||
|
||||
type mockNet struct {
|
||||
msg net.Message
|
||||
}
|
||||
@@ -46,14 +49,16 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage(t *testing.T) {
|
||||
msg = &net.SendKeysMessage{
|
||||
PublicSpendKey: bobPrivKeys.SpendKey().Public().Hex(),
|
||||
PrivateViewKey: bobPrivKeys.ViewKey().Hex(),
|
||||
SpendKeyHash: "17e799afa82d5210fd6d41e1b1cb64784c10d72a34ada97807a4533a30627f01",
|
||||
EthAddress: "0x",
|
||||
}
|
||||
resp, done, err := s.HandleProtocolMessage(msg)
|
||||
require.NoError(t, err)
|
||||
require.False(t, done)
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, time.Second*time.Duration(defaultTimeoutDuration.Int64()), s.t1.Sub(s.t0))
|
||||
require.Equal(t, bobPrivKeys.SpendKey().Public().Hex(), s.bobSpendKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.ViewKey().Hex(), s.bobViewKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.SpendKey().Public().Hex(), s.bobPublicSpendKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.ViewKey().Hex(), s.bobPrivateViewKey.Hex())
|
||||
}
|
||||
|
||||
func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
|
||||
@@ -77,6 +82,8 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
|
||||
msg := &net.SendKeysMessage{
|
||||
PublicSpendKey: bobPrivKeys.SpendKey().Public().Hex(),
|
||||
PrivateViewKey: bobPrivKeys.ViewKey().Hex(),
|
||||
SpendKeyHash: "17e799afa82d5210fd6d41e1b1cb64784c10d72a34ada97807a4533a30627f01",
|
||||
EthAddress: "0x",
|
||||
}
|
||||
|
||||
resp, done, err := s.HandleProtocolMessage(msg)
|
||||
@@ -85,13 +92,14 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, net.NotifyContractDeployedType, resp.Type())
|
||||
require.Equal(t, time.Second*time.Duration(defaultTimeoutDuration.Int64()), s.t1.Sub(s.t0))
|
||||
require.Equal(t, bobPrivKeys.SpendKey().Public().Hex(), s.bobSpendKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.ViewKey().Hex(), s.bobViewKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.SpendKey().Public().Hex(), s.bobPublicSpendKey.Hex())
|
||||
require.Equal(t, bobPrivKeys.ViewKey().Hex(), s.bobPrivateViewKey.Hex())
|
||||
|
||||
// ensure we refund before t0
|
||||
time.Sleep(time.Second * 2)
|
||||
require.NotNil(t, s.net.(*mockNet).msg)
|
||||
require.Equal(t, net.NotifyRefundType, s.net.(*mockNet).msg.Type())
|
||||
// TODO: check balance
|
||||
}
|
||||
|
||||
func TestSwapState_NotifyXMRLock(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package bob
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
@@ -37,6 +36,7 @@ type bob struct {
|
||||
ethPrivKey *ecdsa.PrivateKey
|
||||
auth *bind.TransactOpts
|
||||
callOpts *bind.CallOpts
|
||||
ethAddress ethcommon.Address
|
||||
|
||||
net net.MessageSender
|
||||
|
||||
@@ -63,6 +63,7 @@ func NewBob(ctx context.Context, moneroEndpoint, moneroDaemonEndpoint, ethEndpoi
|
||||
}
|
||||
|
||||
pub := pk.Public().(*ecdsa.PublicKey)
|
||||
addr := crypto.PubkeyToAddress(*pub)
|
||||
|
||||
return &bob{
|
||||
ctx: ctx,
|
||||
@@ -72,9 +73,10 @@ func NewBob(ctx context.Context, moneroEndpoint, moneroDaemonEndpoint, ethEndpoi
|
||||
ethPrivKey: pk,
|
||||
auth: auth,
|
||||
callOpts: &bind.CallOpts{
|
||||
From: crypto.PubkeyToAddress(*pub),
|
||||
From: addr,
|
||||
Context: ctx,
|
||||
},
|
||||
ethAddress: addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -191,10 +193,7 @@ func (s *swapState) watchForRefund() (<-chan *monero.PrivateKeyPair, error) { //
|
||||
}
|
||||
|
||||
// got Alice's secret
|
||||
saBytes := refund.S.Bytes()
|
||||
var sa [32]byte
|
||||
copy(sa[:], saBytes)
|
||||
|
||||
sa := refund.S
|
||||
skA, err := monero.NewPrivateSpendKey(sa[:])
|
||||
if err != nil {
|
||||
log.Info("failed to convert Alice's secret into a key: %w", err)
|
||||
@@ -278,7 +277,8 @@ func (s *swapState) claimFunds() (string, error) {
|
||||
|
||||
// call swap.Swap.Claim() w/ b.privkeys.sk, revealing Bob's secret spend key
|
||||
secret := s.privkeys.SpendKeyBytes()
|
||||
sc := big.NewInt(0).SetBytes(secret)
|
||||
var sc [32]byte
|
||||
copy(sc[:], secret)
|
||||
|
||||
tx, err := s.contract.Claim(s.bob.auth, sc)
|
||||
if err != nil {
|
||||
@@ -286,16 +286,18 @@ func (s *swapState) claimFunds() (string, error) {
|
||||
}
|
||||
|
||||
log.Info("success! Bob claimed funds")
|
||||
log.Info("tx hash: ", tx.Hash())
|
||||
log.Info("tx hash=%s", tx.Hash())
|
||||
|
||||
receipt, err := s.bob.ethClient.TransactionReceipt(s.ctx, tx.Hash())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//log.Info("tx logs: ", fmt.Sprintf("0x%x", receipt.Logs[0].Data))
|
||||
log.Info("included in block number: ", receipt.Logs[0].BlockNumber)
|
||||
log.Info("secret: ", fmt.Sprintf("%x", secret))
|
||||
log.Infof("included in block number=%d gas used=%d s_a=%x",
|
||||
receipt.Logs[0].BlockNumber,
|
||||
receipt.CumulativeGasUsed,
|
||||
secret,
|
||||
)
|
||||
|
||||
balance, err = s.bob.ethClient.BalanceAt(s.ctx, addr, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -43,7 +42,8 @@ type swapState struct {
|
||||
t0, t1 time.Time
|
||||
|
||||
// Alice's keys for this session
|
||||
alicePublicKeys *monero.PublicKeyPair
|
||||
alicePublicKeys *monero.PublicKeyPair
|
||||
alicePrivateViewKey *monero.PrivateViewKey
|
||||
|
||||
// next expected network message
|
||||
nextExpectedMessage net.Message
|
||||
@@ -79,9 +79,13 @@ func (s *swapState) SendKeysMessage() (*net.SendKeysMessage, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sh := s.privkeys.SpendKey().Hash()
|
||||
|
||||
return &net.SendKeysMessage{
|
||||
PublicSpendKey: sk.Hex(),
|
||||
PrivateViewKey: vk.Hex(),
|
||||
SpendKeyHash: hex.EncodeToString(sh[:]),
|
||||
EthAddress: s.bob.ethAddress.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -247,14 +251,27 @@ func (s *swapState) HandleProtocolMessage(msg net.Message) (net.Message, bool, e
|
||||
}
|
||||
|
||||
func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) error {
|
||||
if msg.PublicSpendKey == "" || msg.PublicViewKey == "" {
|
||||
if msg.PublicSpendKey == "" || msg.PrivateViewKey == "" {
|
||||
return errMissingKeys
|
||||
}
|
||||
|
||||
if msg.SpendKeyHash == "" {
|
||||
return errors.New("did not receive SpendKeyHash")
|
||||
}
|
||||
|
||||
// TODO: verify hash derives view key, and that view only wallet can be generated
|
||||
|
||||
log.Debug("got Alice's public keys")
|
||||
s.nextExpectedMessage = &net.NotifyContractDeployed{}
|
||||
|
||||
kp, err := monero.NewPublicKeyPairFromHex(msg.PublicSpendKey, msg.PublicViewKey)
|
||||
vk, err := monero.NewPrivateViewKeyFromHex(msg.PrivateViewKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate Alice's private view key: %w", err)
|
||||
}
|
||||
|
||||
s.alicePrivateViewKey = vk
|
||||
|
||||
kp, err := monero.NewPublicKeyPairFromHex(msg.PublicSpendKey, vk.Public().Hex())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate Alice's public keys: %w", err)
|
||||
}
|
||||
@@ -284,13 +301,10 @@ func (s *swapState) handleRefund(txHash string) (monero.Address, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debug("got Alice's secret: ", hex.EncodeToString(res[0].(*big.Int).Bytes()))
|
||||
sa := res[0].([32]byte)
|
||||
log.Debug("got Alice's secret: ", hex.EncodeToString(sa[:]))
|
||||
|
||||
// got Alice's secret
|
||||
sbBytes := res[0].(*big.Int).Bytes()
|
||||
var sa [32]byte
|
||||
copy(sa[:], sbBytes)
|
||||
|
||||
skA, err := monero.NewPrivateSpendKey(sa[:])
|
||||
if err != nil {
|
||||
log.Errorf("failed to convert Alice's secret into a key: %s", err)
|
||||
|
||||
@@ -15,9 +15,12 @@ import (
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ = logging.SetLogLevel("bob", "debug")
|
||||
|
||||
type mockNet struct {
|
||||
msg net.Message
|
||||
}
|
||||
@@ -36,10 +39,7 @@ func newTestBob(t *testing.T) (*bob, *swapState) {
|
||||
bobAddr, err := bob.client.GetAddress(0)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = bob.daemonClient.GenerateBlocks(bobAddr.Address, 61)
|
||||
require.NoError(t, err)
|
||||
|
||||
//time.Sleep(time.Second * 5)
|
||||
_ = bob.daemonClient.GenerateBlocks(bobAddr.Address, 61)
|
||||
|
||||
swapState := newSwapState(bob, 33, 33)
|
||||
return bob, swapState
|
||||
@@ -70,9 +70,8 @@ func TestSwapState_ClaimFunds(t *testing.T) {
|
||||
bob.auth, err = bind.NewKeyedTransactorWithChainID(pkBob, big.NewInt(common.GanacheChainID))
|
||||
require.NoError(t, err)
|
||||
|
||||
var pubkey [32]byte
|
||||
copy(pubkey[:], swapState.pubkeys.SpendKey().Bytes())
|
||||
swapState.contractAddr, _, swapState.contract, err = swap.DeploySwap(bob.auth, conn, pubkey, [32]byte{}, defaultTimeoutDuration)
|
||||
claimHash := swapState.privkeys.SpendKey().Hash()
|
||||
swapState.contractAddr, _, swapState.contract, err = swap.DeploySwap(bob.auth, conn, claimHash, [32]byte{}, bob.ethAddress, defaultTimeoutDuration)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = swapState.contract.SetReady(bob.auth)
|
||||
@@ -96,7 +95,8 @@ func TestSwapState_handleSendKeysMessage(t *testing.T) {
|
||||
|
||||
msg = &net.SendKeysMessage{
|
||||
PublicSpendKey: alicePrivKeys.SpendKey().Public().Hex(),
|
||||
PublicViewKey: alicePrivKeys.ViewKey().Public().Hex(),
|
||||
PrivateViewKey: alicePrivKeys.ViewKey().Hex(),
|
||||
SpendKeyHash: "01fc704e28a5323372019db199a1c9adfd460eee382f3ee582371323ba62b62e",
|
||||
}
|
||||
|
||||
err = s.handleSendKeysMessage(msg)
|
||||
@@ -106,15 +106,14 @@ func TestSwapState_handleSendKeysMessage(t *testing.T) {
|
||||
require.Equal(t, alicePubKeys.ViewKey().Hex(), s.alicePublicKeys.ViewKey().Hex())
|
||||
}
|
||||
|
||||
func deploySwap(t *testing.T, bob *bob, swapState *swapState, timeout time.Duration) (ethcommon.Address, *swap.Swap) {
|
||||
func deploySwap(t *testing.T, bob *bob, swapState *swapState, refundHash [32]byte, timeout time.Duration) (ethcommon.Address, *swap.Swap) {
|
||||
conn, err := ethclient.Dial(common.DefaultEthEndpoint)
|
||||
require.NoError(t, err)
|
||||
|
||||
tm := big.NewInt(int64(timeout.Seconds()))
|
||||
|
||||
var pubkey [32]byte
|
||||
copy(pubkey[:], swapState.pubkeys.SpendKey().Bytes())
|
||||
addr, _, contract, err := swap.DeploySwap(bob.auth, conn, pubkey, [32]byte{}, tm)
|
||||
claimHash := swapState.privkeys.SpendKey().Hash()
|
||||
addr, _, contract, err := swap.DeploySwap(bob.auth, conn, claimHash, refundHash, bob.ethAddress, tm)
|
||||
require.NoError(t, err)
|
||||
return addr, contract
|
||||
}
|
||||
@@ -138,7 +137,7 @@ func TestSwapState_HandleProtocolMessage_NotifyContractDeployed_ok(t *testing.T)
|
||||
|
||||
duration, err := time.ParseDuration("2s")
|
||||
require.NoError(t, err)
|
||||
addr, _ := deploySwap(t, bob, s, duration)
|
||||
addr, _ := deploySwap(t, bob, s, [32]byte{}, duration)
|
||||
|
||||
msg = &net.NotifyContractDeployed{
|
||||
Address: addr.String(),
|
||||
@@ -175,7 +174,7 @@ func TestSwapState_HandleProtocolMessage_NotifyContractDeployed_timeout(t *testi
|
||||
|
||||
duration, err := time.ParseDuration("1s")
|
||||
require.NoError(t, err)
|
||||
addr, _ := deploySwap(t, bob, s, duration)
|
||||
addr, _ := deploySwap(t, bob, s, [32]byte{}, duration)
|
||||
|
||||
msg = &net.NotifyContractDeployed{
|
||||
Address: addr.String(),
|
||||
@@ -202,10 +201,12 @@ func TestSwapState_HandleProtocolMessage_NotifyReady(t *testing.T) {
|
||||
_, _, err := s.generateKeys()
|
||||
require.NoError(t, err)
|
||||
|
||||
duration, err := time.ParseDuration("1s")
|
||||
duration, err := time.ParseDuration("10m")
|
||||
require.NoError(t, err)
|
||||
_, s.contract = deploySwap(t, bob, s, [32]byte{}, duration)
|
||||
|
||||
_, err = s.contract.SetReady(bob.auth)
|
||||
require.NoError(t, err)
|
||||
_, s.contract = deploySwap(t, bob, s, duration)
|
||||
time.Sleep(duration)
|
||||
|
||||
msg := &net.NotifyReady{}
|
||||
|
||||
@@ -230,7 +231,7 @@ func TestSwapState_handleRefund(t *testing.T) {
|
||||
duration, err := time.ParseDuration("10m")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, s.contract = deploySwap(t, bob, s, duration)
|
||||
_, s.contract = deploySwap(t, bob, s, aliceKeys.SpendKey().Hash(), duration)
|
||||
|
||||
// lock XMR
|
||||
_, err = s.lockFunds(s.providesAmount)
|
||||
@@ -238,7 +239,9 @@ func TestSwapState_handleRefund(t *testing.T) {
|
||||
|
||||
// call refund w/ Alice's spend key
|
||||
secret := aliceKeys.SpendKeyBytes()
|
||||
sc := big.NewInt(0).SetBytes(secret)
|
||||
var sc [32]byte
|
||||
copy(sc[:], secret)
|
||||
|
||||
tx, err := s.contract.Refund(s.bob.auth, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -259,7 +262,7 @@ func TestSwapState_HandleProtocolMessage_NotifyRefund(t *testing.T) {
|
||||
duration, err := time.ParseDuration("10m")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, s.contract = deploySwap(t, bob, s, duration)
|
||||
_, s.contract = deploySwap(t, bob, s, aliceKeys.SpendKey().Hash(), duration)
|
||||
|
||||
// lock XMR
|
||||
_, err = s.lockFunds(s.providesAmount)
|
||||
@@ -267,7 +270,9 @@ func TestSwapState_HandleProtocolMessage_NotifyRefund(t *testing.T) {
|
||||
|
||||
// call refund w/ Alice's spend key
|
||||
secret := aliceKeys.SpendKeyBytes()
|
||||
sc := big.NewInt(0).SetBytes(secret)
|
||||
var sc [32]byte
|
||||
copy(sc[:], secret)
|
||||
|
||||
tx, err := s.contract.Refund(s.bob.auth, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
60
docs/protocol.md
Normal file
60
docs/protocol.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Protocol
|
||||
|
||||
## Current version
|
||||
|
||||
See [this issue describing the update](https://github.com/noot/atomic-swap/issues/36).
|
||||
|
||||
```
|
||||
gas used now to deploy Swap.sol: 640005
|
||||
gas used previously to deploy Swap.sol: 1855645
|
||||
improvement: ~2.9x
|
||||
|
||||
gas used now for the Claim() or Refund() call: 14729
|
||||
gas used previously for the Claim() or Refund() call: 938818
|
||||
improvement: ~64x
|
||||
```
|
||||
|
||||
## Initial version
|
||||
|
||||
Alice has ETH and wants XMR, Bob has XMR and wants ETH. They come to an agreement to do the swap and the amounts they will swap.
|
||||
|
||||
#### Initial (offchain) phase
|
||||
- Alice and Bob each generate Monero secret keys (which consist of secret spend and view keys): (`s_a`, `v_a`) and (`s_b`, `v_b`), which are used to construct valid points on the ed25519 curve (ie. public keys): `P_a` and `P_b` accordingly. Alice sends Bob her public key and Bob sends Alice his public spend key and private view key. Note: The XMR will be locked in the account with address corresponding to the public key `P_a + P_b`. Bob needs to send his private view key so Alice can check that Bob actually locked the amount of XMR he claims he will.
|
||||
|
||||
#### Step 1.
|
||||
Alice deploys a smart contract on Ethereum and locks her ETH in it. The contract has the following properties:
|
||||
- it is non-destructible
|
||||
|
||||
- it contains two timestamps, `t_0` and `t_1`, before and after which different actions are authorized.
|
||||
|
||||
- it is constructed containing `P_a` and`P_b`, so that if Alice or Bob reveals their secret by calling the contract, the contract will verify that the secret corresponds to the expected public key that it was initalized with.
|
||||
|
||||
- it has a `Ready()` function which can only be called by Alice. Once `Ready()` is invoked, Bob can proceed with redeeming his ether. Alice has until the `t_0` timestamp to call `Ready()` - once `t_0` passes, then the contract automatically allows Bob to claim his ether, up until some second timestamp `t_1`.
|
||||
|
||||
- it has a `Claim()` function which can only be called by Bob after `Ready()` is called or `t_0` passes, up until the timestamp `t_1`. After `t_1`, Bob can no longer claim the ETH.
|
||||
|
||||
- `Claim()` takes one parameter from Bob: `s_b`. Once `Claim()` is called, the ETH is transferred to Bob, and simultaneously Bob reveals his secret and thus Alice can claim her XMR by combining her and Bob's secrets.
|
||||
|
||||
- it has a `Refund()` function that can only be called by Alice and only before `Ready()` is called *or* `t_0` is reached. Once `Ready()` is invoked, Alice can no longer call `Refund()` until the next timestamp `t_1`. If Bob doesn't claim his ether by `t_1`, then `Refund()` can be called by Alice once again.
|
||||
|
||||
- `Refund()` takes one parameter from Alice: `s_a`. This allows Alice to get her ETH back in case Bob goes offline, but it simulteneously reveals her secret, allowing Bob to regain access to the XMR he locked.
|
||||
|
||||
#### Step 2.
|
||||
Bob sees the smart contract has been deployed with the correct parameters. He sends his XMR to an account address constructed from `P_a + P_b`. Thus, the funds can only be accessed by an entity having both `s_a` and `s_b`, as the secret spend key to that account is `s_a + s_b`. The funds are viewable by someone having `v_a + v_b`.
|
||||
|
||||
Note: `Refund()` and `Claim()` cannot be called at the same time. This is to prevent the case of front-running where, for example, Bob tries to claim, so his secret `s_b` is in the mempool, and then Alice tries to call `Refund()` with a higher priority while also transferring the XMR in the account controlled by `s_a + s_b`. If her call goes through before Bob's and Bob doesn't notice this happening in time, then Alice will now have *both* the ETH and the XMR. Due to this case, Alice and Bob should not call `Refund()` or `Claim()` when they are approaching `t_0` or `t_1` respectively, as their transaction may not go through in time.
|
||||
|
||||
#### Step 3.
|
||||
Alice sees that the XMR has been locked, and the amount is correct (as she knows `v_a` and Bob send her `v_b` in the first key exchange step). She calls `Ready()` on the smart contract if the XMR has been locked. If the amount of XMR locked is incorrect, Alice calls `Refund()` to abort the swap and reclaim her ETH.
|
||||
|
||||
From this point on, Bob can redeem his ether by calling `Claim(s_b)`, which transfers the ETH to him.
|
||||
|
||||
By redeeming, Bob reveals his secret. Now Alice is the only one that has both `s_a` and `s_b` and she can access the monero in the account created from `P_a + P_b`.
|
||||
|
||||
#### What could go wrong
|
||||
|
||||
- **Alice locked her ETH, but Bob doesn't lock his XMR**. Alice has until time `t_0` to call `Refund()` to reclaim her ETH, which she should do if `t_0` is soon.
|
||||
|
||||
- **Alice called `Ready()`, but Bob never redeems.** Deadlocks are prevented thanks to a second timelock `t_1`, which re-enables Alice to call refund after it, while disabling Bob's ability to claim.
|
||||
|
||||
- **Alice never calls `ready` within `t_0`**. Bob can still claim his ETH by waiting until after `t_0` has passed, as the contract automatically allows him to call `Claim()`.
|
||||
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Adapted from https://github.com/witnet/elliptic-curve-solidity/blob/master/examples/Secp256k1.sol
|
||||
// This file is not being used as of the final Hackathon submission
|
||||
|
||||
pragma solidity ^0.8.5;
|
||||
|
||||
import "./EllipticCurve.sol";
|
||||
|
||||
/**
|
||||
* @title Ed25519 Elliptic Curve
|
||||
* @notice Particularization of Elliptic Curve for ed25519 curve
|
||||
*/
|
||||
contract Ed25519 {
|
||||
uint256 public constant GX =
|
||||
15112221349535400772501151409588531511454012693041857206046113283949847762202;
|
||||
uint256 public constant GY =
|
||||
46316835694926478169428394003475163141307993866256225615783033603165251855960;
|
||||
uint256 public constant AA =
|
||||
37095705934669439343138083508754565189542113879843219016388785533085940283555;
|
||||
uint256 public constant PP =
|
||||
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED;
|
||||
|
||||
/// @notice Public Key derivation from private key
|
||||
/// @param privKey The private key
|
||||
/// @return (qx, qy) The Public Key
|
||||
function derivePubKey(uint256 privKey)
|
||||
external
|
||||
pure
|
||||
returns (uint256, uint256)
|
||||
{
|
||||
return EllipticCurve.ecMul(privKey, GX, GY, AA, PP);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Source https://github.com/javgh/ed25519-solidity
|
||||
|
||||
pragma solidity ^0.8.5;
|
||||
|
||||
// Using formulas from https://hyperelliptic.org/EFD/g1p/auto-twisted-projective.html
|
||||
// and constants from https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03
|
||||
|
||||
contract Ed25519 {
|
||||
uint constant q = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED;
|
||||
uint constant d = 37095705934669439343138083508754565189542113879843219016388785533085940283555;
|
||||
// = -(121665/121666)
|
||||
uint constant Bx = 15112221349535400772501151409588531511454012693041857206046113283949847762202;
|
||||
uint constant By = 46316835694926478169428394003475163141307993866256225615783033603165251855960;
|
||||
|
||||
struct Point {
|
||||
uint x;
|
||||
uint y;
|
||||
uint z;
|
||||
}
|
||||
|
||||
struct Scratchpad {
|
||||
uint a;
|
||||
uint b;
|
||||
uint c;
|
||||
uint d;
|
||||
uint e;
|
||||
uint f;
|
||||
uint g;
|
||||
uint h;
|
||||
}
|
||||
|
||||
function inv(uint a) internal view returns (uint invA) {
|
||||
uint e = q - 2;
|
||||
uint m = q;
|
||||
|
||||
// use bigModExp precompile
|
||||
assembly {
|
||||
let p := mload(0x40)
|
||||
mstore(p, 0x20)
|
||||
mstore(add(p, 0x20), 0x20)
|
||||
mstore(add(p, 0x40), 0x20)
|
||||
mstore(add(p, 0x60), a)
|
||||
mstore(add(p, 0x80), e)
|
||||
mstore(add(p, 0xa0), m)
|
||||
if iszero(staticcall(not(0), 0x05, p, 0xc0, p, 0x20)) {
|
||||
revert(0, 0)
|
||||
}
|
||||
invA := mload(p)
|
||||
}
|
||||
}
|
||||
|
||||
function ecAdd(Point memory p1,
|
||||
Point memory p2) internal pure returns (Point memory p3) {
|
||||
Scratchpad memory tmp;
|
||||
|
||||
tmp.a = mulmod(p1.z, p2.z, q);
|
||||
tmp.b = mulmod(tmp.a, tmp.a, q);
|
||||
tmp.c = mulmod(p1.x, p2.x, q);
|
||||
tmp.d = mulmod(p1.y, p2.y, q);
|
||||
tmp.e = mulmod(d, mulmod(tmp.c, tmp.d, q), q);
|
||||
tmp.f = addmod(tmp.b, q - tmp.e, q);
|
||||
tmp.g = addmod(tmp.b, tmp.e, q);
|
||||
p3.x = mulmod(mulmod(tmp.a, tmp.f, q),
|
||||
addmod(addmod(mulmod(addmod(p1.x, p1.y, q),
|
||||
addmod(p2.x, p2.y, q), q),
|
||||
q - tmp.c, q), q - tmp.d, q), q);
|
||||
p3.y = mulmod(mulmod(tmp.a, tmp.g, q),
|
||||
addmod(tmp.d, tmp.c, q), q);
|
||||
p3.z = mulmod(tmp.f, tmp.g, q);
|
||||
}
|
||||
|
||||
function ecDouble(Point memory p1) internal pure returns (Point memory p2) {
|
||||
Scratchpad memory tmp;
|
||||
|
||||
tmp.a = addmod(p1.x, p1.y, q);
|
||||
tmp.b = mulmod(tmp.a, tmp.a, q);
|
||||
tmp.c = mulmod(p1.x, p1.x, q);
|
||||
tmp.d = mulmod(p1.y, p1.y, q);
|
||||
tmp.e = q - tmp.c;
|
||||
tmp.f = addmod(tmp.e, tmp.d, q);
|
||||
tmp.h = mulmod(p1.z, p1.z, q);
|
||||
tmp.g = addmod(tmp.f, q - mulmod(2, tmp.h, q), q);
|
||||
p2.x = mulmod(addmod(addmod(tmp.b, q - tmp.c, q), q - tmp.d, q),
|
||||
tmp.g, q);
|
||||
p2.y = mulmod(tmp.f, addmod(tmp.e, q - tmp.d, q), q);
|
||||
p2.z = mulmod(tmp.f, tmp.g, q);
|
||||
}
|
||||
|
||||
function scalarMultBase(uint s) public view returns (uint, uint) {
|
||||
Point memory b;
|
||||
Point memory result;
|
||||
b.x = Bx;
|
||||
b.y = By;
|
||||
b.z = 1;
|
||||
result.x = 0;
|
||||
result.y = 1;
|
||||
result.z = 1;
|
||||
|
||||
while (s > 0) {
|
||||
if (s & 1 == 1) { result = ecAdd(result, b); }
|
||||
s = s >> 1;
|
||||
b = ecDouble(b);
|
||||
}
|
||||
|
||||
uint invZ = inv(result.z);
|
||||
result.x = mulmod(result.x, invZ, q);
|
||||
result.y = mulmod(result.y, invZ, q);
|
||||
|
||||
return (result.x, result.y);
|
||||
}
|
||||
}
|
||||
@@ -1,429 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Source https://github.com/witnet/elliptic-curve-solidity/blob/master/contracts/EllipticCurve.sol
|
||||
// This file is not being used as of the final Hackathon submission
|
||||
|
||||
pragma solidity ^0.8.5;
|
||||
|
||||
|
||||
/**
|
||||
* @title Elliptic Curve Library
|
||||
* @dev Library providing arithmetic operations over elliptic curves.
|
||||
* This library does not check whether the inserted points belong to the curve
|
||||
* `isOnCurve` function should be used by the library user to check the aforementioned statement.
|
||||
* @author Witnet Foundation
|
||||
*/
|
||||
library EllipticCurve {
|
||||
|
||||
// Pre-computed constant for 2 ** 255
|
||||
uint256 constant private U255_MAX_PLUS_1 = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
|
||||
|
||||
/// @dev Modular euclidean inverse of a number (mod p).
|
||||
/// @param _x The number
|
||||
/// @param _pp The modulus
|
||||
/// @return q such that x*q = 1 (mod _pp)
|
||||
function invMod(uint256 _x, uint256 _pp) internal pure returns (uint256) {
|
||||
require(_x != 0 && _x != _pp && _pp != 0, "Invalid number");
|
||||
uint256 q = 0;
|
||||
uint256 newT = 1;
|
||||
uint256 r = _pp;
|
||||
uint256 t;
|
||||
while (_x != 0) {
|
||||
t = r / _x;
|
||||
(q, newT) = (newT, addmod(q, (_pp - mulmod(t, newT, _pp)), _pp));
|
||||
(r, _x) = (_x, r - t * _x);
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
/// @dev Modular exponentiation, b^e % _pp.
|
||||
/// Source: https://github.com/androlo/standard-contracts/blob/master/contracts/src/crypto/ECCMath.sol
|
||||
/// @param _base base
|
||||
/// @param _exp exponent
|
||||
/// @param _pp modulus
|
||||
/// @return r such that r = b**e (mod _pp)
|
||||
function expMod(uint256 _base, uint256 _exp, uint256 _pp) internal pure returns (uint256) {
|
||||
require(_pp!=0, "Modulus is zero");
|
||||
|
||||
if (_base == 0)
|
||||
return 0;
|
||||
if (_exp == 0)
|
||||
return 1;
|
||||
|
||||
uint256 r = 1;
|
||||
uint256 bit = U255_MAX_PLUS_1;
|
||||
assembly {
|
||||
for { } gt(bit, 0) { }{
|
||||
r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, bit)))), _pp)
|
||||
r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 2))))), _pp)
|
||||
r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 4))))), _pp)
|
||||
r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 8))))), _pp)
|
||||
bit := div(bit, 16)
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// @dev Converts a point (x, y, z) expressed in Jacobian coordinates to affine coordinates (x', y', 1).
|
||||
/// @param _x coordinate x
|
||||
/// @param _y coordinate y
|
||||
/// @param _z coordinate z
|
||||
/// @param _pp the modulus
|
||||
/// @return (x', y') affine coordinates
|
||||
function toAffine(
|
||||
uint256 _x,
|
||||
uint256 _y,
|
||||
uint256 _z,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256, uint256)
|
||||
{
|
||||
uint256 zInv = invMod(_z, _pp);
|
||||
uint256 zInv2 = mulmod(zInv, zInv, _pp);
|
||||
uint256 x2 = mulmod(_x, zInv2, _pp);
|
||||
uint256 y2 = mulmod(_y, mulmod(zInv, zInv2, _pp), _pp);
|
||||
|
||||
return (x2, y2);
|
||||
}
|
||||
|
||||
/// @dev Derives the y coordinate from a compressed-format point x [[SEC-1]](https://www.secg.org/SEC1-Ver-1.0.pdf).
|
||||
/// @param _prefix parity byte (0x02 even, 0x03 odd)
|
||||
/// @param _x coordinate x
|
||||
/// @param _aa constant of curve
|
||||
/// @param _bb constant of curve
|
||||
/// @param _pp the modulus
|
||||
/// @return y coordinate y
|
||||
function deriveY(
|
||||
uint8 _prefix,
|
||||
uint256 _x,
|
||||
uint256 _aa,
|
||||
uint256 _bb,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256)
|
||||
{
|
||||
require(_prefix == 0x02 || _prefix == 0x03, "Invalid compressed EC point prefix");
|
||||
|
||||
// x^3 + ax + b
|
||||
uint256 y2 = addmod(mulmod(_x, mulmod(_x, _x, _pp), _pp), addmod(mulmod(_x, _aa, _pp), _bb, _pp), _pp);
|
||||
y2 = expMod(y2, (_pp + 1) / 4, _pp);
|
||||
// uint256 cmp = yBit ^ y_ & 1;
|
||||
uint256 y = (y2 + _prefix) % 2 == 0 ? y2 : _pp - y2;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
/// @dev Check whether point (x,y) is on curve defined by a, b, and _pp.
|
||||
/// @param _x coordinate x of P1
|
||||
/// @param _y coordinate y of P1
|
||||
/// @param _aa constant of curve
|
||||
/// @param _bb constant of curve
|
||||
/// @param _pp the modulus
|
||||
/// @return true if x,y in the curve, false else
|
||||
function isOnCurve(
|
||||
uint _x,
|
||||
uint _y,
|
||||
uint _aa,
|
||||
uint _bb,
|
||||
uint _pp)
|
||||
internal pure returns (bool)
|
||||
{
|
||||
if (0 == _x || _x >= _pp || 0 == _y || _y >= _pp) {
|
||||
return false;
|
||||
}
|
||||
// y^2
|
||||
uint lhs = mulmod(_y, _y, _pp);
|
||||
// x^3
|
||||
uint rhs = mulmod(mulmod(_x, _x, _pp), _x, _pp);
|
||||
if (_aa != 0) {
|
||||
// x^3 + a*x
|
||||
rhs = addmod(rhs, mulmod(_x, _aa, _pp), _pp);
|
||||
}
|
||||
if (_bb != 0) {
|
||||
// x^3 + a*x + b
|
||||
rhs = addmod(rhs, _bb, _pp);
|
||||
}
|
||||
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
/// @dev Calculate inverse (x, -y) of point (x, y).
|
||||
/// @param _x coordinate x of P1
|
||||
/// @param _y coordinate y of P1
|
||||
/// @param _pp the modulus
|
||||
/// @return (x, -y)
|
||||
function ecInv(
|
||||
uint256 _x,
|
||||
uint256 _y,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256, uint256)
|
||||
{
|
||||
return (_x, (_pp - _y) % _pp);
|
||||
}
|
||||
|
||||
/// @dev Add two points (x1, y1) and (x2, y2) in affine coordinates.
|
||||
/// @param _x1 coordinate x of P1
|
||||
/// @param _y1 coordinate y of P1
|
||||
/// @param _x2 coordinate x of P2
|
||||
/// @param _y2 coordinate y of P2
|
||||
/// @param _aa constant of the curve
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy) = P1+P2 in affine coordinates
|
||||
function ecAdd(
|
||||
uint256 _x1,
|
||||
uint256 _y1,
|
||||
uint256 _x2,
|
||||
uint256 _y2,
|
||||
uint256 _aa,
|
||||
uint256 _pp)
|
||||
internal pure returns(uint256, uint256)
|
||||
{
|
||||
uint x = 0;
|
||||
uint y = 0;
|
||||
uint z = 0;
|
||||
|
||||
// Double if x1==x2 else add
|
||||
if (_x1==_x2) {
|
||||
// y1 = -y2 mod p
|
||||
if (addmod(_y1, _y2, _pp) == 0) {
|
||||
return(0, 0);
|
||||
} else {
|
||||
// P1 = P2
|
||||
(x, y, z) = jacDouble(
|
||||
_x1,
|
||||
_y1,
|
||||
1,
|
||||
_aa,
|
||||
_pp);
|
||||
}
|
||||
} else {
|
||||
(x, y, z) = jacAdd(
|
||||
_x1,
|
||||
_y1,
|
||||
1,
|
||||
_x2,
|
||||
_y2,
|
||||
1,
|
||||
_pp);
|
||||
}
|
||||
// Get back to affine
|
||||
return toAffine(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
_pp);
|
||||
}
|
||||
|
||||
/// @dev Substract two points (x1, y1) and (x2, y2) in affine coordinates.
|
||||
/// @param _x1 coordinate x of P1
|
||||
/// @param _y1 coordinate y of P1
|
||||
/// @param _x2 coordinate x of P2
|
||||
/// @param _y2 coordinate y of P2
|
||||
/// @param _aa constant of the curve
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy) = P1-P2 in affine coordinates
|
||||
function ecSub(
|
||||
uint256 _x1,
|
||||
uint256 _y1,
|
||||
uint256 _x2,
|
||||
uint256 _y2,
|
||||
uint256 _aa,
|
||||
uint256 _pp)
|
||||
internal pure returns(uint256, uint256)
|
||||
{
|
||||
// invert square
|
||||
(uint256 x, uint256 y) = ecInv(_x2, _y2, _pp);
|
||||
// P1-square
|
||||
return ecAdd(
|
||||
_x1,
|
||||
_y1,
|
||||
x,
|
||||
y,
|
||||
_aa,
|
||||
_pp);
|
||||
}
|
||||
|
||||
/// @dev Multiply point (x1, y1, z1) times d in affine coordinates.
|
||||
/// @param _k scalar to multiply
|
||||
/// @param _x coordinate x of P1
|
||||
/// @param _y coordinate y of P1
|
||||
/// @param _aa constant of the curve
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy) = d*P in affine coordinates
|
||||
function ecMul(
|
||||
uint256 _k,
|
||||
uint256 _x,
|
||||
uint256 _y,
|
||||
uint256 _aa,
|
||||
uint256 _pp)
|
||||
internal pure returns(uint256, uint256)
|
||||
{
|
||||
// Jacobian multiplication
|
||||
(uint256 x1, uint256 y1, uint256 z1) = jacMul(
|
||||
_k,
|
||||
_x,
|
||||
_y,
|
||||
1,
|
||||
_aa,
|
||||
_pp);
|
||||
// Get back to affine
|
||||
return toAffine(
|
||||
x1,
|
||||
y1,
|
||||
z1,
|
||||
_pp);
|
||||
}
|
||||
|
||||
/// @dev Adds two points (x1, y1, z1) and (x2 y2, z2).
|
||||
/// @param _x1 coordinate x of P1
|
||||
/// @param _y1 coordinate y of P1
|
||||
/// @param _z1 coordinate z of P1
|
||||
/// @param _x2 coordinate x of square
|
||||
/// @param _y2 coordinate y of square
|
||||
/// @param _z2 coordinate z of square
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy, qz) P1+square in Jacobian
|
||||
function jacAdd(
|
||||
uint256 _x1,
|
||||
uint256 _y1,
|
||||
uint256 _z1,
|
||||
uint256 _x2,
|
||||
uint256 _y2,
|
||||
uint256 _z2,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256, uint256, uint256)
|
||||
{
|
||||
if (_x1==0 && _y1==0)
|
||||
return (_x2, _y2, _z2);
|
||||
if (_x2==0 && _y2==0)
|
||||
return (_x1, _y1, _z1);
|
||||
|
||||
// We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5
|
||||
uint[4] memory zs; // z1^2, z1^3, z2^2, z2^3
|
||||
zs[0] = mulmod(_z1, _z1, _pp);
|
||||
zs[1] = mulmod(_z1, zs[0], _pp);
|
||||
zs[2] = mulmod(_z2, _z2, _pp);
|
||||
zs[3] = mulmod(_z2, zs[2], _pp);
|
||||
|
||||
// u1, s1, u2, s2
|
||||
zs = [
|
||||
mulmod(_x1, zs[2], _pp),
|
||||
mulmod(_y1, zs[3], _pp),
|
||||
mulmod(_x2, zs[0], _pp),
|
||||
mulmod(_y2, zs[1], _pp)
|
||||
];
|
||||
|
||||
// In case of zs[0] == zs[2] && zs[1] == zs[3], double function should be used
|
||||
require(zs[0] != zs[2] || zs[1] != zs[3], "Use jacDouble function instead");
|
||||
|
||||
uint[4] memory hr;
|
||||
//h
|
||||
hr[0] = addmod(zs[2], _pp - zs[0], _pp);
|
||||
//r
|
||||
hr[1] = addmod(zs[3], _pp - zs[1], _pp);
|
||||
//h^2
|
||||
hr[2] = mulmod(hr[0], hr[0], _pp);
|
||||
// h^3
|
||||
hr[3] = mulmod(hr[2], hr[0], _pp);
|
||||
// qx = -h^3 -2u1h^2+r^2
|
||||
uint256 qx = addmod(mulmod(hr[1], hr[1], _pp), _pp - hr[3], _pp);
|
||||
qx = addmod(qx, _pp - mulmod(2, mulmod(zs[0], hr[2], _pp), _pp), _pp);
|
||||
// qy = -s1*z1*h^3+r(u1*h^2 -x^3)
|
||||
uint256 qy = mulmod(hr[1], addmod(mulmod(zs[0], hr[2], _pp), _pp - qx, _pp), _pp);
|
||||
qy = addmod(qy, _pp - mulmod(zs[1], hr[3], _pp), _pp);
|
||||
// qz = h*z1*z2
|
||||
uint256 qz = mulmod(hr[0], mulmod(_z1, _z2, _pp), _pp);
|
||||
return(qx, qy, qz);
|
||||
}
|
||||
|
||||
/// @dev Doubles a points (x, y, z).
|
||||
/// @param _x coordinate x of P1
|
||||
/// @param _y coordinate y of P1
|
||||
/// @param _z coordinate z of P1
|
||||
/// @param _aa the a scalar in the curve equation
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy, qz) 2P in Jacobian
|
||||
function jacDouble(
|
||||
uint256 _x,
|
||||
uint256 _y,
|
||||
uint256 _z,
|
||||
uint256 _aa,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256, uint256, uint256)
|
||||
{
|
||||
if (_z == 0)
|
||||
return (_x, _y, _z);
|
||||
|
||||
// We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5
|
||||
// Note: there is a bug in the paper regarding the m parameter, M=3*(x1^2)+a*(z1^4)
|
||||
// x, y, z at this point represent the squares of _x, _y, _z
|
||||
uint256 x = mulmod(_x, _x, _pp); //x1^2
|
||||
uint256 y = mulmod(_y, _y, _pp); //y1^2
|
||||
uint256 z = mulmod(_z, _z, _pp); //z1^2
|
||||
|
||||
// s
|
||||
uint s = mulmod(4, mulmod(_x, y, _pp), _pp);
|
||||
// m
|
||||
uint m = addmod(mulmod(3, x, _pp), mulmod(_aa, mulmod(z, z, _pp), _pp), _pp);
|
||||
|
||||
// x, y, z at this point will be reassigned and rather represent qx, qy, qz from the paper
|
||||
// This allows to reduce the gas cost and stack footprint of the algorithm
|
||||
// qx
|
||||
x = addmod(mulmod(m, m, _pp), _pp - addmod(s, s, _pp), _pp);
|
||||
// qy = -8*y1^4 + M(S-T)
|
||||
y = addmod(mulmod(m, addmod(s, _pp - x, _pp), _pp), _pp - mulmod(8, mulmod(y, y, _pp), _pp), _pp);
|
||||
// qz = 2*y1*z1
|
||||
z = mulmod(2, mulmod(_y, _z, _pp), _pp);
|
||||
|
||||
return (x, y, z);
|
||||
}
|
||||
|
||||
/// @dev Multiply point (x, y, z) times d.
|
||||
/// @param _d scalar to multiply
|
||||
/// @param _x coordinate x of P1
|
||||
/// @param _y coordinate y of P1
|
||||
/// @param _z coordinate z of P1
|
||||
/// @param _aa constant of curve
|
||||
/// @param _pp the modulus
|
||||
/// @return (qx, qy, qz) d*P1 in Jacobian
|
||||
function jacMul(
|
||||
uint256 _d,
|
||||
uint256 _x,
|
||||
uint256 _y,
|
||||
uint256 _z,
|
||||
uint256 _aa,
|
||||
uint256 _pp)
|
||||
internal pure returns (uint256, uint256, uint256)
|
||||
{
|
||||
// Early return in case that `_d == 0`
|
||||
if (_d == 0) {
|
||||
return (_x, _y, _z);
|
||||
}
|
||||
|
||||
uint256 remaining = _d;
|
||||
uint256 qx = 0;
|
||||
uint256 qy = 0;
|
||||
uint256 qz = 1;
|
||||
|
||||
// Double and add algorithm
|
||||
while (remaining != 0) {
|
||||
if ((remaining & 1) != 0) {
|
||||
(qx, qy, qz) = jacAdd(
|
||||
qx,
|
||||
qy,
|
||||
qz,
|
||||
_x,
|
||||
_y,
|
||||
_z,
|
||||
_pp);
|
||||
}
|
||||
remaining = remaining / 2;
|
||||
(_x, _y, _z) = jacDouble(
|
||||
_x,
|
||||
_y,
|
||||
_z,
|
||||
_aa,
|
||||
_pp);
|
||||
}
|
||||
return (qx, qy, qz);
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,18 @@
|
||||
|
||||
pragma solidity ^0.8.5;
|
||||
|
||||
// import "./Ed25519.sol";
|
||||
import "./Ed25519_alt.sol";
|
||||
|
||||
contract Swap {
|
||||
// Ed25519 library
|
||||
Ed25519 immutable ed25519;
|
||||
|
||||
// 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
|
||||
bytes32 public immutable pubKeyClaim;
|
||||
// address allowed to claim the ether in this contract
|
||||
address payable immutable claimer;
|
||||
|
||||
// the expected public key derived from the secret `s_a`.
|
||||
// this public key is a point on the ed25519 curve
|
||||
bytes32 public immutable pubKeyRefund;
|
||||
// the expected hash of the secret `s_b`.
|
||||
bytes32 public immutable claimHash;
|
||||
|
||||
// the expected hash of the secret `s_a`.
|
||||
bytes32 public immutable refundHash;
|
||||
|
||||
// timestamp (set at contract creation)
|
||||
// before which Alice can call either set_ready or refund
|
||||
@@ -31,19 +26,19 @@ contract Swap {
|
||||
// this prevents Bob from withdrawing funds without locking funds on the other chain first
|
||||
bool isReady = false;
|
||||
|
||||
event Constructed(bytes32 p);
|
||||
event Constructed(bytes32 claimHash, bytes32 refundHash);
|
||||
event IsReady(bool b);
|
||||
event Claimed(uint256 s);
|
||||
event Refunded(uint256 s);
|
||||
event Claimed(bytes32 s);
|
||||
event Refunded(bytes32 s);
|
||||
|
||||
constructor(bytes32 _pubKeyClaim, bytes32 _pubKeyRefund, uint256 _timeoutDuration) payable {
|
||||
constructor(bytes32 _claimHash, bytes32 _refundHash, address payable _claimer, uint256 _timeoutDuration) payable {
|
||||
owner = payable(msg.sender);
|
||||
pubKeyClaim = _pubKeyClaim;
|
||||
pubKeyRefund = _pubKeyRefund;
|
||||
claimHash = _claimHash;
|
||||
refundHash = _refundHash;
|
||||
claimer = _claimer;
|
||||
timeout_0 = block.timestamp + _timeoutDuration;
|
||||
timeout_1 = block.timestamp + (_timeoutDuration * 2);
|
||||
ed25519 = new Ed25519();
|
||||
emit Constructed(_pubKeyRefund);
|
||||
emit Constructed(claimHash, refundHash);
|
||||
}
|
||||
|
||||
// Alice must call set_ready() within t_0 once she verifies the XMR has been locked
|
||||
@@ -56,11 +51,11 @@ contract Swap {
|
||||
// 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 {
|
||||
function claim(bytes32 _s) external {
|
||||
require(msg.sender == claimer, "only claimer can claim!");
|
||||
require(block.timestamp < timeout_1 && (block.timestamp >= timeout_0 || isReady),
|
||||
"too late or early to claim!");
|
||||
|
||||
verifySecret(_s, pubKeyClaim);
|
||||
require(keccak256(abi.encode(_s)) == claimHash, "secret is not preimage to claimHash");
|
||||
emit Claimed(_s);
|
||||
|
||||
// send eth to caller (Bob)
|
||||
@@ -70,26 +65,16 @@ contract Swap {
|
||||
// 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 {
|
||||
function refund(bytes32 _s) external {
|
||||
require(msg.sender == owner);
|
||||
require(
|
||||
block.timestamp >= timeout_1 || ( block.timestamp < timeout_0 && !isReady),
|
||||
"It's Bob's turn now, please wait!"
|
||||
);
|
||||
|
||||
verifySecret(_s, pubKeyRefund);
|
||||
require(keccak256(abi.encode(_s)) == refundHash, "secret is not preimage to refundHash");
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,17 @@ func (k *PrivateSpendKey) View() (*PrivateViewKey, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Hash returns the keccak256 of the secret key bytes
|
||||
func (k *PrivateSpendKey) Hash() [32]byte {
|
||||
return Keccak256(k.key.Bytes())
|
||||
}
|
||||
|
||||
// HashString returns the keccak256 of the secret key bytes as a hex encoded string
|
||||
func (k *PrivateSpendKey) HashString() string {
|
||||
h := Keccak256(k.key.Bytes())
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
type PrivateViewKey struct {
|
||||
key *ed25519.Scalar
|
||||
}
|
||||
|
||||
@@ -140,15 +140,17 @@ func (m *InitiateMessage) Type() byte {
|
||||
// SendKeysMessage is sent by both parties to each other to initiate the protocol
|
||||
type SendKeysMessage struct {
|
||||
PublicSpendKey string
|
||||
PublicViewKey string
|
||||
PrivateViewKey string
|
||||
SpendKeyHash string
|
||||
EthAddress string
|
||||
}
|
||||
|
||||
func (m *SendKeysMessage) String() string {
|
||||
return fmt.Sprintf("SendKeysMessage PublicSpendKey=%s PublicViewKey=%s PrivateViewKey=%v",
|
||||
return fmt.Sprintf("SendKeysMessage PublicSpendKey=%s PrivateViewKey=%s SpendKeyHash=%s EthAddress=%s",
|
||||
m.PublicSpendKey,
|
||||
m.PublicViewKey,
|
||||
m.PrivateViewKey,
|
||||
m.SpendKeyHash,
|
||||
m.EthAddress,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,12 +2,13 @@ package swap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
@@ -19,18 +20,6 @@ import (
|
||||
|
||||
var defaultTimeoutDuration = big.NewInt(60) // 60 seconds
|
||||
|
||||
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 { //nolint
|
||||
s = reverse(s)
|
||||
return big.NewInt(0).SetBytes(s)
|
||||
}
|
||||
|
||||
func TestDeploySwap(t *testing.T) {
|
||||
conn, err := ethclient.Dial(common.DefaultEthEndpoint)
|
||||
require.NoError(t, err)
|
||||
@@ -41,25 +30,21 @@ func TestDeploySwap(t *testing.T) {
|
||||
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(common.GanacheChainID))
|
||||
require.NoError(t, err)
|
||||
|
||||
address, tx, swapContract, err := DeploySwap(authAlice, conn, [32]byte{}, [32]byte{}, defaultTimeoutDuration)
|
||||
address, tx, swapContract, err := DeploySwap(authAlice, conn, [32]byte{}, [32]byte{}, ethcommon.Address{}, defaultTimeoutDuration)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(address)
|
||||
t.Log(tx)
|
||||
t.Log(swapContract)
|
||||
require.NotEqual(t, ethcommon.Address{}, address)
|
||||
require.NotNil(t, tx)
|
||||
require.NotNil(t, 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
|
||||
@@ -72,7 +57,7 @@ func TestSwap_Claim(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(common.GanacheChainID))
|
||||
authAlice.Value = big.NewInt(10)
|
||||
authAlice.Value = big.NewInt(1000000000000)
|
||||
require.NoError(t, err)
|
||||
authBob, err := bind.NewKeyedTransactorWithChainID(pk_b, big.NewInt(common.GanacheChainID))
|
||||
require.NoError(t, err)
|
||||
@@ -86,11 +71,12 @@ func TestSwap_Claim(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
fmt.Println("BobBalanceBefore: ", bobBalanceBefore)
|
||||
|
||||
var pkAliceFixed [32]byte
|
||||
copy(pkAliceFixed[:], reverse(pubKeyAlice))
|
||||
var pkBobFixed [32]byte
|
||||
copy(pkBobFixed[:], reverse(pubKeyBob))
|
||||
contractAddress, deployTx, swap, err := DeploySwap(authAlice, conn, pkBobFixed, pkAliceFixed, defaultTimeoutDuration)
|
||||
bobPub := pk_b.Public().(*ecdsa.PublicKey)
|
||||
bobAddr := crypto.PubkeyToAddress(*bobPub)
|
||||
claimHash := keyPairBob.SpendKey().Hash()
|
||||
refundHash := keyPairAlice.SpendKey().Hash()
|
||||
|
||||
contractAddress, deployTx, swap, err := DeploySwap(authAlice, conn, claimHash, refundHash, bobAddr, defaultTimeoutDuration)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("Deploy Tx Gas Cost:", deployTx.Gas())
|
||||
|
||||
@@ -100,7 +86,7 @@ func TestSwap_Claim(t *testing.T) {
|
||||
|
||||
contractBalance, err := conn.BalanceAt(context.Background(), contractAddress, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contractBalance, big.NewInt(10))
|
||||
require.Equal(t, contractBalance, big.NewInt(1000000000000))
|
||||
|
||||
txOpts := &bind.TransactOpts{
|
||||
From: authAlice.From,
|
||||
@@ -113,10 +99,9 @@ func TestSwap_Claim(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
var sb [32]byte
|
||||
copy(sb[:], secretBob)
|
||||
_, err = swap.Claim(txOptsBob, sb)
|
||||
require.Regexp(t, ".*too late or early to claim!", err)
|
||||
|
||||
// Alice calls set_ready on the contract
|
||||
@@ -125,7 +110,7 @@ func TestSwap_Claim(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// The main transaction that we're testing. Should work
|
||||
tx, err := swap.Claim(txOptsBob, s)
|
||||
tx, err := swap.Claim(txOptsBob, sb)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
|
||||
@@ -138,23 +123,23 @@ func TestSwap_Claim(t *testing.T) {
|
||||
|
||||
fmt.Println("Tx details are:", tx.Gas())
|
||||
|
||||
// check whether Bob's account balance has increased now
|
||||
// TODO: check whether Bob's account balance has updated
|
||||
// bobBalanceAfter, err := conn.BalanceAt(context.Background(), authBob.From, nil)
|
||||
// fmt.Println("BobBalanceBefore: ", bobBalanceAfter)
|
||||
// fmt.Println("BobBalanceAfter: ", bobBalanceAfter)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, big.NewInt(10), big.NewInt(0).Sub(bobBalanceAfter, bobBalanceBefore))
|
||||
|
||||
// expected := big.NewInt(0).Sub(big.NewInt(1000000000000), tx.Cost())
|
||||
// require.Equal(t, expected, 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()
|
||||
|
||||
@@ -164,6 +149,8 @@ func TestSwap_Refund_Within_T0(t *testing.T) {
|
||||
|
||||
pk_a, err := crypto.HexToECDSA(common.DefaultPrivKeyAlice)
|
||||
require.NoError(t, err)
|
||||
pk_b, err := crypto.HexToECDSA(common.DefaultPrivKeyBob)
|
||||
require.NoError(t, err)
|
||||
|
||||
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
|
||||
require.NoError(t, err)
|
||||
@@ -173,11 +160,11 @@ func TestSwap_Refund_Within_T0(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
fmt.Println("AliceBalanceBefore: ", aliceBalanceBefore)
|
||||
|
||||
var pkAliceFixed [32]byte
|
||||
copy(pkAliceFixed[:], reverse(pubKeyAlice))
|
||||
var pkBobFixed [32]byte
|
||||
copy(pkBobFixed[:], reverse(pubKeyBob))
|
||||
contractAddress, _, swap, err := DeploySwap(authAlice, conn, pkBobFixed, pkAliceFixed, defaultTimeoutDuration)
|
||||
bobPub := pk_b.Public().(*ecdsa.PublicKey)
|
||||
bobAddr := crypto.PubkeyToAddress(*bobPub)
|
||||
claimHash := keyPairBob.SpendKey().Hash()
|
||||
refundHash := keyPairAlice.SpendKey().Hash()
|
||||
contractAddress, _, swap, err := DeploySwap(authAlice, conn, claimHash, refundHash, bobAddr, defaultTimeoutDuration)
|
||||
require.NoError(t, err)
|
||||
|
||||
txOpts := &bind.TransactOpts{
|
||||
@@ -186,8 +173,9 @@ func TestSwap_Refund_Within_T0(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
var sa [32]byte
|
||||
copy(sa[:], secretAlice)
|
||||
_, err = swap.Refund(txOpts, sa)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
|
||||
@@ -204,12 +192,10 @@ 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()
|
||||
|
||||
@@ -219,6 +205,8 @@ func TestSwap_Refund_After_T1(t *testing.T) {
|
||||
|
||||
pk_a, err := crypto.HexToECDSA(common.DefaultPrivKeyAlice)
|
||||
require.NoError(t, err)
|
||||
pk_b, err := crypto.HexToECDSA(common.DefaultPrivKeyBob)
|
||||
require.NoError(t, err)
|
||||
|
||||
authAlice, err := bind.NewKeyedTransactorWithChainID(pk_a, big.NewInt(1337)) // ganache chainID
|
||||
authAlice.Value = big.NewInt(10)
|
||||
@@ -228,11 +216,11 @@ func TestSwap_Refund_After_T1(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
fmt.Println("AliceBalanceBefore: ", aliceBalanceBefore)
|
||||
|
||||
var pkAliceFixed [32]byte
|
||||
copy(pkAliceFixed[:], reverse(pubKeyAlice))
|
||||
var pkBobFixed [32]byte
|
||||
copy(pkBobFixed[:], reverse(pubKeyBob))
|
||||
contractAddress, _, swap, err := DeploySwap(authAlice, conn, pkBobFixed, pkAliceFixed, defaultTimeoutDuration)
|
||||
bobPub := pk_b.Public().(*ecdsa.PublicKey)
|
||||
bobAddr := crypto.PubkeyToAddress(*bobPub)
|
||||
claimHash := keyPairBob.SpendKey().Hash()
|
||||
refundHash := keyPairAlice.SpendKey().Hash()
|
||||
contractAddress, _, swap, err := DeploySwap(authAlice, conn, claimHash, refundHash, bobAddr, defaultTimeoutDuration)
|
||||
require.NoError(t, err)
|
||||
|
||||
txOpts := &bind.TransactOpts{
|
||||
@@ -242,11 +230,12 @@ func TestSwap_Refund_After_T1(t *testing.T) {
|
||||
|
||||
// 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)
|
||||
var sa [32]byte
|
||||
copy(sa[:], secretAlice)
|
||||
_, err = swap.Refund(txOpts, sa)
|
||||
require.Regexp(t, ".*It's Bob's turn now, please wait!", err)
|
||||
|
||||
// wait some, then try again
|
||||
@@ -256,7 +245,7 @@ func TestSwap_Refund_After_T1(t *testing.T) {
|
||||
|
||||
ret := rpcClient.Call(&result, "evm_increaseTime", 3600*25)
|
||||
require.NoError(t, ret)
|
||||
_, err = swap.Refund(txOpts, s)
|
||||
_, err = swap.Refund(txOpts, sa)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The Swap contract has self destructed: should have no balance AND no bytecode at the address
|
||||
|
||||
Reference in New Issue
Block a user