Conversation
|
For now, you can manually instantiate a PrivValidator object and then you don't have to provide the PrivKey as described in the comment. That would allow you to provide it a custom signer. |
|
@adrianbrink I've created #641 to implement those changes. |
There was a problem hiding this comment.
Thanks for this. Love exporting the commands. Can we drop the new binary though, I think it will be confusing for now. Folks building HSMs can have repos that compose the Tendermint binary with their custom HSM (doesn't need to be a fork, just a single main file that calls AddCommand for everything). Once things stabilize, we can look to integrate their libs into Tendermint proper, but let's keep it separate for now.
cmd/tendermint/commands/run_node.go
Outdated
| RunE: runNode, | ||
| } | ||
|
|
||
| // NewRunNodeCmd creates and starts a tendermint node. It allows the user to |
There was a problem hiding this comment.
NewRunNodeCmd returns a cobra Command which ...
cmd/tendermint/commands/run_node.go
Outdated
|
|
||
| var runNodeCmd = &cobra.Command{ | ||
| // RunNodeCmd creates and starts a tendermint node. | ||
| var RunNodeCmd = &cobra.Command{ |
There was a problem hiding this comment.
isn't this redundant if we have the function below?
There was a problem hiding this comment.
I wanted to keep it to not break the API, but I did it anyway so now it's gone :-)
cmd/tendermint/commands/run_node.go
Outdated
| Short: "Run the tendermint node", | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| // Wait until the genesis doc becomes available | ||
| // This is for Mintnet compatibility. |
There was a problem hiding this comment.
we don't need this anymore
types/priv_validator.go
Outdated
| return LoadPrivValidatorWithSigner(filePath, nil) | ||
| } | ||
|
|
||
| func LoadPrivValidatorWithSigner(filePath string, signer Signer) *PrivValidator { |
There was a problem hiding this comment.
Why bother with this ? Why not just
pv := LoadPrivValidator(filePath)
pv.SetSigner(signer)
?
There was a problem hiding this comment.
@ebuchman That was the fix for my comment above.
There was a problem hiding this comment.
But can't we just call the SetSigner method to override ?
There was a problem hiding this comment.
If you follow through the logic of LoadPrivValidator, you'll find it eventually operates on the private key using the default signer. The call to setPubKeyAndAddress calls privVal.Signer.PubKey(), which in turn calls ds.priv.PubKey().
There was a problem hiding this comment.
Right of course. If the priv_key is empty, this won't work. Thanks.
There was a problem hiding this comment.
Ok, so what I would recommend is to change LoadPrivValidator to check if the privkey is empty. If it is, then it should return, and the caller can then call SetSigner. If it's not empty, then it can do
privVal.Signer = NewDefaultSigner(privVal.PrivKey)
privVal.setPubKeyAndAddress()
using the priv key from the file.
Does that make sense?
I'm just wary of making new exposed functions :)
There was a problem hiding this comment.
I had assumed developers would borrow your file format to store their own info. Hence privkey wouldn't be empty, but would be something specific to the crypto implementation (perhaps a PKCS#11 key label).
In case it affects your decision, I've recently decided to leap over this basic implement step and head straight for a new privValidator that runs inside an HSM (as we discussed). So this change we are discussing would be for the benefit of others taking a simpler approach to key management.
There was a problem hiding this comment.
It's just occurred to me (now that I've started coding in earnest), that to benefit from your existing file format, the signer would need to be created after the priv_validator.json file has been read. So these changes might need some additional thought. I'll code up something early next week and will share around.
3077f3c to
13bdc79
Compare
|
@ebuchman Should I work on implementing the 2nd abstraction in this PR or can we merge this and open a new one for the next step? |
|
@dmjones500 Does this work for you? The next step is to extract PrivValidator as an interface so that the HSM can handle everything (including round tracking). |
|
@adrianbrink Sounds good to me. I've made some progress on a basic implementation, just overriding the signer. As soon as you've extracted an interface, I'll bring more logic into the HSM. |
cmd/tendermint/commands/run_node.go
Outdated
| // * Use an external signer for their validators | ||
| // * Supply an in-proc abci app | ||
| // should import github.com/tendermint/tendermint/node and implement | ||
| // their own run_node to call node.NewNode (instead of node.NewNodeDefault) |
There was a problem hiding this comment.
Maybe we need to update this comment?
cmd/tendermint/commands/version.go
Outdated
| var VersionCmd = &cobra.Command{ | ||
| Use: "version", | ||
| Short: "Show version info", | ||
| Short: "Show version infoooooooo", |
types/priv_validator.go
Outdated
|
|
||
| func (privVal *PrivValidator) SetSigner(s Signer) { | ||
| privVal.Signer = s | ||
| privVal.setPubKeyAndAddress() |
There was a problem hiding this comment.
rename to updatePubKeyAndAddressFromSigner or derivePubKeyAndAddressFromSigner ?
13bdc79 to
9085953
Compare
|
Still working on this. Looking better though! |
|
To add to my comment on #647 I think that to outside parties it makes more sense to just ask for a Go However I think the fact you need them is a symptom that your I also think that: type Signable interface {
WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
}Should become: type Digestable interface {
Digest(w io.Writer) (int, error)
}If you want func JointDigest(w io.Write, digestables... Digestable) (n int, err error) {
for _, d := range digestables {
dn, err := d.Digest(w)
n += dn
if err != nil {
return
}
}
return
} |
|
@silasdavis Teasing out a separate Signer interface (based on Go's crypto module) sounds sensible. However, there are cases where it would be useful to override normal priv validator behaviour too. The specific example I have in mind is incorporating the validation logic within a trusted component. Rather than simply saying "sign this" to your HSM (or enclave, or whatever), you pass sufficient information to allow an informed decision about performing the signature. This use case is what is triggering the redesign work to abstract the priv validator. (Or at least it's one of the reasons why). |
|
@dmjones500 having validation/verification happen in a trusted enclave makes sense and sounds like a nice feature. And I see t(hat you need to stay within that enclave to ensure the bytes that get signed were the same ones it verified, so I suppose having two calls to hardware of This being the case then perhaps the interface you have is closer to what type softwarePrivValidator struct {
signer crypto.Signer
}
...
func SoftwarePrivValidator(signer crypto.Signer) types.PrivValidator {
return &softwarePrivValidator{
signer: signer,
}
}Then HSM-integrators that want to host he verification logic can provide the message-specific signing functions with a custom |
Codecov Report
@@ Coverage Diff @@
## develop #637 +/- ##
===========================================
+ Coverage 55.67% 56.37% +0.69%
===========================================
Files 81 81
Lines 8404 8403 -1
===========================================
+ Hits 4679 4737 +58
+ Misses 3331 3262 -69
- Partials 394 404 +10 |
|
@silasdavis take a look at latest commit re NewNode. Does that look good to you? I think there are two use cases for in-process here:
I think PrivValidator is fine rather than Signer because we expose a function to make a new PrivValidator from a Signer: Does that sound sufficient? I'll still look into using Go's crypto.Signer |
|
@ebuchman looks great so far. If 1 is a use case you know about then that sounds reasonable. However, personally my concern would be feature creep, whereby people want a little bit of extra flexibility in configuring the CLI and you end up maintaining half-arsed command line wrapper when you just want to be a good consensus engine. You say they can copy the main.go and change things, I'd say why not have them just copy Regarding I understand that the Can we remove the dependence on the filesystem from PrivValidator altogether and just have it as an in-memory object (connected to some signing resource you don't control)? Even for a |
|
So to give a bit of background the motivation on this 'leave the filesystem alone' (and the DBProvider) stuff from me is:
This looks like it will be really useful for me so thanks ! |
|
I suppose what I'm really asking is we separate the verification logic from how the func LoadPrivValidatorDBWithSigner(dbProvider DBProvider,
signerFunc func(ValidatorID) Signer) *PrivValidatorDB {Or just: func LoadPrivValidatorWithSigner(infoProvider func() *LastSignedInfo,
signerFunc func(ValidatorID) Signer) PrivValidator { |
So they get the rest of the CLI as well - init, unsafe_reset, etc.
No, I think the PrivValidator needs a persistent store to write to. We can have it take something more generic though, like a Writer, rather than a file name. But we do need an object that wraps the signer that checks persisted conditions before signing. As for: this seems nice, but we need the PrivValidator to persist the LastSignedInfo after every time it changes. So something, either the PrivValidator itself or the LastSignedInfo, needs to expose a Save function, which needs to be able to be backed by something persistent. For testing, it should be fine to use an in memory item. |
|
As for crypto.Signer, I don't think we should use this yet. We need a go-crypto.Signature in the rest of the codebase, so we have to convert the returned sig []byte into a crypto.Signature, which means the thing returned by a generic signer would need to either be go-wire encoded according to go-crypto type bytes, or we'd need some other scheme. In either case, I think we're better just being explicit about returning go-crypto.Signature for now. Let me know if you have other/better ideas about this. As for |
|
Here's an idea ... what if we just move signBytesHRS onto the LastSignedInfo and expose it - then it becomes super easy to implement a PrivValidator using whatever backing you want! |
|
I implemented a wrapper around crypto.PrivKey and crypto.PubKey to use the ledger nano s. It works pretty well, except for a question about error handling. By doing so, I was able to use the cli key manager embedded in basecoin with almost no changes (just registering a CreateLedger function to parallel CreateEd25519. I am not sure what the specific use-case here is. Which HSM? I assume no human input, but it wants to verify the data before signing as well? As a separate app? Maybe my work on tendermint/go-crypto#37 can be an inspiration of what (or what not) to do for hardware integration |
|
Nice idea with SignByteHRS, but if the point is we just want to use a custom signer... maybe this inversion would make sense: current proposal: alternative: But then that doesn't make for full security, as the hardware signer shouldn't trust lastsignedinfo coming from code. maybe this: and then a signer should do the check on LastSignedInfo itself. Does that make sense? |
|
Should |
|
Yes, @silasdavis that was my idea. If the signer doesn't check, just keeps the keys safe, then the current work is fine, and just a new Signer implementation. (Which will need a The comments from @dmjones500 suggested that they would like to store the LastSignedInfo on the hardware device, then get enough info before signing to make sure they never double-sign. Maybe stateless-signer (that trusts no double-sigs come to it), and a stateful-signer that checks for double-sigs |
c977cf6 to
7b99039
Compare
16b58e8 to
75b97a5
Compare
|
Ok, I reverted the changes to PrivValidatorFS pending a more formal write up and further discussion. So this PR is now more just about the new interface |
9315896 to
8ae2ffd
Compare
a922984 to
2131f8d
Compare
|
|
||
| // Defaults to tcp | ||
| // ProtocolAndAddress returns the transport protocol | ||
| // and the ip address from the given string. Defaults to tcp. |
There was a problem hiding this comment.
This probably shouldn't be here
| } | ||
| genDoc.Validators = []types.GenesisValidator{types.GenesisValidator{ | ||
| PubKey: privValidator.PubKey, | ||
| PubKey: privValidator.GetPubKey(), |
There was a problem hiding this comment.
because it's already a field.
Once we refactor the PrivValidator more fully (eg. into ID, Signer, LastSignedInfo), we can use PubKey()
| // ResetAll removes the privValidator files. | ||
| // Exported so other CLI tools can use it | ||
| func ResetAll(dbDir, privValFile string, logger log.Logger) { | ||
| resetPrivValidatorLocal(privValFile, logger) |
There was a problem hiding this comment.
resetPrivValidatorFS? to follow the convention https://github.com/tendermint/tendermint/pull/637/files#diff-788e1cba0a726db5563f24b99e3913a1L25
node/node.go
Outdated
| } | ||
| state := sm.LoadState(stateDB) | ||
| if state == nil { | ||
| genDoc, err := genDocProvider() |
There was a problem hiding this comment.
gen as generate or as genesis?
cmd/tendermint/commands/run_node.go
Outdated
| } | ||
| // FuncSignerAndApp takes a config and returns a PrivValidator and ClientCreator. | ||
| // It allows other projects to make Tendermint binaries with custom signers and applications. | ||
| type FuncSignerAndApp func(*cfg.Config) (types.PrivValidator, proxy.ClientCreator) |
There was a problem hiding this comment.
oops this should have been removed.
cmd/tendermint/commands/run_node.go
Outdated
| n.RunForever() | ||
| // NewRunNodeCmd returns the command that allows the CLI to start a | ||
| // node. It can be used with a custom PrivValidator and in-process ABCI application. | ||
| func NewRunNodeCmd(nodeFunc nm.NodeProvider) *cobra.Command { |
There was a problem hiding this comment.
nodeFunc sounds like it's a (func) type.
If it's a function, how about nodeFn as a convention, or perhaps nodeCb, or in this case, provider?
There was a problem hiding this comment.
will change to nodeProvider
There was a problem hiding this comment.
xxxFn sounds good as a convention though. @zramsay want to add to coding repo? function vars should be followed by Fn
There was a problem hiding this comment.
I really like nodeFn
cmd/tendermint/main.go
Outdated
| // * Use an external signer for their validators | ||
| // * Supply an in-proc abci app | ||
| // * Supply a genesis doc file from another source | ||
| // * Provide their own DB implementation |
There was a problem hiding this comment.
woops. tabs vs spaces. looks same in editor
types/priv_validator.go
Outdated
|
|
||
| // Sign | ||
| sig, err := privVal.Sign(signBytes) | ||
| sig, err := privVal.Signer.Sign(signBytes) |
There was a problem hiding this comment.
Signer is an anonymous embedded interface, so the following should work:
privVal.Sign(). Why not use that?
* abci: PrepareProposal (tendermint#6544) * regenerate mocks, proto, mod/sum, and clean up remaining preprocesstxs * fix tests and revert to old go.mod * mockery * add processproposal proto/boilerplate/logic * gofmt * fix test * move UNKNOWN response behaviour to reject * fix test and add testing util code * pass full block data when proposing or processing proposals * linter * add the process proposal method to the e2e app * add missing kvstore abci method * pass block data and results for bass app * use correct kvstore process logic for kvstore app * add new lazy share writers * linter * remove unused arg * sort messages before exporting * formatting and bug fix * fix tests * allow for picking of square size when computing shares for the data square * remove accidental code duplication * fix test from using wrong formatting directive * linter * ci: backport lint configuration changes (tendermint#7225) * lint: cleanup pending lint errors (tendermint#7237) * linter * remove unused arg * sort messages before exporting * formatting and bug fix * fix tests * allow for picking of square size when computing shares for the data square * fix test from using wrong formatting directive * linter * ci: backport lint configuration changes (tendermint#7225) * lint: cleanup pending lint errors (tendermint#7237) * add new lazy share writers * fix rebase * linter * try ci with go 1.17 * Revert "try ci with go 1.17" This reverts commit 0f76b4d444465cf8209bd98bb18f21b242207436. * please work, linter gods * spelling Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com> * initialize pending share using the const share size for capacity * force the last reserve bytes to be zero * remove todo * add typecheck back to golang linter * Revert "ci: backport lint configuration changes (tendermint#7225)" This reverts commit 35178048f0fef2d80a0346f28c7687be75c3db11. * Revert "lint: cleanup pending lint errors (tendermint#7237)" This reverts commit 5d806709a9dbfed4f7dda2facb49d46604584d36. * add the link to issue back in * removed unfinished comment * switch fuzzer back to one minute * regenerate proto * better docs * fix encoding check to include the hash added to Data * add docs to CotiguousShareWriter * fix encoding check * explain why the share reserve byte is zero * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use punctuation Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * use clearer wording for compute shares docs * more accurate docs Co-authored-by: Marko <marbar3778@yahoo.com> Co-authored-by: mconcat <monoidconcat@gmail.com> Co-authored-by: Sam Kleinman <garen@tychoish.com> Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com> Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>
…ermint#637) * Add metric * Fix error message * add to changelog * Add metric TxReceivedMoreThanOnce * observe only when a counter increases * merge both maps into one * Rename metric * Remove metric for priority-mempool, not used anymore * Update changelog * Overwrite prevoius value of wasObserved with false * Undo fix logging * Apply suggestions from code review Co-authored-by: Lasaro <lasaro@informal.systems> * Fix comment * Remove histogram and update changelog --------- Co-authored-by: Lasaro <lasaro@informal.systems>
Part of #647
This is the first step towards allowing HSM modules to sign votes.
The next step is to abstract the PrivValidator into an interface, so that an HSM can also keep track of the latest votes and rounds.