diff --git a/address.go b/address.go index f41963b..53649b9 100644 --- a/address.go +++ b/address.go @@ -24,10 +24,14 @@ package factom import ( "context" + "crypto/ecdsa" + "crypto/ed25519" "crypto/rand" "crypto/sha256" + "encoding/hex" + "fmt" - "crypto/ed25519" + "github.com/ethereum/go-ethereum/crypto" ) // Notes: This file contains all types, interfaces, and methods related to @@ -45,6 +49,15 @@ type FAAddress [sha256.Size]byte // FsAddress is the secret key to a FAAddress. type FsAddress [sha256.Size]byte +// EthSecret is the secret key to a FAAddress +// It uses rcd type 0x0e with ecdsa signing. +// TODO: To get the bitsize, you need to do `priv.Params().Bitsize`. +// It is not in a constant. +type EthSecret [32]byte + +// FeAddress is a Public Factoid Address using the rcde. +type FeAddress [sha256.Size]byte + // ECAddress is a Public Entry Credit Address. type ECAddress [sha256.Size]byte @@ -59,6 +72,9 @@ func (adr *FAAddress) payload() *payload { func (adr *FsAddress) payload() *payload { return (*payload)(adr) } +func (adr *FeAddress) payload() *payload { + return (*payload)(adr) +} func (adr *ECAddress) payload() *payload { return (*payload)(adr) } @@ -71,6 +87,10 @@ var ( fsPrefixBytes = [...]byte{0x64, 0x78} ecPrefixBytes = [...]byte{0x59, 0x2a} esPrefixBytes = [...]byte{0x5d, 0xb6} + + // RCD-e prefixes + fePrefixBytes = [...]byte{0x62, 0xf4} // Fe... + fEPrefixBytes = [...]byte{0x60, 0x28} // FE... ) // PrefixBytes returns the two byte prefix for the address type as a byte @@ -89,6 +109,14 @@ func (FsAddress) PrefixBytes() Bytes { return prefix[:] } +// PrefixBytes returns the two byte prefix for the address type as a byte +// slice. Note that the prefix for a given address type is always the same and +// does not depend on the address value. Returns []byte{0x62, 0xf4}. +func (FeAddress) PrefixBytes() Bytes { + prefix := fePrefixBytes + return prefix[:] +} + // PrefixBytes returns the two byte prefix for the address type as a byte // slice. Note that the prefix for a given address type is always the same and // does not depend on the address value. Returns []byte{0x59, 0x2a}. @@ -108,6 +136,7 @@ func (EsAddress) PrefixBytes() Bytes { const ( faPrefixStr = "FA" fsPrefixStr = "Fs" + fePrefixStr = "Fe" ecPrefixStr = "EC" esPrefixStr = "Es" ) @@ -126,6 +155,13 @@ func (FsAddress) PrefixString() string { return fsPrefixStr } +// PrefixString returns the two prefix bytes for the address type as an encoded +// string. Note that the prefix for a given address type is always the same and +// does not depend on the address value. Returns "Fe". +func (FeAddress) PrefixString() string { + return fePrefixStr +} + // PrefixString returns the two prefix bytes for the address type as an encoded // string. Note that the prefix for a given address type is always the same and // does not depend on the address value. Returns "EC". @@ -152,6 +188,18 @@ func (adr FsAddress) String() string { return adr.payload().StringWithPrefix(adr.PrefixBytes()) } +// String encodes adr into its human readable form: base58check with +// adr.PrefixBytes(). +func (adr FeAddress) String() string { + return adr.payload().StringWithPrefix(adr.PrefixBytes()) +} + +// String encodes adr into its human readable form: base58check with +// adr.PrefixBytes(). +func (adr EthSecret) String() string { + return "0x" + hex.EncodeToString(adr[:]) +} + // String encodes adr into its human readable form: base58check with // adr.PrefixBytes(). func (adr ECAddress) String() string { @@ -174,6 +222,16 @@ func (adr FsAddress) MarshalText() ([]byte, error) { return adr.payload().MarshalTextWithPrefix(adr.PrefixBytes()) } +// MarshalText encodes adr as a string using adr.String(). +func (adr FeAddress) MarshalText() ([]byte, error) { + return adr.payload().MarshalTextWithPrefix(adr.PrefixBytes()) +} + +// MarshalText encodes adr as a string using adr.String(). +func (adr EthSecret) MarshalText() ([]byte, error) { + return []byte(adr.String()), nil +} + // MarshalText encodes adr as a string using adr.String(). func (adr ECAddress) MarshalText() ([]byte, error) { return adr.payload().MarshalTextWithPrefix(adr.PrefixBytes()) @@ -192,6 +250,12 @@ func GenerateFsAddress() (FsAddress, error) { return generatePrivKey() } +// GenerateEthSecret generates a secure random private Etheruem address using +// crypto/rand.Random as the source of randomness. +func GenerateEthSecret() (EthSecret, error) { + return generatePrivKey() +} + // GenerateEsAddress generates a secure random private Entry Credit address // using crypto/rand.Random as the source of randomness. func GenerateEsAddress() (EsAddress, error) { @@ -218,6 +282,18 @@ func NewFsAddress(adrStr string) (adr FsAddress, err error) { return } +// NewFeAddress attempts to parse adrStr into a new FeAddress. +func NewFeAddress(adrStr string) (adr FeAddress, err error) { + err = adr.Set(adrStr) + return +} + +// NewEthSecret attempts to parse adrStr into a new EthSecret. +func NewEthSecret(adrStr string) (adr EthSecret, err error) { + err = adr.Set(adrStr) + return +} + // NewECAddress attempts to parse adrStr into a new ECAddress. func NewECAddress(adrStr string) (adr ECAddress, err error) { err = adr.Set(adrStr) @@ -240,6 +316,30 @@ func (adr *FsAddress) Set(adrStr string) error { return adr.payload().SetWithPrefix(adrStr, adr.PrefixString()) } +// Set attempts to parse adrStr into adr. +func (adr *FeAddress) Set(adrStr string) error { + return adr.payload().SetWithPrefix(adrStr, adr.PrefixString()) +} + +// Set attempts to parse adrStr into adr. +// adrStr is in format 0x[64 character hex] +func (e *EthSecret) Set(adrStr string) error { + // TODO: Payload code expects base 58. So this address type cannot + // use those generic functions + if !has0xPrefix(adrStr) { + return fmt.Errorf("exp 0x prefix") + } + if len(adrStr) != 66 { // 66 is 64 hex + 2 character prefix + return fmt.Errorf("exp 64 hex characters") + } + secret, err := hex.DecodeString(adrStr[2:]) + if err != nil { + return err + } + copy(e[:], secret) + return nil +} + // Set attempts to parse adrStr into adr. func (adr *ECAddress) Set(adrStr string) error { return adr.payload().SetWithPrefix(adrStr, adr.PrefixString()) @@ -262,6 +362,18 @@ func (adr *FsAddress) UnmarshalText(text []byte) error { return adr.payload().UnmarshalTextWithPrefix(text, adr.PrefixString()) } +// UnmarshalText decodes a string with a human readable secret Factoid address +// into adr. +func (adr *FeAddress) UnmarshalText(text []byte) error { + return adr.payload().UnmarshalTextWithPrefix(text, adr.PrefixString()) +} + +// UnmarshalText decodes a string with a human readable secret Factoid address +// into adr. +func (adr *EthSecret) UnmarshalText(text []byte) error { + return adr.Set(string(text)) +} + // UnmarshalText decodes a string with a human readable public Entry Credit // address into adr. func (adr *ECAddress) UnmarshalText(text []byte) error { @@ -281,6 +393,12 @@ func (adr FAAddress) GetFsAddress(ctx context.Context, c *Client) (FsAddress, er return privAdr, err } +func (adr FeAddress) GetEthSecret(ctx context.Context, c *Client) (EthSecret, error) { + var privAdr EthSecret + err := c.getAddress(ctx, adr, &privAdr) + return privAdr, err +} + // GetEsAddress queries factom-walletd for the EsAddress corresponding to adr. func (adr ECAddress) GetEsAddress(ctx context.Context, c *Client) (EsAddress, error) { var privAdr EsAddress @@ -298,32 +416,41 @@ func (c *Client) getAddress(ctx context.Context, pubAdr, privAdr interface{}) er } // GetPrivateAddresses queries factom-walletd for all private addresses. -func (c *Client) GetPrivateAddresses(ctx context.Context) ([]FsAddress, []EsAddress, +func (c *Client) GetPrivateAddresses(ctx context.Context) ([]FsAddress, []EsAddress, []EthSecret, error) { var result struct{ Addresses []struct{ Secret string } } if err := c.WalletdRequest(ctx, "all-addresses", nil, &result); err != nil { - return nil, nil, err + return nil, nil, nil, err } fss := make([]FsAddress, 0, len(result.Addresses)) ess := make([]EsAddress, 0, len(result.Addresses)) + eths := make([]EthSecret, 0, len(result.Addresses)) for _, adr := range result.Addresses { adrStr := adr.Secret + if has0xPrefix(adrStr) { + eth, err := NewEthSecret(adrStr) + if err != nil { + return nil, nil, nil, err + } + eths = append(eths, eth) + continue + } switch adrStr[:2] { case fsPrefixStr: fs, err := NewFsAddress(adrStr) if err != nil { - return nil, nil, err + return nil, nil, nil, err } fss = append(fss, fs) case esPrefixStr: es, err := NewEsAddress(adrStr) if err != nil { - return nil, nil, err + return nil, nil, nil, err } ess = append(ess, es) } } - return fss, ess, nil + return fss, ess, eths, nil } // Save adr with factom-walletd. @@ -331,6 +458,10 @@ func (adr FsAddress) Save(ctx context.Context, c *Client) error { return c.SavePrivateAddresses(ctx, adr.String()) } +func (adr EthSecret) Save(ctx context.Context, c *Client) error { + return c.SavePrivateAddresses(ctx, adr.String()) +} + // Save adr with factom-walletd. func (adr EsAddress) Save(ctx context.Context, c *Client) error { return c.SavePrivateAddresses(ctx, adr.String()) @@ -359,6 +490,16 @@ func (adr FsAddress) GetBalance(ctx context.Context, c *Client) (uint64, error) return adr.FAAddress().GetBalance(ctx, c) } +// GetBalance queries factomd for the Factoid Balance for adr. +func (adr FeAddress) GetBalance(ctx context.Context, c *Client) (uint64, error) { + return adr.FAAddress().GetBalance(ctx, c) +} + +// GetBalance queries factomd for the Factoid Balance for adr. +func (adr EthSecret) GetBalance(ctx context.Context, c *Client) (uint64, error) { + return adr.FAAddress().GetBalance(ctx, c) +} + // GetBalance queries factomd for the Entry Credit Balance for adr. func (adr ECAddress) GetBalance(ctx context.Context, c *Client) (uint64, error) { return c.getBalance(ctx, "entry-credit-balance", adr.String()) @@ -390,6 +531,12 @@ func (adr FsAddress) Remove(ctx context.Context, c *Client) error { return adr.FAAddress().Remove(ctx, c) } +// Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. +func (adr EthSecret) Remove(ctx context.Context, c *Client) error { + panic("Not yet implemented") // TODO: Implement + return nil +} + // Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. func (adr ECAddress) Remove(ctx context.Context, c *Client) error { return c.removeAddress(ctx, adr.String()) @@ -414,6 +561,21 @@ func (adr FsAddress) FAAddress() FAAddress { return sha256d(adr.RCD()) } +// FAAddress returns the FAAddress corresponding to adr. +func (e EthSecret) FeAddress() FeAddress { + return sha256d(e.RCD()) +} + +// FAAddress returns the FAAddress corresponding to adr. +func (e EthSecret) FAAddress() FAAddress { + return sha256d(e.RCD()) +} + +// FAAddress returns the FAAddress corresponding to adr. +func (e FeAddress) FAAddress() FAAddress { + return FAAddress(e) +} + // ECAddress returns the ECAddress corresponding to adr. func (adr EsAddress) ECAddress() (ec ECAddress) { copy(ec[:], adr.PublicKey()) @@ -431,11 +593,24 @@ func (adr FsAddress) RCD() []byte { return append([]byte{RCDType01}, adr.PublicKey()[:]...) } +func (s EthSecret) RCD() []byte { + return append([]byte{RCDType0e}, s.PublicKeyBytes()...) +} + // Sign the msg. func (adr FsAddress) Sign(msg []byte) []byte { return ed25519.Sign(adr.PrivateKey(), msg) } +// Sign the msg. +func (adr EthSecret) Sign(msg []byte) []byte { + sig, err := crypto.Sign(msg, adr.PrivateKey()) + if err != nil { + return nil + } + return sig +} + // PublicKey returns the ed25519.PublicKey for adr. func (adr ECAddress) PublicKey() ed25519.PublicKey { return adr[:] @@ -451,12 +626,50 @@ func (adr FsAddress) PublicKey() ed25519.PublicKey { return adr.PrivateKey().Public().(ed25519.PublicKey) } +func (s EthSecret) PublicKey() ecdsa.PublicKey { + secret := s.PrivateKey() + if secret == nil { + return ecdsa.PublicKey{} + } + return secret.PublicKey +} + +// PublicKeyBytes returns the byte representation of the public key +func (s EthSecret) PublicKeyBytes() []byte { + pub := s.PublicKey() + bytes := crypto.FromECDSAPub(&pub) + // Strip off the 0x04 prefix to indicate an uncompressed key. + // You can find the prefix list here: + // https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html + return bytes[1:] +} + // PrivateKey returns the ed25519.PrivateKey for adr. func (adr FsAddress) PrivateKey() ed25519.PrivateKey { return ed25519.NewKeyFromSeed(adr[:]) } +func (s EthSecret) PrivateKey() *ecdsa.PrivateKey { + secret, err := crypto.ToECDSA(s[:]) + if err != nil { + return nil + } + return secret +} + // PrivateKey returns the ed25519.PrivateKey for adr. func (adr EsAddress) PrivateKey() ed25519.PrivateKey { return ed25519.NewKeyFromSeed(adr[:]) } + +// Extra EthSecret functions + +// EthAddress returns the linked eth address +func (adr EthSecret) EthAddress() string { + return crypto.PubkeyToAddress(adr.PublicKey()).String() +} + +// has0xPrefix validates str begins with '0x' or '0X'. +func has0xPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} diff --git a/address_test.go b/address_test.go index cdddc12..ba1c49c 100644 --- a/address_test.go +++ b/address_test.go @@ -24,8 +24,10 @@ package factom import ( "context" + "encoding/hex" "encoding/json" "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -227,7 +229,7 @@ func TestAddress(t *testing.T) { }) t.Run("GetPrivateAddresses", func(t *testing.T) { - fss, ess, err := c.GetPrivateAddresses(nil) + fss, ess, _, err := c.GetPrivateAddresses(nil) assert := assert.New(t) assert.NoError(err) assert.NotEmpty(fss) @@ -259,3 +261,29 @@ func TestAddress(t *testing.T) { assert.NoError(t, err) }) } + +func TestNewEthSecret(t *testing.T) { + // Eth Vector from + // https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html + + secret := "0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315" + public := "0x6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0" + + "c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0" + ethAddress := "0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9" + faAddress := "FA2M6ddTAgewAiU7Cm5T6wffXsteuqrYkDAtUtbBjcurf3LKH9Eo" + + assert := assert.New(t) + + addr, err := NewEthSecret(secret) + assert.NoError(err) + + // Check eth address + assert.True(strings.EqualFold(addr.EthAddress(), ethAddress)) + + // Check the public key + exp, _ := hex.DecodeString(public[2:]) + assert.Equal(exp, addr.PublicKeyBytes()) + + // Check the FA address + assert.Equal(faAddress, addr.FAAddress().String()) +} diff --git a/go.mod b/go.mod index e5e192c..8e90d44 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,11 @@ require ( github.com/AdamSLevy/jsonrpc2/v13 v13.0.1 github.com/AdamSLevy/retry v0.0.0-20191017184328-cce921f261f4 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 + github.com/btcsuite/btcd v0.20.1-beta // indirect + github.com/ethereum/go-ethereum v1.9.7 github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.4.0 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index 15d4b1b..344e186 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v12 v12.0.1 h1:cVyqoBa4Qgw8bQj3vCBnZHOX/E+uvPM8TbQPobGwY8Q= github.com/AdamSLevy/jsonrpc2/v12 v12.0.1/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347 h1:bnHpux+c+kROwUL+2nscrUODAa33JQWlViGCBD2W5bg= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/jsonrpc2/v13 v13.0.1 h1:hQ5rfCFBQrXQNtvP+2mtH6EmsUAEN61ZF1n8UGVNis0= github.com/AdamSLevy/jsonrpc2/v13 v13.0.1/go.mod h1:8QsYqGKdPEim+n/j9KFTYB2tIj250f044Vxh2XRKhP4= github.com/AdamSLevy/retry v0.0.0-20191017184328-cce921f261f4 h1:qTRFsX5Cb/zeePRrKCfLkH4FJpRTP6DbrzPofBWjF5Y= @@ -12,24 +10,61 @@ github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= github.com/JohnCGriffin/overflow v0.0.0-20170615021017-4d914c927216 h1:2ZboyJ8vl75fGesnG9NpMTD2DyQI3FzMXy4x752rGF0= github.com/JohnCGriffin/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ethereum/go-ethereum v1.9.7 h1:p4O+z0MGzB7xxngHbplcYNloxkFwGkeComhkzWnq0ig= +github.com/ethereum/go-ethereum v1.9.7/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/rcdsigner.go b/rcdsigner.go index ddafc9e..cd1f3e6 100644 --- a/rcdsigner.go +++ b/rcdsigner.go @@ -25,6 +25,8 @@ package factom import ( "crypto/ed25519" "fmt" + + "github.com/ethereum/go-ethereum/crypto" ) // RCDSigner is the interface implemented by types that can generate Redeem @@ -45,6 +47,8 @@ func ValidateRCD(rcd, sig, msg []byte) (Bytes32, error) { switch rcd[0] { case RCDType01: validateRCD = ValidateRCD01 + case RCDType0e: + validateRCD = ValidateRCD0e default: return Bytes32{}, fmt.Errorf("unsupported RCD") } @@ -58,6 +62,12 @@ const ( RCDType01Size = ed25519.PublicKeySize + 1 // SignatureSize is the size of the ed25519 signatures. RCDType01SigSize = ed25519.SignatureSize + + RCDType0e byte = 0x0e + RCDType0eSize = 64 + 1 // Excluding uncompressed 0x04 prefix + // The recover byte allows the public key to be recovered from the + // signature + msg. + RCDType0eSigSize = 65 // 64 byte sig + recovery byte ) func ValidateRCD01(rcd, sig, msg []byte) (Bytes32, error) { @@ -78,3 +88,23 @@ func ValidateRCD01(rcd, sig, msg []byte) (Bytes32, error) { return sha256d(rcd), nil } + +func ValidateRCD0e(rcd []byte, sig []byte, msg []byte) (Bytes32, error) { + if len(rcd) != RCDType0eSize { + return Bytes32{}, fmt.Errorf("invalid RCD size") + } + if rcd[0] != RCDType0e { + return Bytes32{}, fmt.Errorf("invalid RCD type") + } + if len(sig) != RCDType0eSigSize { + return Bytes32{}, fmt.Errorf("invalid signature size") + } + + pubKey := []byte(rcd[1:]) // Omit RCD Type byte + pubKey = append([]byte{0x04}, pubKey...) // Uncompressed prefix + if !crypto.VerifySignature(pubKey, msg, sig[:64]) { // Ignore recovery byte + return Bytes32{}, fmt.Errorf("invalid signature") + } + + return sha256d(rcd), nil +} diff --git a/rcdsigner_test.go b/rcdsigner_test.go new file mode 100644 index 0000000..1f0ad3e --- /dev/null +++ b/rcdsigner_test.go @@ -0,0 +1,104 @@ +package factom_test + +import ( + crand "crypto/rand" + "encoding/hex" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + + "github.com/Factom-Asset-Tokens/factom/varintf" + + "github.com/stretchr/testify/assert" + + . "github.com/Factom-Asset-Tokens/factom" +) + +func TestValidateRCD0e(t *testing.T) { + fmt.Printf("%x\n", varintf.Encode(14)) + fmt.Println(varintf.Decode([]byte{0x0e})) +} + +func TestValidateRCD(t *testing.T) { + // Tests random created addresses creates signatures that pass + // validation and generate the expected rcdhash + + t.Run("rcd1", func(t *testing.T) { + for i := 0; i < 100; i++ { + assert := assert.New(t) + adr, err := GenerateFsAddress() + assert.NoError(err) + + valid, err := ValidateRCD(rcdFields(adr)) + assert.NoError(err) + rcdHash := adr.FAAddress() + assert.Equal(rcdHash[:], valid[:]) + } + }) + + t.Run("rcd-e", func(t *testing.T) { + for i := 0; i < 100; i++ { + assert := assert.New(t) + adr, err := GenerateEthSecret() + assert.NoError(err) + + valid, err := ValidateRCD(rcdFields(adr)) + assert.NoError(err) + rcdHash := adr.FAAddress() + assert.Equal(rcdHash[:], valid[:]) + } + }) +} + +// These are vectors taken from the etheruem repo +func TestValidateRCD0eVectors(t *testing.T) { + assert := assert.New(t) + + // Vector from + // https://github.com/ethereum/go-ethereum/blob/f03b2db7db123a569672b0caed5c1cd735c72ba7/crypto/signature_test.go#L31-L34 + testmsg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") + testsig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") + testpubkey, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") + + valid, err := ValidateRCD0e(append([]byte{0x0e}, testpubkey[1:]...), testsig, testmsg) + assert.NoError(err) + assert.False(valid.IsZero()) + + // TestVerifySignatureMalleable Vector from + // https://github.com/ethereum/go-ethereum/blob/f03b2db7db123a569672b0caed5c1cd735c72ba7/crypto/signature_test.go#L80-L82 + testsig, _ = hex.DecodeString("638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454") + testsig = append(testsig, 0) // Make it 65 bytes + + testpubkeyC, _ := hex.DecodeString("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") + ecdsaPub, err := crypto.DecompressPubkey(testpubkeyC) + assert.NoError(err) + testpubkey = crypto.FromECDSAPub(ecdsaPub) + + testmsg, _ = hex.DecodeString("d301ce462d3e639518f482c7f03821fec1e602018630ce621e1e7851c12343a6") + valid, err = ValidateRCD0e(append([]byte{0x0e}, testpubkey[1:]...), testsig, testmsg) + if err == nil { + t.Errorf("VerifySignature returned true for malleable signature") + } +} + +// generateVector prints out all the fields needed to check an implementation. +func generateVector() { + s, _ := GenerateEthSecret() + fmt.Printf("%20s: %s\n", "Private Key", s) + fmt.Printf("%20s: 0x%x\n", "Public Key", s.PublicKeyBytes()) + fmt.Printf("%20s: %s\n", "FAAddress", s.FAAddress()) + fmt.Printf("%20s: %s\n", "EthAddress", s.EthAddress()) + fmt.Println() + fmt.Printf("%20s: %x\n", "Digest", make([]byte, 32)) + fmt.Printf("%20s: %x\n", "Signature", s.Sign(make([]byte, 32))) +} + +func rcdFields(adr RCDSigner) (rcd []byte, sig []byte, digest []byte) { + digest = make([]byte, 32) + _, _ = crand.Read(digest) + + sig = adr.Sign(digest) + rcd = adr.RCD() + return +}