Skip to content

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

Merged
merged 13 commits into from
May 29, 2025

Conversation

chenchanglew
Copy link
Contributor

@chenchanglew chenchanglew commented Jun 11, 2024

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

@chenchanglew chenchanglew requested a review from a team as a code owner June 11, 2024 12:56
@chenchanglew chenchanglew force-pushed the feature/solution-skvs branch from 05c5cd2 to 8fb8b84 Compare June 11, 2024 13:04
Comment on lines 26 to 33
// chaincode := fpc.NewPrivateChaincode(secretChaincode)
skvsChaincode := fpc.NewSkvsChaincode(secretChaincode)

// start chaincode as a service
server := &shim.ChaincodeServer{
CCID: ccid,
Address: addr,
CC: chaincode,
CC: skvsChaincode,
Copy link
Contributor

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 {
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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

@chenchanglew chenchanglew force-pushed the feature/solution-skvs branch 2 times, most recently from dbe4851 to 754f02d Compare November 26, 2024 18:08
@mbrandenburger
Copy link
Contributor

@chenchanglew can you please rebase in resolve the conflict within ecc_go/build.mk

@mbrandenburger
Copy link
Contributor

@munapower can you also please have look at this PR. Thanks!

@munapower
Copy link
Contributor

@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?

Comment on lines 11 to 16
DEFAULT= cmd/naive/main.go
SKVS_PATH = cmd/skvs/main.go

MAIN_GO_PATH ?=$(DEFAULT)

include $(TOP)/ecc_go/build.mk
Copy link
Contributor

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

Copy link
Contributor

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?

Copy link
Contributor Author

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)

Comment on lines 20 to 29
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
}
Copy link
Contributor

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?

Copy link
Contributor

@mbrandenburger mbrandenburger left a 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 := &chaincode.EnclaveChaincode{
Enclave: enclave_go.NewEnclaveStub(cc),
Enclave: enclave_go.NewSkvsStub(cc),
Copy link
Contributor

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)
Copy link
Contributor

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 ====")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.Warning("==== SKVS NewSkvsStub ====")

}

func NewSkvsStubInterface(stub shim.ChaincodeStubInterface, input *pb.ChaincodeInput, rwset *readWriteSet, sep StateEncryptionFunctions) *SkvsStubInterface {
logger.Warning("==== Get New Skvs Interface =====")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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")
Copy link
Contributor

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.Warningf("Error!! Initializing SKVS failed")
panic("Initializing SKVS failed")

}

func (s *SkvsStubInterface) InitSKVS() error {
logger.Warningf(" === Initializing SKVS === ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.Warningf(" === Initializing SKVS === ")

Comment on lines +44 to +51
if len(encValue) == 0 {
logger.Warningf("SKVS is empty, Initiating.")
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor Author

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.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.Warningf("SKVS has default value, loading current value.")
logger.Debug("SKVS has default value, loading current value.")

@chenchanglew chenchanglew force-pushed the feature/solution-skvs branch from f695c07 to 07e6fa9 Compare May 14, 2025 22:58
Comment on lines 30 to 31
allDataOld: map[string][]byte{},
allDataNew: map[string][]byte{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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")
Copy link
Contributor

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, errors.New("skvs allDataOld key not found")
return nil, nil

chenchanglew and others added 8 commits May 27, 2025 16:35
@chenchanglew chenchanglew force-pushed the feature/solution-skvs branch from 07e6fa9 to f6c2ef5 Compare May 27, 2025 14:36
Copy link
Contributor

@mbrandenburger mbrandenburger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM

@mbrandenburger mbrandenburger merged commit db4ff06 into hyperledger:main May 29, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rollback protection Extension (aka Trusted Ledger)
3 participants