From 7b99039c34dd513d2f1feca49ad726d46f309123 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 21 Sep 2017 12:19:21 -0400 Subject: [PATCH] make signBytesHRS a method on LastSignedInfo --- types/priv_validator.go | 248 +++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 116 deletions(-) diff --git a/types/priv_validator.go b/types/priv_validator.go index 7fe6e1530..892a4f06a 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -34,7 +34,8 @@ func voteToStep(vote *Vote) int8 { } } -// PrivValidator defines the functionality of a local Tendermint validator. +// PrivValidator defines the functionality of a local Tendermint validator +// that signs votes, proposals, and heartbeats, and never double signs. type PrivValidator interface { Address() data.Bytes // redundant since .PubKey().Address() PubKey() crypto.PubKey @@ -61,77 +62,56 @@ type PrivValidatorFS struct { filePath string } -// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath -// or else generates a new one and saves it to the filePath. -func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS { - var PrivValidatorFS *PrivValidatorFS - if _, err := os.Stat(filePath); err == nil { - PrivValidatorFS = LoadPrivValidatorFS(filePath) - } else { - PrivValidatorFS = GenPrivValidatorFS(filePath) - PrivValidatorFS.Save() - } - return PrivValidatorFS +// Address returns the address of the validator. +// Implements PrivValidator. +func (pv *PrivValidatorFS) Address() data.Bytes { + return pv.ID.Address } -// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath. -func LoadPrivValidatorFS(filePath string) *PrivValidatorFS { - privValJSONBytes, err := ioutil.ReadFile(filePath) - if err != nil { - cmn.Exit(err.Error()) - } - privVal := PrivValidatorFS{} - err = json.Unmarshal(privValJSONBytes, &privVal) - if err != nil { - cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) - } - - privVal.filePath = filePath - return &privVal +// PubKey returns the public key of the validator. +// Implements PrivValidator. +func (pv *PrivValidatorFS) PubKey() crypto.PubKey { + return pv.ID.PubKey } -// GenPrivValidatorFS generates a new validator with randomly generated private key -// and sets the filePath, but does not call Save(). -func GenPrivValidatorFS(filePath string) *PrivValidatorFS { - privKey := crypto.GenPrivKeyEd25519().Wrap() - return &PrivValidatorFS{ - ID: ValidatorID{privKey.PubKey().Address(), privKey.PubKey()}, - Info: LastSignedInfo{ - LastStep: stepNone, - }, - Signer: NewDefaultSigner(privKey), - filePath: filePath, +// SignVote signs a canonical representation of the vote, along with the chainID. +// Implements PrivValidator. +func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { + privVal.mtx.Lock() + defer privVal.mtx.Unlock() + signature, err := privVal.Info.SignBytesHRS(privVal.Signer, + vote.Height, vote.Round, voteToStep(vote), SignBytes(chainID, vote)) + if err != nil { + return errors.New(cmn.Fmt("Error signing vote: %v", err)) } + privVal.save() + vote.Signature = signature + return nil } -// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom -// signer object. The PrivValidatorFS handles double signing prevention by persisting -// data to the filePath, while the Signer handles the signing. -// If the filePath does not exist, the PrivValidatorFS must be created manually and saved. -func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(ValidatorID) Signer) *PrivValidatorFS { - privValJSONBytes, err := ioutil.ReadFile(filePath) - if err != nil { - cmn.Exit(err.Error()) - } - privVal := PrivValidatorFS{} - err = json.Unmarshal(privValJSONBytes, &privVal) +// SignProposal signs a canonical representation of the proposal, along with the chainID. +// Implements PrivValidator. +func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { + privVal.mtx.Lock() + defer privVal.mtx.Unlock() + signature, err := privVal.Info.SignBytesHRS(privVal.Signer, + proposal.Height, proposal.Round, stepPropose, SignBytes(chainID, proposal)) if err != nil { - cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + return fmt.Errorf("Error signing proposal: %v", err) } - - privVal.filePath = filePath - privVal.Signer = signerFunc(privVal.ID) - return &privVal -} - -// Address returns the address of the validator. -func (pv *PrivValidatorFS) Address() data.Bytes { - return pv.ID.Address + privVal.save() + proposal.Signature = signature + return nil } -// PubKey returns the public key of the validator. -func (pv *PrivValidatorFS) PubKey() crypto.PubKey { - return pv.ID.PubKey +// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. +// Implements PrivValidator. +func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { + privVal.mtx.Lock() + defer privVal.mtx.Unlock() + var err error + heartbeat.Signature, err = privVal.Signer.Sign(SignBytes(chainID, heartbeat)) + return err } // Save persists the PrivValidatorFS to disk. @@ -192,43 +172,102 @@ func (privVal *PrivValidatorFS) Reset() { privVal.Save() } -// SignVote signs a canonical representation of the vote, along with the chainID. -func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - signature, err := privVal.signBytesHRS(vote.Height, vote.Round, voteToStep(vote), SignBytes(chainID, vote)) +// String returns a string representation of the PrivValidatorFS. +func (privVal *PrivValidatorFS) String() string { + info := privVal.Info + return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.Address(), info.LastHeight, info.LastRound, info.LastStep) +} + +// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath +// or else generates a new one and saves it to the filePath. +func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS { + var PrivValidatorFS *PrivValidatorFS + if _, err := os.Stat(filePath); err == nil { + PrivValidatorFS = LoadPrivValidatorFS(filePath) + } else { + PrivValidatorFS = GenPrivValidatorFS(filePath) + PrivValidatorFS.Save() + } + return PrivValidatorFS +} + +// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath. +func LoadPrivValidatorFS(filePath string) *PrivValidatorFS { + privValJSONBytes, err := ioutil.ReadFile(filePath) if err != nil { - return errors.New(cmn.Fmt("Error signing vote: %v", err)) + cmn.Exit(err.Error()) } - vote.Signature = signature - return nil + privVal := PrivValidatorFS{} + err = json.Unmarshal(privValJSONBytes, &privVal) + if err != nil { + cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + } + + privVal.filePath = filePath + return &privVal } -// SignProposal signs a canonical representation of the proposal, along with the chainID. -func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - signature, err := privVal.signBytesHRS(proposal.Height, proposal.Round, stepPropose, SignBytes(chainID, proposal)) +// GenPrivValidatorFS generates a new validator with randomly generated private key +// and sets the filePath, but does not call Save(). +func GenPrivValidatorFS(filePath string) *PrivValidatorFS { + privKey := crypto.GenPrivKeyEd25519().Wrap() + return &PrivValidatorFS{ + ID: ValidatorID{privKey.PubKey().Address(), privKey.PubKey()}, + Info: LastSignedInfo{ + LastStep: stepNone, + }, + Signer: NewDefaultSigner(privKey), + filePath: filePath, + } +} + +// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom +// signer object. The PrivValidatorFS handles double signing prevention by persisting +// data to the filePath, while the Signer handles the signing. +// If the filePath does not exist, the PrivValidatorFS must be created manually and saved. +func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(ValidatorID) Signer) *PrivValidatorFS { + privValJSONBytes, err := ioutil.ReadFile(filePath) if err != nil { - return fmt.Errorf("Error signing proposal: %v", err) + cmn.Exit(err.Error()) } - proposal.Signature = signature - return nil + privVal := PrivValidatorFS{} + err = json.Unmarshal(privValJSONBytes, &privVal) + if err != nil { + cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + } + + privVal.filePath = filePath + privVal.Signer = signerFunc(privVal.ID) + return &privVal } -// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. -func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - var err error - heartbeat.Signature, err = privVal.Signer.Sign(SignBytes(chainID, heartbeat)) - return err +//------------------------------------- + +// ValidatorID contains the identity of the validator. +type ValidatorID struct { + Address data.Bytes `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` } -// check if there's a regression. Else sign and write the hrs+signature to disk -func (privVal *PrivValidatorFS) signBytesHRS(height, round int, step int8, signBytes []byte) (crypto.Signature, error) { +//------------------------------------- + +// LastSignedInfo contains information about the latest +// data signed by a validator to help prevent double signing. +type LastSignedInfo struct { + LastHeight int `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures + LastSignBytes data.Bytes `json:"last_signbytes,omitempty"` // so we dont lose signatures +} + +// SignBytesHRS signs the given signBytes with the signer if the height/round/step (HRS) +// are greater than the latest state of the LastSignedInfo. If the HRS are equal, +// it returns the LastSignedInfo.LastSignature. +func (info *LastSignedInfo) SignBytesHRS(signer Signer, + height, round int, step int8, signBytes []byte) (crypto.Signature, error) { + sig := crypto.Signature{} - info := privVal.Info // If height regression, err if info.LastHeight > height { return sig, errors.New("Height regression") @@ -262,46 +301,23 @@ func (privVal *PrivValidatorFS) signBytesHRS(height, round int, step int8, signB } // Sign - sig, err := privVal.Signer.Sign(signBytes) + sig, err := signer.Sign(signBytes) if err != nil { return sig, err } // Persist height/round/step - privVal.Info.LastHeight = height - privVal.Info.LastRound = round - privVal.Info.LastStep = step - privVal.Info.LastSignature = sig - privVal.Info.LastSignBytes = signBytes - privVal.save() + info.LastHeight = height + info.LastRound = round + info.LastStep = step + info.LastSignature = sig + info.LastSignBytes = signBytes return sig, nil } -// String returns a string representation of the PrivValidatorFS. -func (privVal *PrivValidatorFS) String() string { - info := privVal.Info - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.Address(), info.LastHeight, info.LastRound, info.LastStep) -} - //------------------------------------- -// ValidatorID contains the identity of the validator. -type ValidatorID struct { - Address data.Bytes `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` -} - -// LastSignedInfo contains information about the latest -// data signed by a validator to help prevent double signing. -type LastSignedInfo struct { - LastHeight int `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures - LastSignBytes data.Bytes `json:"last_signbytes,omitempty"` // so we dont lose signatures -} - // Signer is an interface that defines how to sign messages. // It is the caller's duty to verify the msg before calling Sign, // eg. to avoid double signing.