-
Notifications
You must be signed in to change notification settings - Fork 93
Feature/solution skvs #766
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
Feature/solution skvs #766
Conversation
05c5cd2
to
8fb8b84
Compare
// chaincode := fpc.NewPrivateChaincode(secretChaincode) | ||
skvsChaincode := fpc.NewSkvsChaincode(secretChaincode) | ||
|
||
// start chaincode as a service | ||
server := &shim.ChaincodeServer{ | ||
CCID: ccid, | ||
Address: addr, | ||
CC: chaincode, | ||
CC: skvsChaincode, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering if we want multiple main files? So we have an example to run secret keeper with and without skvs?
/samples/chaincode/secret-keeper-go/cmd/simple/main.go
/samples/chaincode/secret-keeper-go/cmd/skvs/main.go
A few words in the secret-keeper readme would be nice as well.
"github.com/pkg/errors" | ||
) | ||
|
||
type SkvsStubInterface struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name of this file should be all small caps
return &skvsStub{enclaveStub} | ||
} | ||
|
||
func (e *skvsStub) ChaincodeInvoke(stub shim.ChaincodeStubInterface, chaincodeRequestMessageBytes []byte) ([]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the feeling that we should remove this wrapper and instead just inject a provider function for the stub that we can set externally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no sure about this one, might need to ask more insight
dbe4851
to
754f02d
Compare
@chenchanglew can you please rebase in resolve the conflict within |
@munapower can you also please have look at this PR. Thanks! |
@mbrandenburger @chenchanglew not sure what I am supposed to test. @chenchanglew did you resolve the conflict Marcus mentions? What am I supposed to test? Should I follow the Readme or what? |
DEFAULT= cmd/naive/main.go | ||
SKVS_PATH = cmd/skvs/main.go | ||
|
||
MAIN_GO_PATH ?=$(DEFAULT) | ||
|
||
include $(TOP)/ecc_go/build.mk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a small section in the readme of this demo to explain how to build the chaincode with these different options.
For example:
MAIN_GO_PATH=cmd/naive/main.go make
or
MAIN_GO_PATH=cmd/skvs/main.go make
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is nothing else the user need to change in order to use skvs, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is just build for different solutions,
the real change need to do is here
secretChaincode, _ := contractapi.NewChaincode(&chaincode.SecretKeeper{})
// chaincode := fpc.NewPrivateChaincode(secretChaincode)
skvsChaincode := fpc.NewSkvsChaincode(secretChaincode)
ecc_go/chaincode/singleKVS.go
Outdated
func NewSkvsChaincode(cc shim.Chaincode) *chaincode.EnclaveChaincode { | ||
logger.Info("Creating new SKVS Chaincode") | ||
ecc := &chaincode.EnclaveChaincode{ | ||
Enclave: enclave_go.NewSkvsStub(cc), | ||
Validator: endorsement.NewValidator(), | ||
Extractor: &chaincode.ExtractorImpl{}, | ||
Ercc: &ercc.StubImpl{}, | ||
} | ||
return ecc | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if it would be better to add an option to the NewPrivateChaincode
method to instantiate the with SKVS rather than creating this new constructor.
It could look like ...
func NewPrivateChaincode(cc shim.Chaincode, options ...func(*chaincode.EnclaveChaincode)) *chaincode.EnclaveChaincode {
ecc := &chaincode.EnclaveChaincode{
Enclave: enclave_go.NewSkvsStub(cc),
Validator: endorsement.NewValidator(),
Extractor: &chaincode.ExtractorImpl{},
Ercc: &ercc.StubImpl{},
}
for _, o := range options {
o(ecc)
}
return ecc
}
func WithSKVS() func(*chaincode.EnclaveChaincode) {
return func(ecc *chaincode.EnclaveChaincode) {
ecc.Enclave = enclave_go.NewSkvsStub(cc)
}
}
and in the chaincode main.go
, the developer would write something like that ...
// naive
chaincode := fpc.NewPrivateChaincode(secretChaincode)
// with SKVS
chaincode := fpc.NewPrivateChaincode(secretChaincode, fpc.WithSKVS)
See this article https://golang.cafe/blog/golang-functional-options-pattern.html
WDYT?
754f02d
to
dca2e20
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Zac for the refactoring. I've found a few things we should still fix. Please have a look.
ecc_go/chaincode/private.go
Outdated
ecc := &chaincode.EnclaveChaincode{ | ||
Enclave: enclave_go.NewEnclaveStub(cc), | ||
Enclave: enclave_go.NewSkvsStub(cc), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe here we want to keep the "normal" NewEnclaveStub
@@ -62,22 +62,18 @@ func (s *SkvsStubInterface) InitSKVS() error { | |||
} | |||
} | |||
|
|||
logger.Warningf("SKVS Init finish, allDataOld: %s, allDataNew: %s", s.allDataOld, s.allDataNew) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest that we remove these loggers as they may print sensitive data.
) | ||
|
||
func NewSkvsStub(cc shim.Chaincode) *EnclaveStub { | ||
logger.Warning("==== SKVS NewSkvsStub ====") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.Warning("==== SKVS NewSkvsStub ====") |
} | ||
|
||
func NewSkvsStubInterface(stub shim.ChaincodeStubInterface, input *pb.ChaincodeInput, rwset *readWriteSet, sep StateEncryptionFunctions) *SkvsStubInterface { | ||
logger.Warning("==== Get New Skvs Interface =====") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.Warning("==== Get New Skvs Interface =====") |
skvsStub := SkvsStubInterface{fpcStub, map[string][]byte{}, map[string][]byte{}, "SKVS"} | ||
err := skvsStub.InitSKVS() | ||
if err != nil { | ||
logger.Warningf("Error!! Initializing SKVS failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have an error ... shouldn't we better through a panic? We cannot really go on and continue, right?
skvsStub := SkvsStubInterface{fpcStub, map[string][]byte{}, map[string][]byte{}, "SKVS"} | ||
err := skvsStub.InitSKVS() | ||
if err != nil { | ||
logger.Warningf("Error!! Initializing SKVS failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.Warningf("Error!! Initializing SKVS failed") | |
panic("Initializing SKVS failed") |
} | ||
|
||
func (s *SkvsStubInterface) InitSKVS() error { | ||
logger.Warningf(" === Initializing SKVS === ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.Warningf(" === Initializing SKVS === ") |
if len(encValue) == 0 { | ||
logger.Warningf("SKVS is empty, Initiating.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we also handle this error differently? Can we actually continue if encValue
is empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we can continue if the encValue is empty.
func (s *SkvsStubInterface) InitSKVS() error { | ||
logger.Warningf(" === Initializing SKVS === ") | ||
|
||
// get current state, this will only operate once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it makes sense to protect the entire Init method with sync.Once
?
https://pkg.go.dev/sync#Once
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function is safe even it runs multiple time, since the
encValue, err := s.GetPublicState(s.key)
will always fetch the latest value, if someone try to do something malicious by calling this init function they will just get the latest public state.
Plus I already change the init function into a private function thus I think it should be okay without the sync.Once.
if err != nil { | ||
return err | ||
} | ||
logger.Warningf("SKVS has default value, loading current value.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.Warningf("SKVS has default value, loading current value.") | |
logger.Debug("SKVS has default value, loading current value.") |
f695c07
to
07e6fa9
Compare
allDataOld: map[string][]byte{}, | ||
allDataNew: map[string][]byte{}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allDataOld: map[string][]byte{}, | |
allDataNew: map[string][]byte{}, | |
allDataOld: make(map[string][]byte), | |
allDataNew: make(map[string][]byte), |
} | ||
err := skvsStub.initSKVS() | ||
if err != nil { | ||
panic("Initializing SKVS failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
panic("Initializing SKVS failed") | |
panic(fmt.Sprintf("Initializing SKVS failed, err: %v", err)) |
func (s *SkvsStubInterface) GetState(key string) ([]byte, error) { | ||
value, found := s.allDataOld[key] | ||
if !found { | ||
return nil, errors.New("skvs allDataOld key not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, shouldn't we return nil, nil
in that case?
// If the key does not exist in the state database, (nil, nil) is returned.
GetState(key string) ([]byte, error)
https://github.com/hyperledger/fabric-chaincode-go/blob/main/shim/interfaces.go#L80C1-L81C38
|
||
"github.com/hyperledger/fabric-chaincode-go/shim" | ||
pb "github.com/hyperledger/fabric-protos-go/peer" | ||
"github.com/pkg/errors" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"github.com/pkg/errors" |
func (s *SkvsStubInterface) GetState(key string) ([]byte, error) { | ||
value, found := s.allDataOld[key] | ||
if !found { | ||
return nil, errors.New("skvs allDataOld key not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, errors.New("skvs allDataOld key not found") | |
return nil, nil |
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
Signed-off-by: chenchanglew <[email protected]>
07e6fa9
to
f6c2ef5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! LGTM
What this PR does / why we need it:
Implement a Rollback attack protection solution for FPC: SKVS.
Single Key-Value Storage (SKVS) is a naive approach for rollback attacks. All key-value pairs are encapsulated and stored in this approach with a single call to put_state(). During execution, the enclave must load the entire state before accessing individual key-value pairs. While this approach prevents the rollback attack, applications with large states and multiple writers will experience bad performance, as the use of a single key-value pair will cause transactions to fail due to concurrent write issues.
A user can use it by changing the chain code to SVKS chaincode
ex:
skvsChaincode := fpc.NewPrivateChaincode(secretChaincode, fpc.WithSKVS())
for example we can test with skvs/main.go in secret keeper
make ECC_MAIN_FILES=cmd/skvs/main.go with_go env docker
Which issue(s) this PR fixes:
Fixes #484
Special notes for your reviewer:
Loom demonstration video: Watch here