Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rcd e #133

Closed
wants to merge 7 commits into from
Closed

Rcd e #133

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 235 additions & 1 deletion addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ package factom

import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"strings"

"github.com/FactomProject/btcutil/base58"
ed "github.com/FactomProject/ed25519"
"github.com/FactomProject/go-bip32"
"github.com/FactomProject/go-bip39"
"github.com/FactomProject/go-bip44"
"github.com/ethereum/go-ethereum/crypto"
)

// Common Address errors
Expand All @@ -33,26 +37,51 @@ const (
FactoidSec
ECPub
ECSec
EthSec // 0x[32byte hex]
EthFA // Fe...
EthGatewayFA // FE...
)

const (
AddressLength = 38
PrefixLength = 2
ChecksumLength = 4
BodyLength = AddressLength - ChecksumLength

// In hex characters, not bytes
EthSecretLength = 64
EthSecretPrefix = 2
)

var (
fcPubPrefix = []byte{0x5f, 0xb1}
fcSecPrefix = []byte{0x64, 0x78}
ecPubPrefix = []byte{0x59, 0x2a}
ecSecPrefix = []byte{0x5d, 0xb6}

// RCD-e prefixes
fePubPrefix = []byte{0x62, 0xf4} // Fe...
fEPubPrefix = []byte{0x60, 0x28} // FE...
)

// AddressStringType determin the type of address from the given string.
// AddressStringType determine the type of address from the given string.
// AddressStringType must return one of the defined address types;
// InvalidAddress, FactoidPub, FactoidSec, ECPub, or ECSec.
func AddressStringType(s string) addressStringType {
if has0xPrefix(s) {
_, err := hex.DecodeString(s[2:])
if err != nil {
return InvalidAddress
}

// Prefix + Secret length check means secret key
if len(s) == EthSecretPrefix+EthSecretLength {
return EthSec
}

return InvalidAddress
}

p := base58.Decode(s)

if len(p) != AddressLength {
Expand All @@ -76,11 +105,20 @@ func AddressStringType(s string) addressStringType {
return FactoidPub
case bytes.Equal(prefix, fcSecPrefix):
return FactoidSec
case bytes.Equal(prefix, fePubPrefix):
return EthFA
case bytes.Equal(prefix, fEPubPrefix):
return EthGatewayFA
default:
return InvalidAddress
}
}

// 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')
}

// IsValidAddress checks that a string is a valid address of one of the defined
// address types.
//
Expand Down Expand Up @@ -425,6 +463,202 @@ func (a *FactoidAddress) String() string {
return base58.Encode(buf.Bytes())
}

// EthSecret is a Factoid Redeem Condition Datastructure (a type 0x0e RCD is
// just the public key) and a corresponding secret key.
type EthSecret struct {
RCD RCD
Sec *[32]byte
}

// NewFactoidAddress creates a blank rcd/secret key pair for a Factoid Address.
func NewEthSecret() *EthSecret {
a := new(EthSecret)
r := NewRCDe()
r.Pub = new([64]byte)
a.RCD = r
a.Sec = new([32]byte)
return a
}

func (a EthSecret) PrivateKey() *ecdsa.PrivateKey {
secret, err := crypto.ToECDSA(a.Sec[:])
if err != nil {
return nil
}
return secret
}

func (a EthSecret) PublicKey() ecdsa.PublicKey {
secret := a.PrivateKey()
if secret == nil {
return ecdsa.PublicKey{}
}
return secret.PublicKey
}

// GetEthSecret creates a Factoid Address rcd/secret key pair from a secret
// Factoid Address string i.e. 0x1234...
func GetEthSecret(s string) (*EthSecret, error) {
if AddressStringType(s) != EthSec {
return nil, ErrInvalidAddress
}

sec, err := hex.DecodeString(s[2:])
if err != nil { // Should never hit this
return nil, ErrInvalidAddress
}

return MakeEthSecret(sec)
}

// MakeEthSecret creates a EthSecret rcd/secret key pair from a
// secret key []byte.
func MakeEthSecret(sec []byte) (*EthSecret, error) {
if len(sec) != 32 {
return nil, ErrSecKeyLength
}

a := NewEthSecret()
err := a.UnmarshalBinary(sec)
if err != nil {
return nil, err
}

return a, nil
}

// MakeBIP44EthSecret generates an EthSecret from a 12 word mnemonic,
// an account index, a chain index, and an address index, according to the bip44
// standard for multicoin wallets.
func MakeBIP44EthSecret(mnemonic string, account, chain, address uint32) (*EthSecret, error) {
mnemonic, err := ParseMnemonic(mnemonic)
if err != nil {
return nil, err
}

child, err := bip44.NewKeyFromMnemonic(mnemonic, bip44.TypeEther, account, chain, address)
if err != nil {
return nil, err
}

// TODO: Verify this against eth derivation
return MakeEthSecret(child.Key)
}

func (a *EthSecret) UnmarshalBinary(data []byte) error {
_, err := a.UnmarshalBinaryData(data)
return err
}

func (a *EthSecret) UnmarshalBinaryData(data []byte) ([]byte, error) {
if len(data) < 32 {
return nil, ErrSecKeyLength
}

if a.Sec == nil {
a.Sec = new([32]byte)
}

copy(a.Sec[:], data[:32])
r := NewRCDe()

pubBytes := a.PubBytes()
if len(pubBytes) != 64 {
return nil, fmt.Errorf("incorrect number of bytes for public key")
}
copy(r.Pub[:], pubBytes)
a.RCD = r

return data[32:], nil
}

func (a *EthSecret) MarshalBinary() ([]byte, error) {
return a.SecBytes()[:32], nil
}

// RCDHash returns the Hash of the Redeem Condition Datastructure from a Factoid
// Address.
func (a *EthSecret) RCDHash() []byte {
return a.RCD.Hash()
}

// RCDType returns the Redeem Condition Datastructure type used by the Factoid
// Address.
func (a *EthSecret) RCDType() uint8 {
return a.RCD.Type()
}

// PubBytes returns the byte representation of the public key
func (a EthSecret) PubBytes() []byte {
pub := a.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:]
}

// EthAddress returns the linked ether address
func (a EthSecret) EthAddress() string {
return crypto.PubkeyToAddress(a.PublicKey()).String()
}

// SecBytes returns the []byte representation of the secret key.
func (a *EthSecret) SecBytes() []byte {
return a.Sec[:]
}

// SecFixed returns the fixed size secret key ([64]byte).
func (a *EthSecret) SecFixed() *[32]byte {
return a.Sec
}

// SecString returns the string encoding of the secret key i.e. 0x123...
func (a EthSecret) SecString() string {
str := hex.EncodeToString(a.Sec[:])
return "0x" + str
}

// Returns the FA address
func (a EthSecret) FAString() string {
return EncodeRCDHash(fcPubPrefix, a.RCDHash())
}

// Returns the FE address
func (a EthSecret) GateWayAddress() string {
return EncodeRCDHash(fEPubPrefix, a.RCDHash())
}

func EthGatewayToRegular(s string) (string, error) {
if AddressStringType(s) != EthGatewayFA {
return "", fmt.Errorf("expected an ethereum gateway address")
}

p := base58.Decode(s)
return EncodeRCDHash(fePubPrefix, p[PrefixLength:BodyLength]), nil
}

// Returns the Fe address
func (a EthSecret) String() string {
return EncodeRCDHash(fePubPrefix, a.RCDHash())
}

func EncodeRCDHash(prefix []byte, payload []byte) string {
buf := new(bytes.Buffer)

// FE address prefix
buf.Write(prefix)

// RCD Hash
buf.Write(payload)

// Checksum
check := shad(buf.Bytes())[:ChecksumLength]
buf.Write(check)

return base58.Encode(buf.Bytes())
}

// ParseMnemonic parse and validate a bip39 mnumonic string. Remove extra
// spaces, capitalization, etc. Return an error if the string is invalid.
func ParseMnemonic(mnemonic string) (string, error) {
Expand Down
26 changes: 23 additions & 3 deletions addresses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func TestAddressStringType(t *testing.T) {
a2 = "Fs1KWJrpLdfucvmYwN2nWrwepLn8ercpMbzXshd1g8zyhKXLVLWj"
a3 = "EC2DKSYyRcNWf7RS963VFYgMExoHRYLHVeCfQ9PGPmNzwrcmgm2r"
a4 = "Es2Rf7iM6PdsqfYCo3D1tnAR65SkLENyWJG1deUzpRMQmbh9F3eG"
a5 = "0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315"
)

if v := AddressStringType(a0); v != InvalidAddress {
Expand All @@ -106,13 +107,16 @@ func TestAddressStringType(t *testing.T) {
t.Errorf("wrong address type %s %#v", a1, v)
}
if v := AddressStringType(a2); v != FactoidSec {
t.Errorf("wrong address type %s %#v", a1, v)
t.Errorf("wrong address type %s %#v", a2, v)
}
if v := AddressStringType(a3); v != ECPub {
t.Errorf("wrong address type %s %#v", a1, v)
t.Errorf("wrong address type %s %#v", a3, v)
}
if v := AddressStringType(a4); v != ECSec {
t.Errorf("wrong address type %s %#v", a1, v)
t.Errorf("wrong address type %s %#v", a4, v)
}
if v := AddressStringType(a5); v != EthSec {
t.Errorf("wrong address type %s %#v", a5, v)
}
}

Expand Down Expand Up @@ -271,6 +275,22 @@ func TestMakeBIP44FactoidAddress(t *testing.T) {
}
}

func TestMakeBIP44EthAddress(t *testing.T) {
m := "yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow"
cannonAdr := "0xA27DF20E6579aC472481F0Ea918165d24bFb713b"

eth, err := MakeBIP44EthSecret(m, bip32.FirstHardenedChild, 0, 0)
if err != nil {
t.Error(err)
}

if eth.EthAddress() != cannonAdr {
t.Errorf(
"incorrect factoid address from 12 words: got %s expecting %s",
eth.EthAddress(), cannonAdr)
}
}

func TestParseAndValidateMnemonic(t *testing.T) {
goodms := []string{
"yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow yellow", // valid
Expand Down
20 changes: 17 additions & 3 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading