github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/agent.go (about)

     1  // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.)
     2  // Use of this source code is governed by GPLv3 found in the LICENSE file
     3  //---------------------------------------------------------------------------------------
     4  // key generation and marshal/unmarshaling and abstraction of agent for holochains
     5  
     6  package holochain
     7  
     8  import (
     9  	"crypto/rand"
    10  	"errors"
    11  	"fmt"
    12  	b58 "github.com/jbenet/go-base58"
    13  	ic "github.com/libp2p/go-libp2p-crypto"
    14  	peer "github.com/libp2p/go-libp2p-peer"
    15  	"io"
    16  	"os"
    17  	"path/filepath"
    18  	"runtime"
    19  )
    20  
    21  // AgentIdentity is the user's unique identity information in context of this holochain.
    22  // it follows AgentIdentitySchema in DNA
    23  type AgentIdentity string
    24  
    25  type AgentType int
    26  
    27  const (
    28  	LibP2P = iota
    29  )
    30  
    31  // Agent abstracts the key behaviors and connection to a holochain node address
    32  // Note that this is currently only a partial abstraction because the NodeID is always a libp2p peer.ID
    33  // to complete the abstraction so we could use other libraries for p2p2 network transaction we
    34  // would need to also abstract a matching NodeID type
    35  type Agent interface {
    36  	Identity() AgentIdentity
    37  	SetIdentity(id AgentIdentity)
    38  	AgentType() AgentType
    39  	GenKeys(seed io.Reader) error
    40  	PrivKey() ic.PrivKey
    41  	PubKey() ic.PubKey
    42  	EncodePubKey() (string, error)
    43  	NodeID() (peer.ID, string, error)
    44  	AgentEntry(revocation Revocation) (AgentEntry, error)
    45  }
    46  
    47  type LibP2PAgent struct {
    48  	identity AgentIdentity
    49  	priv     ic.PrivKey
    50  	pub      ic.PubKey // cached so as not to recalculate all the time
    51  }
    52  
    53  func (a *LibP2PAgent) Identity() AgentIdentity {
    54  	return a.identity
    55  }
    56  func (a *LibP2PAgent) SetIdentity(id AgentIdentity) {
    57  	a.identity = id
    58  }
    59  
    60  func (a *LibP2PAgent) AgentType() AgentType {
    61  	return LibP2P
    62  }
    63  
    64  func (a *LibP2PAgent) PrivKey() ic.PrivKey {
    65  	return a.priv
    66  }
    67  
    68  func (a *LibP2PAgent) PubKey() ic.PubKey {
    69  	return a.pub
    70  }
    71  
    72  func (a *LibP2PAgent) EncodePubKey() (b58pk string, err error) {
    73  	var pk []byte
    74  	pk, err = ic.MarshalPublicKey(a.pub)
    75  	if err != nil {
    76  		return
    77  	}
    78  	b58pk = b58.Encode(pk)
    79  	return
    80  }
    81  
    82  func DecodePubKey(b58pk string) (pubKey ic.PubKey, err error) {
    83  	var pubKeyBytes []byte
    84  	pubKeyBytes = b58.Decode(b58pk)
    85  	pubKey, err = ic.UnmarshalPublicKey(pubKeyBytes)
    86  	return
    87  }
    88  
    89  func (a *LibP2PAgent) GenKeys(seed io.Reader) (err error) {
    90  	var priv ic.PrivKey
    91  	if seed == nil {
    92  		seed = rand.Reader
    93  	}
    94  	priv, _, err = ic.GenerateEd25519Key(seed)
    95  	if err != nil {
    96  		return
    97  	}
    98  	a.priv = priv
    99  	a.pub = priv.GetPublic()
   100  	return
   101  }
   102  
   103  func (a *LibP2PAgent) NodeID() (nodeID peer.ID, nodeIDStr string, err error) {
   104  	nodeID, err = peer.IDFromPrivateKey(a.PrivKey())
   105  	if err == nil {
   106  		nodeIDStr = peer.IDB58Encode(nodeID)
   107  	}
   108  	return
   109  }
   110  
   111  func (a *LibP2PAgent) AgentEntry(revocation Revocation) (entry AgentEntry, err error) {
   112  
   113  	entry = AgentEntry{
   114  		Identity: a.Identity(),
   115  	}
   116  	if revocation != nil {
   117  		entry.Revocation, err = revocation.Marshal()
   118  		if err != nil {
   119  			return
   120  		}
   121  	}
   122  	entry.PublicKey, err = a.EncodePubKey()
   123  	if err != nil {
   124  		return
   125  	}
   126  	return
   127  }
   128  
   129  // NewAgent creates an agent structure of the given type
   130  // Note: currently only IPFS agents are implemented
   131  func NewAgent(agentType AgentType, identity AgentIdentity, seed io.Reader) (agent Agent, err error) {
   132  	switch agentType {
   133  	case LibP2P:
   134  		a := LibP2PAgent{
   135  			identity: identity,
   136  		}
   137  		err = a.GenKeys(seed)
   138  		if err != nil {
   139  			return
   140  		}
   141  		agent = &a
   142  	default:
   143  		err = fmt.Errorf("unknown key type: %d", agentType)
   144  	}
   145  	return
   146  }
   147  
   148  // SaveAgent saves out the keys and agent name to the given directory
   149  func SaveAgent(path string, agent Agent) (err error) {
   150  	WriteFile([]byte(agent.Identity()), path, AgentFileName)
   151  	if err != nil {
   152  		return
   153  	}
   154  	if FileExists(path, PrivKeyFileName) {
   155  		return errors.New("keys already exist")
   156  	}
   157  	var k []byte
   158  	k, err = agent.PrivKey().Bytes()
   159  	if err != nil {
   160  		return
   161  	}
   162  	err = WriteFile(k, path, PrivKeyFileName)
   163  	os.Chmod(filepath.Join(path, PrivKeyFileName), OS_USER_R)
   164  	return
   165  }
   166  
   167  // LoadAgent gets the agent identity and private key from the specified directory
   168  // TODO confirm against chain?
   169  func LoadAgent(path string) (agent Agent, err error) {
   170  	var perms os.FileMode
   171  
   172  	// TODO, make this check also work on windows instead of just bypassing!
   173  	if runtime.GOOS != "windows" {
   174  		perms, err = filePerms(path, PrivKeyFileName)
   175  		if err != nil {
   176  			return
   177  		}
   178  		if perms != OS_USER_R {
   179  			err = errors.New(filepath.Join(path, PrivKeyFileName) + " file not read-only")
   180  			return
   181  		}
   182  	}
   183  	var identity []byte
   184  	identity, err = ReadFile(path, AgentFileName)
   185  	if err != nil {
   186  		return
   187  	}
   188  	a := LibP2PAgent{
   189  		identity: AgentIdentity(identity),
   190  	}
   191  	k, err := ReadFile(path, PrivKeyFileName)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	a.priv, err = ic.UnmarshalPrivateKey(k)
   196  	if err != nil {
   197  		return
   198  	}
   199  	a.pub = a.priv.GetPublic()
   200  	agent = &a
   201  	return
   202  }