add private key proof to SendKeyMessage, verify on receipt (#62)

This commit is contained in:
noot
2021-12-11 20:54:21 -05:00
committed by GitHub
parent d6aba125e4
commit bcbb77d8f6
25 changed files with 453 additions and 290 deletions

View File

@@ -2,6 +2,7 @@ package monero
import (
"github.com/noot/atomic-swap/common"
"github.com/noot/atomic-swap/monero/crypto"
"github.com/noot/atomic-swap/rpcclient"
)
@@ -10,9 +11,9 @@ type Client interface {
GetAccounts() (*getAccountsResponse, error)
GetAddress(idx uint) (*getAddressResponse, error)
GetBalance(idx uint) (*GetBalanceResponse, error)
Transfer(to Address, accountIdx, amount uint) (*TransferResponse, error)
GenerateFromKeys(kp *PrivateKeyPair, filename, password string, env common.Environment) error
GenerateViewOnlyWalletFromKeys(vk *PrivateViewKey, address Address, filename, password string) error
Transfer(to crypto.Address, accountIdx, amount uint) (*TransferResponse, error)
GenerateFromKeys(kp *crypto.PrivateKeyPair, filename, password string, env common.Environment) error
GenerateViewOnlyWalletFromKeys(vk *crypto.PrivateViewKey, address crypto.Address, filename, password string) error
GetHeight() (uint, error)
Refresh() error
OpenWallet(filename, password string) error
@@ -38,7 +39,7 @@ func (c *client) GetBalance(idx uint) (*GetBalanceResponse, error) {
return c.callGetBalance(idx)
}
func (c *client) Transfer(to Address, accountIdx, amount uint) (*TransferResponse, error) {
func (c *client) Transfer(to crypto.Address, accountIdx, amount uint) (*TransferResponse, error) {
destination := Destination{
Amount: amount,
Address: string(to),
@@ -47,11 +48,12 @@ func (c *client) Transfer(to Address, accountIdx, amount uint) (*TransferRespons
return c.callTransfer([]Destination{destination}, accountIdx)
}
func (c *client) GenerateFromKeys(kp *PrivateKeyPair, filename, password string, env common.Environment) error {
return c.callGenerateFromKeys(kp.sk, kp.vk, kp.Address(env), filename, password)
func (c *client) GenerateFromKeys(kp *crypto.PrivateKeyPair, filename, password string, env common.Environment) error {
return c.callGenerateFromKeys(kp.SpendKey(), kp.ViewKey(), kp.Address(env), filename, password)
}
func (c *client) GenerateViewOnlyWalletFromKeys(vk *PrivateViewKey, address Address, filename, password string) error {
func (c *client) GenerateViewOnlyWalletFromKeys(vk *crypto.PrivateViewKey, address crypto.Address,
filename, password string) error {
return c.callGenerateFromKeys(nil, vk, address, filename, password)
}

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/noot/atomic-swap/common"
"github.com/noot/atomic-swap/monero/crypto"
"github.com/stretchr/testify/require"
)
@@ -38,15 +39,15 @@ func TestClient_Transfer(t *testing.T) {
t.Fatal("need to wait for balance to unlock")
}
kpA, err := GenerateKeys()
kpA, err := crypto.GenerateKeys()
require.NoError(t, err)
kpB, err := GenerateKeys()
kpB, err := crypto.GenerateKeys()
require.NoError(t, err)
kpABPub := SumSpendAndViewKeys(kpA.PublicKeyPair(), kpB.PublicKeyPair())
kpABPub := crypto.SumSpendAndViewKeys(kpA.PublicKeyPair(), kpB.PublicKeyPair())
vkABPriv := SumPrivateViewKeys(kpA.vk, kpB.vk)
vkABPriv := crypto.SumPrivateViewKeys(kpA.ViewKey(), kpB.ViewKey())
r, err := rand.Int(rand.Reader, big.NewInt(10000))
require.NoError(t, err)
@@ -82,7 +83,7 @@ func TestClient_Transfer(t *testing.T) {
_ = daemon.callGenerateBlocks(aliceAddress.Address, 16)
// generate spend account for A+B
skAKPriv := SumPrivateSpendKeys(kpA.sk, kpB.sk)
skAKPriv := crypto.SumPrivateSpendKeys(kpA.SpendKey(), kpB.SpendKey())
// ignore the error for now, as it can error with "Wallet already exists."
_ = cB.callGenerateFromKeys(skAKPriv, vkABPriv, kpABPub.Address(common.Mainnet),
fmt.Sprintf("test-wallet-%d", r), "")
@@ -97,6 +98,6 @@ func TestClient_Transfer(t *testing.T) {
}
// transfer from account A+B back to Alice's address
_, err = cB.Transfer(Address(aliceAddress.Address), 0, 1)
_, err = cB.Transfer(crypto.Address(aliceAddress.Address), 0, 1)
require.NoError(t, err)
}

58
monero/crypto/address.go Normal file
View File

@@ -0,0 +1,58 @@
package crypto
import (
"github.com/noot/atomic-swap/common"
)
const (
addressPrefixMainnet byte = 18
addressPrefixStagenet byte = 24
)
// Address represents a base58-encoded string
type Address string
func getChecksum(data ...[]byte) (result [4]byte) {
keccak256 := Keccak256(data...)
copy(result[:], keccak256[:4])
return
}
// AddressBytes returns the address as bytes for a PrivateKeyPair with the given environment (ie. mainnet or stagenet)
func (kp *PrivateKeyPair) AddressBytes(env common.Environment) []byte {
return kp.PublicKeyPair().AddressBytes(env)
}
// Address returns the base58-encoded address for a PrivateKeyPair with the given environment
// (ie. mainnet or stagenet)
func (kp *PrivateKeyPair) Address(env common.Environment) Address {
return Address(EncodeMoneroBase58(kp.AddressBytes(env)))
}
// AddressBytes returns the address as bytes for a PublicKeyPair with the given environment (ie. mainnet or stagenet)
func (kp *PublicKeyPair) AddressBytes(env common.Environment) []byte {
psk := kp.sk.key.Bytes()
pvk := kp.vk.key.Bytes()
c := append(psk, pvk...)
var prefix byte
switch env {
case common.Mainnet, common.Development:
prefix = addressPrefixMainnet
case common.Stagenet:
prefix = addressPrefixStagenet
}
// address encoding is:
// (network_prefix) + (32-byte public spend key) + (32-byte-byte public view key)
// + first_4_Bytes(Hash(network_prefix + (32-byte public spend key) + (32-byte public view key)))
checksum := getChecksum(append([]byte{prefix}, c...))
addr := append(append([]byte{prefix}, c...), checksum[:4]...)
return addr
}
// Address returns the base58-encoded address for a PublicKeyPair with the given environment
// (ie. mainnet or stagenet)
func (kp *PublicKeyPair) Address(env common.Environment) Address {
return Address(EncodeMoneroBase58(kp.AddressBytes(env)))
}

View File

@@ -1,6 +1,6 @@
// this file is from https://github.com/paxosglobal/moneroutil/tree/33d7e0c11a62d2ac67213781a0b485d0de4aca70
package monero
package crypto
import (
"math/big"

View File

@@ -1,7 +1,8 @@
package monero
package crypto
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
@@ -11,69 +12,9 @@ import (
"github.com/noot/atomic-swap/common"
ed25519 "filippo.io/edwards25519"
"github.com/ebfe/keccak"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
)
const (
addressPrefixMainnet byte = 18
addressPrefixStagenet byte = 24
)
// PublicSpendOnSecp256k1 returns a public spend key on the secp256k1 curve
func PublicSpendOnSecp256k1(k []byte) (x, y *big.Int) {
return secp256k1.S256().ScalarBaseMult(k)
}
// SumSpendAndViewKeys sums two PublicKeyPairs, returning another PublicKeyPair.
func SumSpendAndViewKeys(a, b *PublicKeyPair) *PublicKeyPair {
return &PublicKeyPair{
sk: SumPublicKeys(a.sk, b.sk),
vk: SumPublicKeys(a.vk, b.vk),
}
}
// SumPublicKeys sums two public keys (points)
func SumPublicKeys(a, b *PublicKey) *PublicKey {
s := ed25519.NewIdentityPoint().Add(a.key, b.key)
return &PublicKey{
key: s,
}
}
// SumPrivateSpendKeys sums two private spend keys (scalars)
func SumPrivateSpendKeys(a, b *PrivateSpendKey) *PrivateSpendKey {
s := ed25519.NewScalar().Add(a.key, b.key)
return &PrivateSpendKey{
key: s,
}
}
// SumPrivateViewKeys sums two private view keys (scalars)
func SumPrivateViewKeys(a, b *PrivateViewKey) *PrivateViewKey {
s := ed25519.NewScalar().Add(a.key, b.key)
return &PrivateViewKey{
key: s,
}
}
// Keccak256 returns the keccak256 hash of the data.
func Keccak256(data ...[]byte) (result [32]byte) {
h := keccak.New256()
for _, b := range data {
h.Write(b)
}
r := h.Sum(nil)
copy(result[:], r)
return
}
func getChecksum(data ...[]byte) (result [4]byte) {
keccak256 := Keccak256(data...)
copy(result[:], keccak256[:4])
return
}
const privateKeySize = 32
var (
@@ -118,39 +59,11 @@ func NewPrivateKeyPairFromBytes(skBytes, vkBytes []byte) (*PrivateKeyPair, error
}, nil
}
// AddressBytes returns the address as bytes for a PrivateKeyPair with the given environment (ie. mainnet or stagenet)
func (kp *PrivateKeyPair) AddressBytes(env common.Environment) []byte {
psk := kp.sk.Public().key.Bytes()
pvk := kp.vk.Public().key.Bytes()
c := append(psk, pvk...)
var prefix byte
switch env {
case common.Mainnet, common.Development:
prefix = addressPrefixMainnet
case common.Stagenet:
prefix = addressPrefixStagenet
}
// address encoding is:
// 0x12+(32-byte public spend key) + (32-byte-byte public view key)
// + First_4_Bytes(Hash(0x12+(32-byte public spend key) + (32-byte public view key)))
checksum := getChecksum(append([]byte{prefix}, c...))
addr := append(append([]byte{prefix}, c...), checksum[:4]...)
return addr
}
// SpendKeyBytes returns the canoncail byte encoding of the private spend key.
func (kp *PrivateKeyPair) SpendKeyBytes() []byte {
return kp.sk.key.Bytes()
}
// Address returns the base58-encoded address for a PrivateKeyPair with the given environment
// (ie. mainnet or stagenet)
func (kp *PrivateKeyPair) Address(env common.Environment) Address {
return Address(EncodeMoneroBase58(kp.AddressBytes(env)))
}
// PublicKeyPair returns the PublicKeyPair corresponding to the PrivateKeyPair
func (kp *PrivateKeyPair) PublicKeyPair() *PublicKeyPair {
return &PublicKeyPair{
@@ -182,7 +95,8 @@ func (kp *PrivateKeyPair) Marshal(env common.Environment) ([]byte, error) {
// PrivateSpendKey represents a monero private spend key
type PrivateSpendKey struct {
key *ed25519.Scalar
seed [32]byte
key *ed25519.Scalar
}
// NewPrivateSpendKey returns a new PrivateSpendKey from the given canonically-encoded scalar.
@@ -388,50 +302,63 @@ func (kp *PublicKeyPair) ViewKey() *PublicKey {
return kp.vk
}
// AddressBytes returns the address as bytes for a PublicKeyPair with the given environment (ie. mainnet or stagenet)
func (kp *PublicKeyPair) AddressBytes(env common.Environment) []byte {
psk := kp.sk.key.Bytes()
pvk := kp.vk.key.Bytes()
c := append(psk, pvk...)
var prefix byte
switch env {
case common.Mainnet, common.Development:
prefix = addressPrefixMainnet
case common.Stagenet:
prefix = addressPrefixStagenet
}
// address encoding is:
// 0x12+(32-byte public spend key) + (32-byte-byte public view key)
// + First_4_Bytes(Hash(0x12+(32-byte public spend key) + (32-byte public view key)))
checksum := getChecksum(append([]byte{prefix}, c...))
addr := append(append([]byte{prefix}, c...), checksum[:4]...)
return addr
}
// Address returns the base58-encoded address for a PublicKeyPair with the given environment
// (ie. mainnet or stagenet)
func (kp *PublicKeyPair) Address(env common.Environment) Address {
return Address(EncodeMoneroBase58(kp.AddressBytes(env)))
}
// GenerateKeys returns a private spend key and view key
// GenerateKeys generates a private spend key and view key
func GenerateKeys() (*PrivateKeyPair, error) {
var seed [64]byte
var seed [32]byte
_, err := rand.Read(seed[:])
if err != nil {
return nil, err
}
s, err := ed25519.NewScalar().SetUniformBytes(seed[:])
// we hash the seed for compatibility w/ the ed25519 stdlib
h := sha512.Sum512(seed[:])
s, err := ed25519.NewScalar().SetBytesWithClamping(h[:32])
if err != nil {
return nil, fmt.Errorf("failed to set bytes: %w", err)
}
sk := &PrivateSpendKey{
key: s,
seed: seed,
key: s,
}
return sk.AsPrivateKeyPair()
}
// PublicSpendOnSecp256k1 returns a public spend key on the secp256k1 curve
func PublicSpendOnSecp256k1(k []byte) (x, y *big.Int) {
return secp256k1.S256().ScalarBaseMult(k)
}
// SumSpendAndViewKeys sums two PublicKeyPairs, returning another PublicKeyPair.
func SumSpendAndViewKeys(a, b *PublicKeyPair) *PublicKeyPair {
return &PublicKeyPair{
sk: SumPublicKeys(a.sk, b.sk),
vk: SumPublicKeys(a.vk, b.vk),
}
}
// SumPublicKeys sums two public keys (points)
func SumPublicKeys(a, b *PublicKey) *PublicKey {
s := ed25519.NewIdentityPoint().Add(a.key, b.key)
return &PublicKey{
key: s,
}
}
// SumPrivateSpendKeys sums two private spend keys (scalars)
func SumPrivateSpendKeys(a, b *PrivateSpendKey) *PrivateSpendKey {
s := ed25519.NewScalar().Add(a.key, b.key)
return &PrivateSpendKey{
key: s,
}
}
// SumPrivateViewKeys sums two private view keys (scalars)
func SumPrivateViewKeys(a, b *PrivateViewKey) *PrivateViewKey {
s := ed25519.NewScalar().Add(a.key, b.key)
return &PrivateViewKey{
key: s,
}
}

View File

@@ -1,4 +1,4 @@
package monero
package crypto
import (
"encoding/hex"

14
monero/crypto/hash.go Normal file
View File

@@ -0,0 +1,14 @@
package crypto
import "github.com/ebfe/keccak"
// Keccak256 returns the keccak256 hash of the data.
func Keccak256(data ...[]byte) (result [32]byte) {
h := keccak.New256()
for _, b := range data {
h.Write(b)
}
r := h.Sum(nil)
copy(result[:], r)
return
}

54
monero/crypto/sign.go Normal file
View File

@@ -0,0 +1,54 @@
package crypto
import (
"crypto/ed25519"
"encoding/hex"
"errors"
)
// Signature represents an ed25519 signature
type Signature struct {
s []byte
}
// NewSignatureFromHex returns a new Signature from the given hex-encoded string.
// The string must be 64 bytes.
func NewSignatureFromHex(s string) (*Signature, error) {
b, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
if len(b) != ed25519.SignatureSize {
return nil, errors.New("invalid length for signature")
}
return &Signature{
s: b,
}, nil
}
// Hex returns the signature as a hex-encoded string.
func (s *Signature) Hex() string {
return hex.EncodeToString(s.s)
}
// Sign signs the given message with the private key.
// The private key must have been created with GenerateKeys().
func (k *PrivateSpendKey) Sign(msg []byte) (*Signature, error) {
if k.seed == [32]byte{} {
return nil, errors.New("private key does not have seed, key must be created with GenerateKeys")
}
pub := k.Public().key.Bytes()
pk := ed25519.PrivateKey(append(k.seed[:], pub...))
return &Signature{
s: ed25519.Sign(pk, msg),
}, nil
}
// Verify verifies that the message was signed with the given signature and key.
func (k *PublicKey) Verify(msg []byte, sig *Signature) bool {
pk := ed25519.PublicKey(k.key.Bytes())
return ed25519.Verify(pk, msg, sig.s)
}

View File

@@ -0,0 +1,44 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPrivateSpendKey_Sign(t *testing.T) {
kp, err := GenerateKeys()
require.NoError(t, err)
msg := []byte("testmessage")
sig, err := kp.sk.Sign(msg)
require.NoError(t, err)
require.NotNil(t, sig)
}
func TestPrivateSpendKey_Verify(t *testing.T) {
kp, err := GenerateKeys()
require.NoError(t, err)
msg := []byte("testmessage")
sig, err := kp.sk.Sign(msg)
require.NoError(t, err)
require.NotNil(t, sig)
ok := kp.sk.Public().Verify(msg, sig)
require.True(t, ok)
}
func TestSignature_Hex(t *testing.T) {
kp, err := GenerateKeys()
require.NoError(t, err)
msg := []byte("testmessage")
sig, err := kp.sk.Sign(msg)
require.NoError(t, err)
hex := sig.Hex()
sig2, err := NewSignatureFromHex(hex)
require.NoError(t, err)
require.Equal(t, sig, sig2)
}

View File

@@ -1,4 +1,4 @@
package monero
package crypto
import (
"fmt"

View File

@@ -5,12 +5,10 @@ import (
"fmt"
"strings"
"github.com/noot/atomic-swap/monero/crypto"
"github.com/noot/atomic-swap/rpcclient"
)
// Address represents a base58-encoded string
type Address string
type generateFromKeysRequest struct {
Filename string `json:"filename"`
Address string `json:"address"`
@@ -24,7 +22,7 @@ type generateFromKeysResponse struct {
Info string `json:"info"`
}
func (c *client) callGenerateFromKeys(sk *PrivateSpendKey, vk *PrivateViewKey, address Address,
func (c *client) callGenerateFromKeys(sk *crypto.PrivateSpendKey, vk *crypto.PrivateViewKey, address crypto.Address,
filename, password string) error {
const (
method = "generate_from_keys"

View File

@@ -7,18 +7,20 @@ import (
"testing"
"github.com/noot/atomic-swap/common"
"github.com/noot/atomic-swap/monero/crypto"
"github.com/stretchr/testify/require"
)
func TestCallGenerateFromKeys(t *testing.T) {
kp, err := GenerateKeys()
kp, err := crypto.GenerateKeys()
require.NoError(t, err)
r, err := rand.Int(rand.Reader, big.NewInt(999))
require.NoError(t, err)
c := NewClient(common.DefaultBobMoneroEndpoint)
err = c.callGenerateFromKeys(kp.sk, kp.vk, kp.Address(common.Mainnet), fmt.Sprintf("test-wallet-%d", r), "")
err = c.callGenerateFromKeys(kp.SpendKey(), kp.ViewKey(), kp.Address(common.Mainnet),
fmt.Sprintf("test-wallet-%d", r), "")
require.NoError(t, err)
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/noot/atomic-swap/common"
"github.com/noot/atomic-swap/monero/crypto"
logging "github.com/ipfs/go-log"
)
@@ -43,7 +44,8 @@ func WaitForBlocks(client Client) error {
}
// CreateMoneroWallet creates a monero wallet from a private keypair.
func CreateMoneroWallet(name string, env common.Environment, client Client, kpAB *PrivateKeyPair) (Address, error) {
func CreateMoneroWallet(name string, env common.Environment, client Client,
kpAB *crypto.PrivateKeyPair) (crypto.Address, error) {
t := time.Now().Format("2006-Jan-2-15:04:05")
walletName := fmt.Sprintf("%s-%s", name, t)
if err := client.GenerateFromKeys(kpAB, walletName, "", env); err != nil {