diff --git a/.changelog/5715.feature.md b/.changelog/5715.feature.md new file mode 100644 index 00000000000..89ff7e776f2 --- /dev/null +++ b/.changelog/5715.feature.md @@ -0,0 +1 @@ +keymanager/src/churp: Fetch key shares and recover key diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs.go b/go/consensus/cometbft/apps/keymanager/churp/txs.go index 17ecc86cc29..9c9368c9873 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs.go @@ -13,12 +13,18 @@ import ( kmCommon "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/common" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" + "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/features" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" "github.com/oasisprotocol/oasis-core/go/registry/api" staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error { + // Make sure the `MayQuery` field is empty until the next breaking upgrade. + if err := verifyPolicy(ctx, &req.Policy); err != nil { + return err + } + // Prepare state. state := churpState.NewMutableState(ctx.State()) @@ -125,6 +131,11 @@ func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error } func (ext *churpExt) update(ctx *tmapi.Context, req *churp.UpdateRequest) error { + // Make sure the `MayQuery` field is empty until the next breaking upgrade. + if err := verifyPolicy(ctx, req.Policy); err != nil { + return err + } + // Prepare state. state := churpState.NewMutableState(ctx.State()) @@ -545,3 +556,18 @@ func resetHandoff(status *churp.Status, nextHandoff beacon.EpochTime) { status.NextChecksum = nil status.Applications = nil } + +func verifyPolicy(ctx *tmapi.Context, policy *churp.SignedPolicySGX) error { + // Allow non-empty `MayQuery` field with the 24.2 release. + enabled, err := features.IsFeatureVersion(ctx, "24.2") + if err != nil { + return err + } + if enabled { + return nil + } + if policy != nil && policy.Policy.MayQuery != nil { + return api.ErrInvalidArgument + } + return nil +} diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs_test.go b/go/consensus/cometbft/apps/keymanager/churp/txs_test.go index f144ac533df..d667f2bcb29 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs_test.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs_test.go @@ -16,10 +16,12 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/entity" "github.com/oasisprotocol/oasis-core/go/common/node" "github.com/oasisprotocol/oasis-core/go/common/quantity" + consensusState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/abci/state" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" + consensusGenesis "github.com/oasisprotocol/oasis-core/go/consensus/genesis" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" registry "github.com/oasisprotocol/oasis-core/go/registry/api" staking "github.com/oasisprotocol/oasis-core/go/staking/api" @@ -56,6 +58,7 @@ type TxTestSuite struct { state *churpState.MutableState regState *registryState.MutableState stakeState *stakingState.MutableState + consState *consensusState.MutableState nodes []*testNode computeRuntimes []*registry.Runtime @@ -79,6 +82,7 @@ func (s *TxTestSuite) SetupTest() { s.state = churpState.NewMutableState(s.ctx.State()) s.regState = registryState.NewMutableState(s.ctx.State()) s.stakeState = stakingState.NewMutableState(s.ctx.State()) + s.consState = consensusState.NewMutableState(s.ctx.State()) // Set up default consensus parameters. err := s.state.SetConsensusParameters(s.ctx, &churp.DefaultConsensusParameters) @@ -87,6 +91,8 @@ func (s *TxTestSuite) SetupTest() { require.NoError(s.T(), err) err = s.stakeState.SetConsensusParameters(s.ctx, &staking.ConsensusParameters{}) require.NoError(s.T(), err) + err = s.consState.SetConsensusParameters(s.ctx, &consensusGenesis.Parameters{}) + require.NoError(s.T(), err) // Prepare nodes. s.nodes = make([]*testNode, 0, numNodes) diff --git a/go/consensus/cometbft/features/features.go b/go/consensus/cometbft/features/features.go index c04f4cc44dd..4108ce99b36 100644 --- a/go/consensus/cometbft/features/features.go +++ b/go/consensus/cometbft/features/features.go @@ -1,4 +1,4 @@ -package api +package features import ( "fmt" diff --git a/go/keymanager/churp/policy.go b/go/keymanager/churp/policy.go index cf01d90e69a..4a783b3dd6d 100644 --- a/go/keymanager/churp/policy.go +++ b/go/keymanager/churp/policy.go @@ -3,6 +3,7 @@ package churp import ( "fmt" + "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/sgx" @@ -12,7 +13,8 @@ import ( var PolicySGXSignatureContext = signature.NewContext("oasis-core/keymanager/churp: policy") // PolicySGX represents an SGX access control policy used to authenticate -// key manager enclaves during handoffs. +// key manager enclaves during handoffs and remote client enclaves when +// querying key shares. type PolicySGX struct { Identity @@ -20,12 +22,16 @@ type PolicySGX struct { Serial uint32 `json:"serial"` // MayShare is the vector of enclave identities from which a share can be - // obtained during handouts. + // obtained during handoffs. MayShare []sgx.EnclaveIdentity `json:"may_share"` // MayJoin is the vector of enclave identities that may form the new // committee in the next handoffs. MayJoin []sgx.EnclaveIdentity `json:"may_join"` + + // MayQuery is the map of runtime identities to the vector of enclave + // identities that may query key shares. + MayQuery map[common.Namespace][]sgx.EnclaveIdentity `json:"may_query,omitempty"` } // SanityCheck verifies the validity of the policy. diff --git a/go/keymanager/churp/rpc.go b/go/keymanager/churp/rpc.go index 75a9ca883c6..7c3143099cf 100644 --- a/go/keymanager/churp/rpc.go +++ b/go/keymanager/churp/rpc.go @@ -35,6 +35,9 @@ var ( // RPCMethodBivariateShare is the name of the `bivariate_share` method. RPCMethodBivariateShare = "churp/bivariate_share" + + // RPCMethodSGXPolicyKeyShare is the name of the `sgx_policy_key_share` method. + RPCMethodSGXPolicyKeyShare = "churp/sgx_policy_key_share" ) // HandoffRequest represents a handoff request. diff --git a/go/keymanager/secrets/policy_sgx.go b/go/keymanager/secrets/policy_sgx.go index 508ca272c35..188b11c229f 100644 --- a/go/keymanager/secrets/policy_sgx.go +++ b/go/keymanager/secrets/policy_sgx.go @@ -43,8 +43,10 @@ type EnclavePolicySGX struct { MayQuery map[common.Namespace][]sgx.EnclaveIdentity `json:"may_query"` // MayReplicate is the vector of enclave IDs that may retrieve the master - // secret (Note: Each enclave ID may always implicitly replicate from other - // instances of itself). + // secret. + // + // NOTE: Each enclave ID may always implicitly replicate from other + // instances of itself. MayReplicate []sgx.EnclaveIdentity `json:"may_replicate"` } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/archive_api.go b/go/oasis-test-runner/scenario/e2e/runtime/archive_api.go index 0e6b472f959..48f5f305bf0 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/archive_api.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/archive_api.go @@ -22,7 +22,7 @@ import ( var ArchiveAPI scenario.Scenario = &archiveAPI{ Scenario: *NewScenario( "archive-api", - NewTestClient().WithScenario(InsertTransferKeyValueScenario), + NewTestClient().WithScenario(InsertTransferScenario), ), } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/dump_restore.go b/go/oasis-test-runner/scenario/e2e/runtime/dump_restore.go index f324b6b8e23..2211c31df70 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/dump_restore.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/dump_restore.go @@ -54,7 +54,7 @@ func newDumpRestoreImpl( sc := &dumpRestoreImpl{ Scenario: *NewScenario( name, - NewTestClient().WithScenario(InsertKeyValueScenario), + NewTestClient().WithScenario(InsertScenario), ), mapGenesisDocumentFn: mapGenesisDocumentFn, } @@ -188,6 +188,6 @@ func (sc *dumpRestoreImpl) Run(ctx context.Context, childEnv *env.Env) error { } // Check that everything works with restored state. - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveKeyValueScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveScenario) return sc.Scenario.Run(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/gas_fees.go b/go/oasis-test-runner/scenario/e2e/runtime/gas_fees.go index cd0cbfa8c3e..64b9f53fc0a 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/gas_fees.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/gas_fees.go @@ -124,7 +124,7 @@ func (sc *gasFeesRuntimesImpl) Run(ctx context.Context, _ *env.Env) error { // Submit a runtime transaction to check whether transaction processing works. sc.Logger.Info("submitting transaction to runtime") - if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, 0, "hello", "non-free world", false, 0); err != nil { + if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, 0, "hello", "non-free world", 0, 0, plaintextTxKind); err != nil { return err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go b/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go index 1158fd6aaf8..a921bf8ec83 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go @@ -61,7 +61,7 @@ func newGovernanceConsensusUpgradeImpl(correctUpgradeVersion, cancelUpgrade bool sc := &governanceConsensusUpgradeImpl{ Scenario: *NewScenario( name, - NewTestClient().WithScenario(InsertTransferKeyValueScenario), + NewTestClient().WithScenario(InsertTransferScenario), ), correctUpgradeVersion: correctUpgradeVersion, shouldCancelUpgrade: cancelUpgrade, @@ -465,6 +465,6 @@ func (sc *governanceConsensusUpgradeImpl) Run(ctx context.Context, childEnv *env } // Check that runtime still works after the upgrade. - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveKeyValueScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveScenario) return sc.Scenario.Run(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/halt_restore.go b/go/oasis-test-runner/scenario/e2e/runtime/halt_restore.go index 556450f5ce0..1e2211e5c74 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/halt_restore.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/halt_restore.go @@ -41,7 +41,7 @@ func newHaltRestoreImpl(suspended bool) scenario.Scenario { return &haltRestoreImpl{ Scenario: *NewScenario( name, - NewTestClient().WithScenario(InsertTransferKeyValueScenario), + NewTestClient().WithScenario(InsertTransferScenario), ), haltEpoch: beacon.EpochTime(haltEpoch), suspendRuntime: suspended, @@ -220,7 +220,7 @@ func (sc *haltRestoreImpl) Run(ctx context.Context, childEnv *env.Env) error { / return err } - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveKeyValueScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveScenario) // Start the new network again and run the test client. if err = sc.StartNetworkAndWaitForClientSync(ctx); err != nil { diff --git a/go/oasis-test-runner/scenario/e2e/runtime/halt_restore_nonmock.go b/go/oasis-test-runner/scenario/e2e/runtime/halt_restore_nonmock.go index c5f355c4bff..e3688e4cee0 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/halt_restore_nonmock.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/halt_restore_nonmock.go @@ -25,7 +25,7 @@ func newHaltRestoreNonMockImpl() scenario.Scenario { return &haltRestoreNonMockImpl{ Scenario: *NewScenario( name, - NewTestClient().WithScenario(InsertTransferKeyValueScenario), + NewTestClient().WithScenario(InsertTransferScenario), ), haltEpoch: 8, } @@ -133,7 +133,7 @@ func (sc *haltRestoreNonMockImpl) Run(ctx context.Context, childEnv *env.Env) er return err } - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveKeyValueScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveScenario) // Start the new network again and run the test client. if err = sc.StartNetworkAndTestClient(ctx, childEnv); err != nil { diff --git a/go/oasis-test-runner/scenario/e2e/runtime/helpers_churp.go b/go/oasis-test-runner/scenario/e2e/runtime/helpers_churp.go index f179e538fd4..cabf25d96e9 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/helpers_churp.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/helpers_churp.go @@ -5,6 +5,7 @@ import ( "fmt" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/sgx" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" "github.com/oasisprotocol/oasis-core/go/keymanager/api" @@ -35,6 +36,10 @@ func (sc *Scenario) createChurp(ctx context.Context, id uint8, threshold uint8, req.Policy.Policy.MayShare = []sgx.EnclaveIdentity{*enclaveID} } + if enclaveID := sc.Net.Runtimes()[1].GetEnclaveIdentity(0); enclaveID != nil { + req.Policy.Policy.MayQuery = map[common.Namespace][]sgx.EnclaveIdentity{KeyValueRuntimeID: {*enclaveID}} + } + if err := req.Policy.Sign(api.TestSigners); err != nil { return err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/history_reindex.go b/go/oasis-test-runner/scenario/e2e/runtime/history_reindex.go index e9eb84a815f..817b28da7d9 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/history_reindex.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/history_reindex.go @@ -29,7 +29,7 @@ func newHistoryReindexImpl() scenario.Scenario { return &historyReindexImpl{ Scenario: *NewScenario( "history-reindex", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp_txs.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp_txs.go new file mode 100644 index 00000000000..f40738c5fe6 --- /dev/null +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp_txs.go @@ -0,0 +1,211 @@ +package runtime + +import ( + "context" + "fmt" + "time" + + beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/churp" + "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" + "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" + "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario" +) + +var ( + insertEncWithChurpScenario = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, encryptedWithChurpTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithChurpTxKind}, + RemoveKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithChurpTxKind}, + GetKeyValueTx{"my_key", "", 0, 0, encryptedWithChurpTxKind}, + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, encryptedWithChurpTxKind}, + }) + + getEncWithChurpScenario = NewTestClientScenario([]interface{}{ + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithChurpTxKind}, + }) +) + +// KeymanagerChurpTxs is the key manager CHURP scenario exercising encrypted transactions. +var KeymanagerChurpTxs scenario.Scenario = newKmChurpTxsImpl() + +type kmChurpTxsImpl struct { + Scenario +} + +func newKmChurpTxsImpl() scenario.Scenario { + return &kmChurpTxsImpl{ + Scenario: *NewScenario( + "keymanager-churp-txs", + NewTestClient(), + ), + } +} + +func (sc *kmChurpTxsImpl) Clone() scenario.Scenario { + return &kmChurpTxsImpl{ + Scenario: *sc.Scenario.Clone().(*Scenario), + } +} + +func (sc *kmChurpTxsImpl) Fixture() (*oasis.NetworkFixture, error) { + f, err := sc.Scenario.Fixture() + if err != nil { + return nil, err + } + + // We need 4 key managers to test all handoff kinds. + f.Keymanagers[0].ChurpIDs = []uint8{0} + for i := 0; i < 3; i++ { + f.Keymanagers = append(f.Keymanagers, f.Keymanagers[0]) + } + + // Enable CHURP extension. + f.Network.EnableKeyManagerCHURP = true + + // Speed up the test. + f.Network.Beacon.VRFParameters = &beacon.VRFParameters{ + Interval: 10, + ProofSubmissionDelay: 2, + } + + return f, nil +} + +func (sc *kmChurpTxsImpl) Run(ctx context.Context, childEnv *env.Env) error { + if err := sc.Net.Start(); err != nil { + return err + } + + if err := sc.Net.ClientController().WaitReady(ctx); err != nil { + return err + } + + stCh, stSub, err := sc.Net.ClientController().Keymanager.Churp().WatchStatuses(ctx) + if err != nil { + return err + } + defer stSub.Close() + + // Create scheme. The handoff interval is set to 4 so that a newly started + // key manager node has enough time to register. This could be lowered once + // we refactor key manager node tracker on P2P layer. + id := uint8(0) + threshold := uint8(1) + handoffInterval := beacon.EpochTime(4) + nonce := uint64(0) + + if err = sc.createChurp(ctx, id, threshold, handoffInterval, nonce); err != nil { + return err + } + + // 1. Dealing phase + sc.Logger.Info("waiting handoff to complete (dealing phase)") + + firstStatus, err := sc.waitNextHandoff(ctx, 0, 4, stCh) + if err != nil { + return err + } + + sc.Logger.Info("testing handoff (dealing phase)") + + sc.TestClient.scenario = insertEncWithChurpScenario + if err = sc.RunTestClientAndCheckLogs(ctx, childEnv); err != nil { + return err + } + if err = sc.clearComputeNodeCache(ctx); err != nil { + return err + } + + // 2. Committee unchanged. + sc.Logger.Info("waiting handoff to complete (committee unchanged)") + + secondStatus, err := sc.waitNextHandoff(ctx, firstStatus.Handoff, 4, stCh) + if err != nil { + return err + } + + sc.Logger.Info("testing handoff (committee unchanged)") + + sc.TestClient.scenario = getEncWithChurpScenario + if err = sc.RunTestClientAndCheckLogs(ctx, childEnv); err != nil { + return err + } + if err = sc.clearComputeNodeCache(ctx); err != nil { + return err + } + + // Stop one key manager so that the committee changes. + if err = sc.Net.Keymanagers()[0].Stop(); err != nil { + return err + } + + // 3. Committee changed (key manager removed). + sc.Logger.Info("waiting handoff to complete (committee changed)") + + thirdStatus, err := sc.waitNextHandoff(ctx, secondStatus.Handoff, 3, stCh) + if err != nil { + return err + } + + sc.Logger.Info("testing handoff (committee changed)") + + if err = sc.RunTestClientAndCheckLogs(ctx, childEnv); err != nil { + return err + } + if err = sc.clearComputeNodeCache(ctx); err != nil { + return err + } + + // Start another key manager so that the committee changes. + if err = sc.Net.Keymanagers()[0].Start(); err != nil { + return err + } + if err = sc.Net.Keymanagers()[0].WaitReady(ctx); err != nil { + return err + } + + // 4. Committee changed (key manager added). + sc.Logger.Info("waiting handoff to complete (committee changed)") + + _, err = sc.waitNextHandoff(ctx, thirdStatus.Handoff, 4, stCh) + if err != nil { + return err + } + + sc.Logger.Info("testing handoff (committee changed)") + + if err = sc.RunTestClientAndCheckLogs(ctx, childEnv); err != nil { + return err + } + + return nil +} + +func (sc *kmChurpTxsImpl) clearComputeNodeCache(ctx context.Context) error { + for _, w := range sc.Net.ComputeWorkers() { + if err := w.Restart(ctx); err != nil { + return err + } + } + return nil +} + +func (sc *kmChurpTxsImpl) waitNextHandoff(ctx context.Context, lastHandoff beacon.EpochTime, committeeSize int, stCh <-chan *churp.Status) (*churp.Status, error) { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + for { + status, err := sc.nextChurpStatus(ctx, stCh) + if err != nil { + return nil, err + } + if status.Handoff <= lastHandoff { + continue + } + if n := len(status.Committee); n != committeeSize { + return nil, fmt.Errorf("committee should have %d and not %d members", committeeSize, n) + } + return status, nil + } +} diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_dump_restore.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_dump_restore.go index 68e9a495a77..f7a0376d9aa 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_dump_restore.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_dump_restore.go @@ -27,7 +27,7 @@ func newKmDumpRestoreImpl() scenario.Scenario { return &kmDumpRestoreImpl{ Scenario: *NewScenario( "keymanager-dump-restore", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go index 1041a09201d..e8ef98694bd 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go @@ -43,7 +43,7 @@ func newKmEphemeralSecretsImpl() scenario.Scenario { return &kmEphemeralSecretsImpl{ Scenario: *NewScenario( "keymanager-ephemeral-secrets", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go index 4097b40fa22..812d40befb8 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go @@ -26,7 +26,7 @@ func newKmMasterSecretsImpl() scenario.Scenario { return &kmMasterSecretsImpl{ Scenario: *NewScenario( "keymanager-master-secrets", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go index f1e4342570e..e0dbf981961 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go @@ -24,7 +24,7 @@ func newKmReplicateImpl() scenario.Scenario { return &kmReplicateImpl{ Scenario: *NewScenario( "keymanager-replication", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_restart.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_restart.go index f069e1850d0..37cd17077e1 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_restart.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_restart.go @@ -21,7 +21,7 @@ func newKmRestartImpl() scenario.Scenario { return &kmRestartImpl{ Scenario: *NewScenario( "keymanager-restart", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } @@ -88,6 +88,6 @@ func (sc *kmRestartImpl) Run(ctx context.Context, childEnv *env.Env) error { // Run the second client on a different key so that it will require // a second trip to the keymanager. sc.Logger.Info("starting a second client to check if key manager works") - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveKeyValueEncScenarioV2) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveEncWithSecretsScenarioV2) return sc.RunTestClientAndCheckLogs(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go index 3a56e08933a..cb84bae83f4 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go @@ -12,7 +12,6 @@ import ( "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario" - "github.com/oasisprotocol/oasis-core/go/registry/api" registry "github.com/oasisprotocol/oasis-core/go/registry/api" staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) @@ -180,7 +179,7 @@ func (sc *kmRotationFailureImpl) extendKeymanagerRegistrations(ctx context.Conte km := sc.Net.Keymanagers()[idx] // Update expiration. - nodeDesc, err := sc.Net.ClientController().Registry.GetNode(ctx, &api.IDQuery{ + nodeDesc, err := sc.Net.ClientController().Registry.GetNode(ctx, ®istry.IDQuery{ Height: consensus.HeightLatest, ID: km.NodeID, }) diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_upgrade.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_upgrade.go index 80dc7e51ef2..21da4cd4321 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_upgrade.go @@ -25,7 +25,7 @@ func NewKmUpgradeImpl() scenario.Scenario { return &KmUpgradeImpl{ Scenario: *NewScenario( "keymanager-upgrade", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } @@ -70,6 +70,6 @@ func (sc *KmUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error { // Run client again. sc.Logger.Info("starting a second client to check if key manager works") - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveKeyValueEncScenarioV2) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveEncWithSecretsScenarioV2) return sc.RunTestClientAndCheckLogs(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/late_start.go b/go/oasis-test-runner/scenario/e2e/runtime/late_start.go index 6f7d2ece43f..02f64817b24 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/late_start.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/late_start.go @@ -26,7 +26,7 @@ func newLateStartImpl(name string) scenario.Scenario { return &lateStartImpl{ Scenario: *NewScenario( name, - NewTestClient().WithScenario(SimpleKeyValueScenario), + NewTestClient().WithScenario(SimpleScenario), ), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/multiple_runtimes.go b/go/oasis-test-runner/scenario/e2e/runtime/multiple_runtimes.go index d6a1523de63..512f35522cb 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/multiple_runtimes.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/multiple_runtimes.go @@ -163,7 +163,7 @@ func (sc *multipleRuntimesImpl) Run(ctx context.Context, _ *env.Env) error { "runtime_id", rt.ID, ) - if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, rt.ID, uint64(i), "hello", fmt.Sprintf("world at iteration %d from %s", i, rt.ID), false, 0); err != nil { + if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, rt.ID, uint64(i), "hello", fmt.Sprintf("world at iteration %d from %s", i, rt.ID), 0, 0, plaintextTxKind); err != nil { return err } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/node_shutdown.go b/go/oasis-test-runner/scenario/e2e/runtime/node_shutdown.go index 78b9b5fff59..fb3d632d2a6 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/node_shutdown.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/node_shutdown.go @@ -28,7 +28,7 @@ func newNodeShutdownImpl() scenario.Scenario { sc := &nodeShutdownImpl{ Scenario: *NewScenario( "node-shutdown", - NewTestClient().WithScenario(SimpleKeyValueScenario), + NewTestClient().WithScenario(SimpleScenario), ), } return sc diff --git a/go/oasis-test-runner/scenario/e2e/runtime/offset_restart.go b/go/oasis-test-runner/scenario/e2e/runtime/offset_restart.go index 43ebf28a8b5..3c00fe6e74d 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/offset_restart.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/offset_restart.go @@ -19,7 +19,7 @@ func newOffsetRestartImpl() scenario.Scenario { sc := &offsetRestartImpl{ Scenario: *NewScenario( "offset-restart", - NewTestClient().WithScenario(InsertTransferKeyValueScenario), + NewTestClient().WithScenario(InsertTransferScenario), ), } return sc @@ -89,6 +89,6 @@ func (sc *offsetRestartImpl) Run(ctx context.Context, childEnv *env.Env) error { // if these disconnected after the client node had already seen them, thereby // hanging the network (no transactions could be submitted). sc.Logger.Info("network back up, trying to run client again") - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveKeyValueScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(RemoveScenario) return sc.Scenario.Run(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/rofl.go b/go/oasis-test-runner/scenario/e2e/runtime/rofl.go index 137b79e84f9..730384d888a 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/rofl.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/rofl.go @@ -16,12 +16,12 @@ type roflImpl struct { func newROFL() scenario.Scenario { return &roflImpl{ Scenario: *NewScenario("rofl", NewTestClient().WithScenario(NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key", "my_value", "", true, 0}, - GetKeyValueTx{"my_key", "my_value", true, 0}, - RemoveKeyValueTx{"my_key", "my_value", true, 0}, - GetKeyValueTx{"my_key", "", true, 0}, + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, + RemoveKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key", "", 0, 0, encryptedWithSecretsTxKind}, // Check that the ROFL component wrote the HTTP response into storage. - KeyExistsTx{"rofl_http", false, 0}, + KeyExistsTx{"rofl_http", 0, 0, plaintextTxKind}, }))), } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go b/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go index 5270ff519d2..f74b48f1781 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go @@ -214,7 +214,7 @@ func (sc *runtimeDynamicImpl) Run(ctx context.Context, childEnv *env.Env) error sc.Logger.Info("submitting transaction to runtime", "seq", i, ) - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", fmt.Sprintf("world %d", i), false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", fmt.Sprintf("world %d", i), 0, 0, plaintextTxKind); err != nil { return err } rtNonce++ @@ -322,7 +322,7 @@ func (sc *runtimeDynamicImpl) Run(ctx context.Context, childEnv *env.Env) error // Submit a runtime transaction to check whether the runtimes got resumed. sc.Logger.Info("submitting transaction to runtime") - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", "final world", false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", "final world", 0, 0, plaintextTxKind); err != nil { return err } rtNonce++ @@ -500,7 +500,7 @@ func (sc *runtimeDynamicImpl) Run(ctx context.Context, childEnv *env.Env) error // Submit a runtime transaction to check whether the runtimes got resumed. sc.Logger.Info("submitting transaction to runtime") - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", "final world for sure", false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, rtNonce, "hello", "final world for sure", 0, 0, plaintextTxKind); err != nil { return err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/runtime_prune.go b/go/oasis-test-runner/scenario/e2e/runtime/runtime_prune.go index 40ac96c8f88..c2e70efa2b7 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/runtime_prune.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/runtime_prune.go @@ -81,7 +81,7 @@ func (sc *runtimePruneImpl) Run(ctx context.Context, _ *env.Env) error { "seq", i, ) - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, uint64(i), "hello", fmt.Sprintf("world %d", i), false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, uint64(i), "hello", fmt.Sprintf("world %d", i), 0, 0, plaintextTxKind); err != nil { return err } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/runtime_upgrade.go b/go/oasis-test-runner/scenario/e2e/runtime/runtime_upgrade.go index 487ac617e15..d5ae536f094 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/runtime_upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/runtime_upgrade.go @@ -25,7 +25,7 @@ func newRuntimeUpgradeImpl() scenario.Scenario { return &runtimeUpgradeImpl{ Scenario: *NewScenario( "runtime-upgrade", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } @@ -67,6 +67,6 @@ func (sc *runtimeUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error // Run client again. sc.Logger.Info("starting a second client to check if runtime works") - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveKeyValueEncScenarioV2) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveEncWithSecretsScenarioV2) return sc.RunTestClientAndCheckLogs(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/scenario.go b/go/oasis-test-runner/scenario/e2e/runtime/scenario.go index e6f62e4af17..aec8f87264a 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/scenario.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/scenario.go @@ -39,13 +39,13 @@ var ( // Runtime is the basic network + client test case with runtime support. Runtime scenario.Scenario = NewScenario( "runtime", - NewTestClient().WithScenario(SimpleKeyValueScenario), + NewTestClient().WithScenario(SimpleScenario), ) // RuntimeEncryption is the basic network + client with encryption test case. RuntimeEncryption scenario.Scenario = NewScenario( "runtime-encryption", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ) // DefaultRuntimeLogWatcherHandlerFactories is a list of default log watcher @@ -339,6 +339,7 @@ func RegisterScenarios() error { KeymanagerUpgrade, KeymanagerChurp, KeymanagerChurpMany, + KeymanagerChurpTxs, // Dump/restore test. DumpRestore, DumpRestoreRuntimeRoundAdvance, diff --git a/go/oasis-test-runner/scenario/e2e/runtime/sentry.go b/go/oasis-test-runner/scenario/e2e/runtime/sentry.go index 02a2e84a87b..8f2d8b5ce70 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/sentry.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/sentry.go @@ -43,7 +43,7 @@ func newSentryImpl() scenario.Scenario { return &sentryImpl{ Scenario: *NewScenario( "sentry", - NewTestClient().WithScenario(SimpleKeyValueScenario), + NewTestClient().WithScenario(SimpleScenario), ), } } @@ -186,9 +186,6 @@ func (s *sentryImpl) Run(ctx context.Context, childEnv *env.Env) error { // noli } validator2 := s.Net.Validators()[2] - if err != nil { - return fmt.Errorf("sentry: error loading validator node identity: %w", err) - } validator2Ctrl, err := oasis.NewController(validator2.SocketPath()) if err != nil { return err diff --git a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync.go b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync.go index 78e817a7386..50b0476d83f 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync.go @@ -26,7 +26,7 @@ func newStorageSyncImpl() scenario.Scenario { return &storageSyncImpl{ Scenario: *NewScenario( "storage-sync", - NewTestClient().WithScenario(SimpleKeyValueScenario), + NewTestClient().WithScenario(SimpleScenario), ), } } @@ -102,7 +102,7 @@ func (sc *storageSyncImpl) Run(ctx context.Context, childEnv *env.Env) error { / sc.Logger.Info("submitting transaction to runtime", "seq", i, ) - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, drbg.Uint64(), "checkpoint", fmt.Sprintf("my cp %d", i), false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, drbg.Uint64(), "checkpoint", fmt.Sprintf("my cp %d", i), 0, 0, plaintextTxKind); err != nil { return err } } @@ -179,7 +179,7 @@ func (sc *storageSyncImpl) Run(ctx context.Context, childEnv *env.Env) error { / sc.Logger.Info("submitting large transaction to runtime", "seq", i, ) - if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, drbg.Uint64(), fmt.Sprintf("%d key %d", i, i), fmt.Sprintf("my cp %d: ", i)+largeVal, false, 0); err != nil { + if _, err = sc.submitKeyValueRuntimeInsertTx(ctx, KeyValueRuntimeID, drbg.Uint64(), fmt.Sprintf("%d key %d", i, i), fmt.Sprintf("my cp %d: ", i)+largeVal, 0, 0, plaintextTxKind); err != nil { return err } } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_from_registered.go b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_from_registered.go index 6c9f69380aa..2931900fba7 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_from_registered.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_from_registered.go @@ -26,7 +26,7 @@ func newStorageSyncFromRegisteredImpl() scenario.Scenario { return &storageSyncFromRegisteredImpl{ Scenario: *NewScenario( "storage-sync-registered", - NewTestClient().WithScenario(InsertRemoveKeyValueEncScenario), + NewTestClient().WithScenario(InsertRemoveEncWithSecretsScenario), ), } } @@ -182,6 +182,6 @@ func (sc *storageSyncFromRegisteredImpl) Run(ctx context.Context, childEnv *env. // Run the client again. sc.Logger.Info("starting a second client to check if runtime works with compute worker 1") - sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveKeyValueEncScenarioV2) + sc.Scenario.TestClient = NewTestClient().WithSeed("seed2").WithScenario(InsertRemoveEncWithSecretsScenarioV2) return sc.RunTestClientAndCheckLogs(ctx, childEnv) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_inconsistent.go b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_inconsistent.go index d63d0e71d12..50e50a2a0c0 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_inconsistent.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/storage_sync_inconsistent.go @@ -29,7 +29,7 @@ func newStorageSyncInconsistentImpl() scenario.Scenario { sc := &storageSyncInconsistentImpl{ Scenario: *NewScenario( "storage-sync-inconsistent", - NewTestClient().WithScenario(SimpleKeyValueScenarioRepeated), + NewTestClient().WithScenario(SimpleRepeatedScenario), ), } sc.Scenario.debugNoRandomInitialEpoch = true // I give up. diff --git a/go/oasis-test-runner/scenario/e2e/runtime/test_client.go b/go/oasis-test-runner/scenario/e2e/runtime/test_client.go index 0338b59cc14..5b4dab26a0d 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/test_client.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/test_client.go @@ -17,6 +17,47 @@ import ( staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) +// GetCall represents a call to get a key-value pair. +type GetCall struct { + Key string `json:"key"` + Generation uint64 `json:"generation,omitempty"` + ChurpID uint8 `json:"churp_id,omitempty"` +} + +// RemoveCall represents a call to remove a key-value pair. +type RemoveCall struct { + Key string `json:"key"` + Generation uint64 `json:"generation,omitempty"` + ChurpID uint8 `json:"churp_id,omitempty"` +} + +// InsertCall represents a call to insert a key-value pair. +type InsertCall struct { + Key string `json:"key"` + Value string `json:"value"` + Generation uint64 `json:"generation,omitempty"` + ChurpID uint8 `json:"churp_id,omitempty"` +} + +// EncryptCall represents a call to encrypt a plaintext. +type EncryptCall struct { + Epoch beacon.EpochTime `json:"epoch"` + KeyPairID string `json:"key_pair_id"` + Plaintext []byte `json:"plaintext"` +} + +// DecryptCall represents a call to decrypt a ciphertext. +type DecryptCall struct { + Epoch beacon.EpochTime `json:"epoch"` + KeyPairID string `json:"key_pair_id"` + Ciphertext []byte `json:"ciphertext"` +} + +// TransferCall represents a call to transfer tokens. +type TransferCall struct { + Transfer staking.Transfer `json:"transfer"` +} + // TestClient is a client that exercises a pre-determined workload against // the simple key-value runtime. type TestClient struct { @@ -193,8 +234,9 @@ func (cli *TestClient) submit(ctx context.Context, req interface{}, rng rand.Sou rng.Uint64(), req.Key, req.Value, - req.Encrypted, req.Generation, + req.ChurpID, + req.Kind, ) if err != nil { return fmt.Errorf("failed to insert k/v pair: %w", err) @@ -209,8 +251,9 @@ func (cli *TestClient) submit(ctx context.Context, req interface{}, rng rand.Sou KeyValueRuntimeID, rng.Uint64(), req.Key, - req.Encrypted, req.Generation, + req.ChurpID, + req.Kind, ) if err != nil { return err @@ -225,8 +268,9 @@ func (cli *TestClient) submit(ctx context.Context, req interface{}, rng rand.Sou KeyValueRuntimeID, rng.Uint64(), req.Key, - req.Encrypted, req.Generation, + req.ChurpID, + req.Kind, ) if err != nil { return err @@ -241,8 +285,9 @@ func (cli *TestClient) submit(ctx context.Context, req interface{}, rng rand.Sou KeyValueRuntimeID, rng.Uint64(), req.Key, - req.Encrypted, req.Generation, + req.ChurpID, + req.Kind, ) if err != nil { return err @@ -258,8 +303,9 @@ func (cli *TestClient) submit(ctx context.Context, req interface{}, rng rand.Sou rng.Uint64(), req.Key, req.Value, - req.Encrypted, req.Generation, + req.ChurpID, + req.Kind, ) if err != nil { return err @@ -359,11 +405,7 @@ func (sc *Scenario) submitKeyValueRuntimeEncryptTx( "plaintext", plaintext, ) - args := struct { - Epoch beacon.EpochTime `json:"epoch"` - KeyPairID string `json:"key_pair_id"` - Plaintext []byte `json:"plaintext"` - }{ + args := EncryptCall{ Epoch: epoch, KeyPairID: keyPairID, Plaintext: plaintext, @@ -386,11 +428,7 @@ func (sc *Scenario) submitKeyValueRuntimeDecryptTx( "ciphertext", ciphertext, ) - args := struct { - Epoch beacon.EpochTime `json:"epoch"` - KeyPairID string `json:"key_pair_id"` - Ciphertext []byte `json:"ciphertext"` - }{ + args := DecryptCall{ Epoch: epoch, KeyPairID: keyPairID, Ciphertext: ciphertext, @@ -404,30 +442,36 @@ func (sc *Scenario) submitKeyValueRuntimeInsertTx( id common.Namespace, nonce uint64, key, value string, - encrypted bool, generation uint64, + churpID uint8, + kind uint, ) (string, error) { sc.Logger.Info("inserting k/v pair", "key", key, "value", value, - "encrypted", encrypted, "generation", generation, + "churp_id", churpID, + "kind", kind, ) - args := struct { - Key string `json:"key"` - Value string `json:"value"` - Generation uint64 `json:"generation,omitempty"` - }{ + var method string + switch kind { + case plaintextTxKind: + method = "insert" + case encryptedWithSecretsTxKind: + method = "enc_insert" + case encryptedWithChurpTxKind: + method = "churp_insert" + } + + args := InsertCall{ Key: key, Value: value, Generation: generation, + ChurpID: churpID, } - if encrypted { - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "enc_insert", args) - } - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "insert", args) + return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, method, args) } func (sc *Scenario) submitKeyValueRuntimeGetTx( @@ -435,27 +479,34 @@ func (sc *Scenario) submitKeyValueRuntimeGetTx( id common.Namespace, nonce uint64, key string, - encrypted bool, generation uint64, + churpID uint8, + kind uint, ) (string, error) { sc.Logger.Info("retrieving k/v pair", "key", key, - "encrypted", encrypted, "generation", generation, + "churp_id", churpID, + "kind", kind, ) - args := struct { - Key string `json:"key"` - Generation uint64 `json:"generation,omitempty"` - }{ + var method string + switch kind { + case plaintextTxKind: + method = "get" + case encryptedWithSecretsTxKind: + method = "enc_get" + case encryptedWithChurpTxKind: + method = "churp_get" + } + + args := GetCall{ Key: key, Generation: generation, + ChurpID: churpID, } - if encrypted { - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "enc_get", args) - } - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "get", args) + return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, method, args) } func (sc *Scenario) submitKeyValueRuntimeRemoveTx( @@ -463,27 +514,34 @@ func (sc *Scenario) submitKeyValueRuntimeRemoveTx( id common.Namespace, nonce uint64, key string, - encrypted bool, generation uint64, + churpID uint8, + kind uint, ) (string, error) { sc.Logger.Info("removing k/v pair", "key", key, - "encrypted", encrypted, "generation", generation, + "churp_id", churpID, + "kind", kind, ) - args := struct { - Key string `json:"key"` - Generation uint64 `json:"generation,omitempty"` - }{ + var method string + switch kind { + case plaintextTxKind: + method = "remove" + case encryptedWithSecretsTxKind: + method = "enc_remove" + case encryptedWithChurpTxKind: + method = "churp_remove" + } + + args := RemoveCall{ Key: key, Generation: generation, + ChurpID: churpID, } - if encrypted { - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "enc_remove", args) - } - return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, "remove", args) + return sc.submitRuntimeTxAndDecodeString(ctx, id, nonce, method, args) } func (sc *Scenario) submitKeyValueRuntimeGetRuntimeIDTx( @@ -506,30 +564,36 @@ func (sc *Scenario) submitKeyValueRuntimeInsertMsg( id common.Namespace, nonce uint64, key, value string, - encrypted bool, generation uint64, + churpID uint8, + kind uint, ) error { sc.Logger.Info("submitting incoming runtime message", "key", key, "value", value, - "encrypted", encrypted, "generation", generation, + "churp_id", churpID, + "kind", kind, ) - args := struct { - Key string `json:"key"` - Value string `json:"value"` - Generation uint64 `json:"generation,omitempty"` - }{ + var method string + switch kind { + case plaintextTxKind: + method = "insert" + case encryptedWithSecretsTxKind: + method = "enc_insert" + case encryptedWithChurpTxKind: + method = "churp_insert" + } + + args := InsertCall{ Key: key, Value: value, Generation: generation, + ChurpID: churpID, } - if encrypted { - return sc.submitRuntimeInMsg(ctx, id, nonce, "enc_insert", args) - } - return sc.submitRuntimeInMsg(ctx, id, nonce, "insert", args) + return sc.submitRuntimeInMsg(ctx, id, nonce, method, args) } func (sc *Scenario) submitAndDecodeRuntimeQuery( @@ -563,9 +627,7 @@ func (sc *Scenario) submitKeyValueRuntimeGetQuery( "round", round, ) - args := struct { - Key string `json:"key"` - }{ + args := GetCall{ Key: key, } @@ -582,9 +644,7 @@ func (sc *Scenario) submitConsensusTransferTx( "transfer", transfer, ) - _, err := sc.submitRuntimeTx(ctx, id, nonce, "consensus_transfer", struct { - Transfer staking.Transfer `json:"transfer"` - }{ + _, err := sc.submitRuntimeTx(ctx, id, nonce, "consensus_transfer", TransferCall{ Transfer: transfer, }) if err != nil { diff --git a/go/oasis-test-runner/scenario/e2e/runtime/test_client_scenario.go b/go/oasis-test-runner/scenario/e2e/runtime/test_client_scenario.go index 3ddc02530d3..6fb91cc97a4 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/test_client_scenario.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/test_client_scenario.go @@ -2,57 +2,53 @@ package runtime import ( "fmt" - - beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" ) var ( - InsertKeyValueScenario = NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key", "my_value", "", false, 0}, - GetKeyValueTx{"my_key", "my_value", false, 0}, + InsertScenario = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, plaintextTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, plaintextTxKind}, }) - InsertKeyValueEncScenario = NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key", "my_value", "", true, 0}, - GetKeyValueTx{"my_key", "my_value", true, 0}, + InsertEncWithSecretsScenario = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, }) - RemoveKeyValueScenario = NewTestClientScenario([]interface{}{ - GetKeyValueTx{"my_key", "my_value", false, 0}, + RemoveScenario = NewTestClientScenario([]interface{}{ + GetKeyValueTx{"my_key", "my_value", 0, 0, plaintextTxKind}, }) - RemoveKeyValueEncScenario = NewTestClientScenario([]interface{}{ - GetKeyValueTx{"my_key", "my_value", true, 0}, + RemoveEncWithSecretsScenario = NewTestClientScenario([]interface{}{ + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, }) - InsertTransferKeyValueScenario = NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key", "my_value", "", false, 0}, - GetKeyValueTx{"my_key", "my_value", false, 0}, + InsertTransferScenario = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, plaintextTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, plaintextTxKind}, ConsensusTransferTx{}, }) - InsertRemoveKeyValueEncScenario = NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key", "my_value", "", true, 0}, - GetKeyValueTx{"my_key", "my_value", true, 0}, - RemoveKeyValueTx{"my_key", "my_value", true, 0}, - GetKeyValueTx{"my_key", "", true, 0}, + InsertRemoveEncWithSecretsScenario = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key", "my_value", "", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, + RemoveKeyValueTx{"my_key", "my_value", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key", "", 0, 0, encryptedWithSecretsTxKind}, }) - InsertRemoveKeyValueEncScenarioV2 = NewTestClientScenario([]interface{}{ - InsertKeyValueTx{"my_key2", "my_value2", "", true, 0}, - GetKeyValueTx{"my_key2", "my_value2", true, 0}, - RemoveKeyValueTx{"my_key2", "my_value2", true, 0}, - GetKeyValueTx{"my_key2", "", true, 0}, + InsertRemoveEncWithSecretsScenarioV2 = NewTestClientScenario([]interface{}{ + InsertKeyValueTx{"my_key2", "my_value2", "", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key2", "my_value2", 0, 0, encryptedWithSecretsTxKind}, + RemoveKeyValueTx{"my_key2", "my_value2", 0, 0, encryptedWithSecretsTxKind}, + GetKeyValueTx{"my_key2", "", 0, 0, encryptedWithSecretsTxKind}, }) - SimpleKeyValueScenario = newSimpleKeyValueScenario(false, false) - - SimpleKeyValueEncScenario = newSimpleKeyValueScenario(false, true) - - SimpleKeyValueScenarioRepeated = newSimpleKeyValueScenario(true, false) + SimpleScenario = newSimpleKeyValueScenario(false, plaintextTxKind) + SimpleRepeatedScenario = newSimpleKeyValueScenario(true, plaintextTxKind) + SimpleEncWithSecretsScenario = newSimpleKeyValueScenario(false, encryptedWithSecretsTxKind) ) -func newSimpleKeyValueScenario(repeat bool, encrypted bool) TestClientScenario { +func newSimpleKeyValueScenario(repeat bool, kind uint) TestClientScenario { return func(submit func(req interface{}) error) error { // Check whether Runtime ID is also set remotely. // @@ -73,10 +69,10 @@ func newSimpleKeyValueScenario(repeat bool, encrypted bool) TestClientScenario { response = fmt.Sprintf("hello_value_from_%s:%d", KeyValueRuntimeID, iter-1) } - if err := submit(InsertKeyValueTx{key, value, response, encrypted, 0}); err != nil { + if err := submit(InsertKeyValueTx{key, value, response, 0, 0, kind}); err != nil { return err } - if err := submit(GetKeyValueTx{key, value, encrypted, 0}); err != nil { + if err := submit(GetKeyValueTx{key, value, 0, 0, kind}); err != nil { return err } @@ -88,13 +84,13 @@ func newSimpleKeyValueScenario(repeat bool, encrypted bool) TestClientScenario { response = value } - if err := submit(InsertKeyValueTx{key, value, response, encrypted, 0}); err != nil { + if err := submit(InsertKeyValueTx{key, value, response, 0, 0, kind}); err != nil { return err } if err := submit(ConsensusTransferTx{}); err != nil { return err } - if err := submit(GetKeyValueTx{key, value, encrypted, 0}); err != nil { + if err := submit(GetKeyValueTx{key, value, 0, 0, kind}); err != nil { return err } @@ -108,84 +104,16 @@ func newSimpleKeyValueScenario(repeat bool, encrypted bool) TestClientScenario { inMsgKey = "in_msg" inMsgValue = "hello world from inmsg" ) - if err := submit(InsertMsg{inMsgKey, inMsgValue, encrypted, 0}); err != nil { + if err := submit(InsertMsg{inMsgKey, inMsgValue, 0, 0, kind}); err != nil { return err } - if err := submit(GetKeyValueTx{inMsgKey, inMsgValue, encrypted, 0}); err != nil { + if err := submit(GetKeyValueTx{inMsgKey, inMsgValue, 0, 0, kind}); err != nil { return err } return submit(ConsensusAccountsTx{}) } } -// KeyValueQuery queries the value stored under the given key for the specified round from -// the database, and verifies that the response (current value) contains the expected data. -type KeyValueQuery struct { - Key string - Response string - Round uint64 -} - -// EncryptDecryptTx encrypts and decrypts a message while verifying if the original message -// matches the decrypted result. -type EncryptDecryptTx struct { - Message []byte - KeyPairID string - Epoch beacon.EpochTime -} - -// InsertKeyValueTx inserts a key/value pair to the database, and verifies that the response -// (previous value) contains the expected data. -type InsertKeyValueTx struct { - Key string - Value string - Response string - Encrypted bool - Generation uint64 -} - -// GetKeyValueTx retrieves the value stored under the given key from the database, -// and verifies that the response (current value) contains the expected data. -type GetKeyValueTx struct { - Key string - Response string - Encrypted bool - Generation uint64 -} - -// KeyExistsTx retrieves the value stored under the given key from the database and verifies that -// the response (current value) is non-empty. -type KeyExistsTx struct { - Key string - Encrypted bool - Generation uint64 -} - -// RemoveKeyValueTx removes the value stored under the given key from the database. -type RemoveKeyValueTx struct { - Key string - Response string - Encrypted bool - Generation uint64 -} - -// InsertMsg inserts an incoming runtime message. -type InsertMsg struct { - Key string - Value string - Encrypted bool - Generation uint64 -} - -// GetRuntimeIDTx retrieves the runtime ID. -type GetRuntimeIDTx struct{} - -// ConsensusTransferTx submits and empty consensus staking transfer. -type ConsensusTransferTx struct{} - -// ConsensusAccountsTx tests consensus account query. -type ConsensusAccountsTx struct{} - // TestClientScenario is a test scenario for a key-value runtime test client. type TestClientScenario func(submit func(req interface{}) error) error diff --git a/go/oasis-test-runner/scenario/e2e/runtime/test_client_txs.go b/go/oasis-test-runner/scenario/e2e/runtime/test_client_txs.go new file mode 100644 index 00000000000..6a757a55d0d --- /dev/null +++ b/go/oasis-test-runner/scenario/e2e/runtime/test_client_txs.go @@ -0,0 +1,90 @@ +package runtime + +import ( + beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" +) + +const ( + // plaintextTxKind refers to a key/value transaction where the data + // is unencrypted. + plaintextTxKind uint = iota + // encryptedWithSecretsTxKind refers to a key/value transaction where + // the data is encrypted using a state key derived from master secrets. + encryptedWithSecretsTxKind + // encryptedWithChurpTxKind refers to a key/value transaction where + // the data is encrypted using a state key derived from CHURP. + encryptedWithChurpTxKind +) + +// KeyValueQuery queries the value stored under the given key for the specified round from +// the database, and verifies that the response (current value) contains the expected data. +type KeyValueQuery struct { + Key string + Response string + Round uint64 +} + +// EncryptDecryptTx encrypts and decrypts a message while verifying if the original message +// matches the decrypted result. +type EncryptDecryptTx struct { + Message []byte + KeyPairID string + Epoch beacon.EpochTime +} + +// InsertKeyValueTx inserts a key/value pair to the database, and verifies that the response +// (previous value) contains the expected data. +type InsertKeyValueTx struct { + Key string + Value string + Response string + Generation uint64 + ChurpID uint8 + Kind uint +} + +// GetKeyValueTx retrieves the value stored under the given key from the database, +// and verifies that the response (current value) contains the expected data. +type GetKeyValueTx struct { + Key string + Response string + Generation uint64 + ChurpID uint8 + Kind uint +} + +// KeyExistsTx retrieves the value stored under the given key from the database and verifies that +// the response (current value) is non-empty. +type KeyExistsTx struct { + Key string + Generation uint64 + ChurpID uint8 + Kind uint +} + +// RemoveKeyValueTx removes the value stored under the given key from the database. +type RemoveKeyValueTx struct { + Key string + Response string + Generation uint64 + ChurpID uint8 + Kind uint +} + +// InsertMsg inserts an incoming runtime message. +type InsertMsg struct { + Key string + Value string + Generation uint64 + ChurpID uint8 + Kind uint +} + +// GetRuntimeIDTx retrieves the runtime ID. +type GetRuntimeIDTx struct{} + +// ConsensusTransferTx submits and empty consensus staking transfer. +type ConsensusTransferTx struct{} + +// ConsensusAccountsTx tests consensus account query. +type ConsensusAccountsTx struct{} diff --git a/go/oasis-test-runner/scenario/e2e/runtime/trust_root.go b/go/oasis-test-runner/scenario/e2e/runtime/trust_root.go index ad742ea1cfd..05c56298fbd 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/trust_root.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/trust_root.go @@ -16,7 +16,7 @@ import ( // TrustRoot is the consensus trust root verification scenario. var TrustRoot scenario.Scenario = NewTrustRootImpl( "simple", - NewTestClient().WithScenario(SimpleKeyValueEncScenario), + NewTestClient().WithScenario(SimpleEncWithSecretsScenario), ) type TrustRootImpl struct { @@ -190,7 +190,7 @@ func (sc *TrustRootImpl) Run(ctx context.Context, childEnv *env.Env) (err error) // Use non-encrypted transactions, as queries don't support decryption. queries = append(queries, - InsertKeyValueTx{key, value, "", false, 0}, + InsertKeyValueTx{key, value, "", 0, 0, plaintextTxKind}, KeyValueQuery{key, value, roothash.RoundLatest}, ) } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/trust_root_change.go b/go/oasis-test-runner/scenario/e2e/runtime/trust_root_change.go index 11fa2b0bbab..f798995e0bd 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/trust_root_change.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/trust_root_change.go @@ -37,7 +37,7 @@ var ( // changes, e.g. on dump-restore network upgrades. TrustRootChangeTest scenario.Scenario = newTrustRootChangeImpl( "change", - NewTestClient().WithScenario(InsertKeyValueEncScenario), + NewTestClient().WithScenario(InsertEncWithSecretsScenario), true, ) @@ -46,7 +46,7 @@ var ( // consensus chain context changes. TrustRootChangeFailsTest scenario.Scenario = newTrustRootChangeImpl( "change-fails", - NewTestClient().WithScenario(SimpleKeyValueEncScenario), + NewTestClient().WithScenario(SimpleEncWithSecretsScenario), false, ) ) @@ -344,6 +344,6 @@ func (sc *trustRootChangeImpl) dumpRestoreNetwork(childEnv *env.Env, f func(*oas func (sc *trustRootChangeImpl) startRestoredStateTestClient(ctx context.Context, childEnv *env.Env, round int64) error { // Check that everything works with restored state. seed := fmt.Sprintf("seed %d", round) - sc.Scenario.TestClient = NewTestClient().WithSeed(seed).WithScenario(RemoveKeyValueEncScenario) + sc.Scenario.TestClient = NewTestClient().WithSeed(seed).WithScenario(RemoveEncWithSecretsScenario) return sc.Scenario.Run(ctx, childEnv) } diff --git a/go/worker/keymanager/churp.go b/go/worker/keymanager/churp.go index 369c13d57c4..1462343b644 100644 --- a/go/worker/keymanager/churp.go +++ b/go/worker/keymanager/churp.go @@ -17,6 +17,7 @@ import ( cmnBackoff "github.com/oasisprotocol/oasis-core/go/common/backoff" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-core/go/common/node" "github.com/oasisprotocol/oasis-core/go/config" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" @@ -51,6 +52,7 @@ var ( churp.RPCMethodShareReductionPoint: {}, churp.RPCMethodShareDistributionPoint: {}, churp.RPCMethodBivariateShare: {}, + churp.RPCMethodSGXPolicyKeyShare: {}, } ) @@ -65,7 +67,7 @@ type churpWorker struct { kmWorker *Worker - mu sync.Mutex + mu sync.RWMutex churps map[uint8]*churp.Status // Guarded by mutex. watcher *nodeWatcher @@ -104,8 +106,11 @@ func (w *churpWorker) Methods() []string { } // Connect implements RPCAccessController interface. -func (w *churpWorker) Connect(_ context.Context, peerID core.PeerID) (b bool) { +func (w *churpWorker) Connect(ctx context.Context, peerID core.PeerID) (b bool) { // Secure methods are accessible to peers that pass authorization. + if err := w.authorizeNode(ctx, peerID); err == nil { + return true + } if err := w.authorizeKeyManager(peerID); err == nil { return true } @@ -114,7 +119,7 @@ func (w *churpWorker) Connect(_ context.Context, peerID core.PeerID) (b bool) { } // Authorize implements RPCAccessController interface. -func (w *churpWorker) Authorize(_ context.Context, method string, kind enclaverpc.Kind, peerID core.PeerID) (err error) { +func (w *churpWorker) Authorize(ctx context.Context, method string, kind enclaverpc.Kind, peerID core.PeerID) (err error) { // Check if the method is supported. switch kind { case enclaverpc.KindInsecureQuery: @@ -130,7 +135,53 @@ func (w *churpWorker) Authorize(_ context.Context, method string, kind enclaverp } // All peers must undergo the authorization process. - return w.authorizeKeyManager(peerID) + switch method { + case churp.RPCMethodSGXPolicyKeyShare: + return w.authorizeNode(ctx, peerID) + case churp.RPCMethodVerificationMatrix, + churp.RPCMethodShareReductionPoint, + churp.RPCMethodShareDistributionPoint, + churp.RPCMethodBivariateShare: + return w.authorizeKeyManager(peerID) + default: + return fmt.Errorf("unsupported method: %s", method) + } +} + +func (w *churpWorker) authorizeNode(ctx context.Context, peerID core.PeerID) error { + rt, err := w.kmWorker.runtime.RegistryDescriptor(ctx) + if err != nil { + return err + } + + switch rt.TEEHardware { + case node.TEEHardwareInvalid: + // Insecure key manager enclaves can be queried by all runtimes (used for testing). + return nil + case node.TEEHardwareIntelSGX: + // Secure key manager enclaves can be queried by runtimes specified in the policy. + w.mu.RLock() + statuses := maps.Values(w.churps) + w.mu.RUnlock() + + // Retrieve the list of runtimes that the peer participates in. + rts := w.kmWorker.accessList.Runtimes(peerID) + + // Grant access if the peer participates in any allowed runtime. + for _, status := range statuses { + if status == nil { + continue + } + for rt := range status.Policy.Policy.MayQuery { + if rts.Contains(rt) { + return nil + } + } + } + return fmt.Errorf("query not allowed") + default: + return fmt.Errorf("unsupported hardware: %s", rt.TEEHardware) + } } func (w *churpWorker) authorizeKeyManager(peerID core.PeerID) error { @@ -155,8 +206,8 @@ func (w *churpWorker) Initialized() <-chan struct{} { // GetStatus returns the worker status. func (w *churpWorker) GetStatus() workerKm.ChurpStatus { - w.mu.Lock() - defer w.mu.Unlock() + w.mu.RLock() + defer w.mu.RUnlock() status := workerKm.ChurpStatus{ Schemes: make(map[uint8]workerKm.ChurpSchemeStatus), @@ -234,14 +285,16 @@ func (w *churpWorker) handleNewBlock(blk *consensus.Block) { // handleStatusUpdate is responsible for handling status update. func (w *churpWorker) handleStatusUpdate(status *churp.Status) { - w.mu.Lock() - defer w.mu.Unlock() - // Skip schemes we are not involved in. if status.RuntimeID != w.kmWorker.runtimeID { return } - if _, ok := w.churps[status.ID]; !ok { + + w.mu.RLock() + _, ok := w.churps[status.ID] + w.mu.RUnlock() + + if !ok { return } @@ -250,7 +303,9 @@ func (w *churpWorker) handleStatusUpdate(status *churp.Status) { ) // Update status. + w.mu.Lock() w.churps[status.ID] = status + w.mu.Unlock() // Notify all workers about the new status. w.watcher.Update(status) diff --git a/keymanager/src/api/errors.rs b/keymanager/src/api/errors.rs index fad6395e143..faffaa24c14 100644 --- a/keymanager/src/api/errors.rs +++ b/keymanager/src/api/errors.rs @@ -31,6 +31,8 @@ pub enum KeyManagerError { PolicyChanged, #[error("policy has invalid runtime")] PolicyInvalidRuntime, + #[error("insufficient key shares")] + InsufficientKeyShares, #[error("insufficient signatures")] InsufficientSignatures, #[error("runtime signing key missing")] diff --git a/keymanager/src/churp/handler.rs b/keymanager/src/churp/handler.rs index a611def8831..0cbad7d4f09 100644 --- a/keymanager/src/churp/handler.rs +++ b/keymanager/src/churp/handler.rs @@ -12,10 +12,6 @@ use group::{Group, GroupEncoding}; use rand::rngs::OsRng; use sp800_185::KMac; -#[cfg(target_env = "sgx")] -use oasis_core_runtime::{ - common::sgx::EnclaveIdentity, consensus::keymanager::churp::SignedPolicySGX, -}; use oasis_core_runtime::{ common::{ crypto::{ @@ -23,10 +19,11 @@ use oasis_core_runtime::{ signature::{PublicKey, Signer}, }, namespace::Namespace, + sgx::EnclaveIdentity, }, consensus::{ beacon::EpochTime, - keymanager::churp::{Status, SuiteId}, + keymanager::churp::{SignedPolicySGX, Status, SuiteId}, verifier::Verifier, }, enclave_rpc::Context as RpcContext, @@ -37,7 +34,8 @@ use oasis_core_runtime::{ }; use secret_sharing::{ - churp::{Dealer, Handoff, HandoffKind, Shareholder, ShareholderId, VerifiableSecretShare}, + churp::{encode_shareholder, Dealer, Handoff, HandoffKind, Shareholder, VerifiableSecretShare}, + kdc::KeySharer, suites::{p384, Suite}, vss::{ matrix::VerificationMatrix, @@ -52,9 +50,10 @@ use crate::{ }; use super::{ - storage::Storage, ApplicationRequest, ConfirmationRequest, EncodedVerifiableSecretShare, Error, - FetchRequest, FetchResponse, HandoffRequest, QueryRequest, SignedApplicationRequest, - SignedConfirmationRequest, State as ChurpState, VerifiedPolicies, + storage::Storage, ApplicationRequest, ConfirmationRequest, EncodedEncryptedPoint, + EncodedVerifiableSecretShare, Error, FetchRequest, FetchResponse, HandoffRequest, + KeyShareRequest, QueryRequest, SignedApplicationRequest, SignedConfirmationRequest, + State as ChurpState, VerifiedPolicies, }; /// A handoff interval that disables handoffs. @@ -72,6 +71,35 @@ const CONFIRMATION_REQUEST_SIGNATURE_CONTEXT: &[u8] = const CHECKSUM_VERIFICATION_MATRIX_CUSTOM: &[u8] = b"oasis-core/keymanager/churp: verification matrix"; +/// Domain separation tag for encoding shareholder identifiers. +const ENCODE_SHAREHOLDER_CONTEXT: &[u8] = b"oasis-core/keymanager/churp: encode shareholder"; + +/// Domain separation tag for encoding key identifiers for key share derivation +/// approved by an SGX policy. +/// +/// SGX policies specify which enclave identities are authorized to access +/// runtime key shares. +const ENCODE_SGX_POLICY_KEY_ID_CONTEXT: &[u8] = + b"oasis-core/keymanager/churp: encode SGX policy key ID"; + +/// Domain separation tag for encoding key identifiers for key share derivation +/// approved by a custom policy. +/// +/// Custom policies allow access to key shares only for clients that submit +/// a proof, which can be validated against the policy. The hash of the policy +/// is part of the key identifier and is integral to the key derivation process. +#[allow(dead_code)] +const ENCODE_CUSTOM_POLICY_KEY_ID_CONTEXT: &[u8] = + b"oasis-core/keymanager/churp: encode custom policy key ID"; + +/// The runtime separator used to add additional domain separation based +/// on the runtime ID. +const RUNTIME_CONTEXT_SEPARATOR: &[u8] = b" for runtime "; + +/// The churp separator used to add additional domain separation based +/// on the churp ID. +const CHURP_CONTEXT_SEPARATOR: &[u8] = b" for churp "; + /// Data associated with a handoff. struct HandoffData { /// The epoch of the handoff. @@ -105,7 +133,6 @@ pub struct Churp { /// Verified churp state. churp_state: ChurpState, /// Verified registry state. - #[cfg_attr(not(target_env = "sgx"), allow(unused))] registry_state: RegistryState, /// Shareholders with secret shares for the last successfully completed @@ -117,7 +144,6 @@ pub struct Churp { handoffs: Mutex>, /// Cached verified policies. - #[cfg_attr(not(target_env = "sgx"), allow(unused))] policies: VerifiedPolicies, } @@ -222,7 +248,7 @@ impl Churp { /// needs to be kept secret and generated only for authorized nodes. pub fn share_reduction_switch_point( &self, - _ctx: &RpcContext, + ctx: &RpcContext, req: &QueryRequest, ) -> Result> { let status = self.verify_next_handoff(req.id, req.runtime_id, req.epoch)?; @@ -236,11 +262,9 @@ impl Churp { if !status.applications.contains_key(node_id) { return Err(Error::NotInCommittee.into()); } - #[cfg(target_env = "sgx")] - { - self.verify_node_id(_ctx, node_id)?; - self.verify_enclave(_ctx, &status.policy)?; - } + + self.verify_node_id(ctx, node_id)?; + self.verify_km_enclave(ctx, &status.policy)?; match status.suite_id { SuiteId::NistP384Sha3_384 => { @@ -257,7 +281,8 @@ impl Churp { where S: Suite, { - let x = ShareholderId(node_id.0).encode::()?; + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; let shareholder = self.get_shareholder::(status.id, status.handoff)?; let point = shareholder.switch_point(&x); let point = scalar_to_bytes(&point); @@ -301,11 +326,9 @@ impl Churp { if !status.applications.contains_key(node_id) { return Err(Error::NotInCommittee.into()); } - #[cfg(target_env = "sgx")] - { - self.verify_node_id(_ctx, node_id)?; - self.verify_enclave(_ctx, &status.policy)?; - } + + self.verify_node_id(_ctx, node_id)?; + self.verify_km_enclave(_ctx, &status.policy)?; match status.suite_id { SuiteId::NistP384Sha3_384 => { @@ -322,7 +345,8 @@ impl Churp { where S: Suite + 'static, { - let x = ShareholderId(node_id.0).encode::()?; + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; let handoff = self.get_handoff::(status.id, status.next_handoff)?; let shareholder = handoff.get_reduced_shareholder()?; let point = shareholder.switch_point(&x); @@ -364,11 +388,9 @@ impl Churp { if !status.applications.contains_key(node_id) { return Err(Error::NotInCommittee.into()); } - #[cfg(target_env = "sgx")] - { - self.verify_node_id(_ctx, node_id)?; - self.verify_enclave(_ctx, &status.policy)?; - } + + self.verify_node_id(_ctx, node_id)?; + self.verify_km_enclave(_ctx, &status.policy)?; match status.suite_id { SuiteId::NistP384Sha3_384 => { @@ -385,7 +407,8 @@ impl Churp { where S: Suite, { - let x = ShareholderId(node_id.0).encode::()?; + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; let kind = Self::handoff_kind(status); let dealer = self.get_dealer::(status.id, status.next_handoff)?; let share = dealer.make_share(x, kind); @@ -398,6 +421,42 @@ impl Churp { }) } + /// Returns the key share for the given key ID generated by the key + /// derivation center. + /// + /// Key share: + /// ```text + /// KS_i = s_i * H(key_id) + /// ``` + /// + /// WARNING: This method must be called over a secure channel as the key + /// share needs to be kept secret and generated only for authorized nodes. + pub fn sgx_policy_key_share( + &self, + ctx: &RpcContext, + req: &KeyShareRequest, + ) -> Result { + let status = self.verify_last_handoff(req.id, req.runtime_id, req.epoch)?; + + self.verify_rt_enclave(ctx, &status.policy, &req.key_runtime_id)?; + + match status.suite_id { + SuiteId::NistP384Sha3_384 => { + self.make_key_share::(&req.key_id.0, &status) + } + } + } + + fn make_key_share(&self, key_id: &[u8], status: &Status) -> Result + where + S: Suite, + { + let shareholder = self.get_shareholder::(status.id, status.handoff)?; + let dst = self.domain_separation_tag(ENCODE_SGX_POLICY_KEY_ID_CONTEXT, status.id); + let point = shareholder.make_key_share::(key_id, &dst)?; + Ok((&point).into()) + } + /// Prepare CHURP for participation in the given handoff of the protocol. /// /// Initialization randomly selects a bivariate polynomial for the given @@ -534,23 +593,23 @@ impl Churp { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Handoff, client: &RemoteClient, ) -> Result where S: Suite, { - let id = ShareholderId(node_id.0); + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; - if !handoff.needs_share_reduction_switch_point(&id)? { + if !handoff.needs_share_reduction_switch_point(&x)? { return Err(Error::InvalidShareholder.into()); } // Fetch from the host node. if node_id == self.node_id { - let me = id.encode::()?; let shareholder = self.get_shareholder::(status.id, status.handoff)?; - let point = shareholder.switch_point(&me); + let point = shareholder.switch_point(&x); if handoff.needs_verification_matrix()? { // Local verification matrix is trusted. @@ -558,7 +617,7 @@ impl Churp { handoff.set_verification_matrix(vm)?; } - return handoff.add_share_reduction_switch_point(id, point); + return handoff.add_share_reduction_switch_point(x, point); } // Fetch from the remote node. @@ -566,7 +625,7 @@ impl Churp { if handoff.needs_verification_matrix()? { // The remote verification matrix needs to be verified. - let vm = block_on(client.verification_matrix(status.id, status.handoff))?; + let vm = block_on(client.churp_verification_matrix(status.id, status.handoff))?; let checksum = Self::checksum_verification_matrix_bytes( &vm, self.runtime_id, @@ -583,11 +642,14 @@ impl Churp { handoff.set_verification_matrix(vm)?; } - let point = - block_on(client.share_reduction_point(status.id, status.next_handoff, self.node_id))?; + let point = block_on(client.churp_share_reduction_point( + status.id, + status.next_handoff, + self.node_id, + ))?; let point = scalar_from_bytes(&point).ok_or(Error::PointDecodingFailed)?; - handoff.add_share_reduction_switch_point(id, point) + handoff.add_share_reduction_switch_point(x, point) } /// Tries to fetch switch data points for full share distribution from @@ -638,37 +700,37 @@ impl Churp { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Handoff, client: &RemoteClient, ) -> Result where S: Suite, { - let id = ShareholderId(node_id.0); + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; - if !handoff.needs_full_share_distribution_switch_point(&id)? { + if !handoff.needs_full_share_distribution_switch_point(&x)? { return Err(Error::InvalidShareholder.into()); } // Fetch from the host node. if node_id == self.node_id { - let me = id.encode::()?; let shareholder = handoff.get_reduced_shareholder()?; - let point = shareholder.switch_point(&me); + let point = shareholder.switch_point(&x); - return handoff.add_full_share_distribution_switch_point(id, point); + return handoff.add_full_share_distribution_switch_point(x, point); } // Fetch from the remote node. client.set_nodes(vec![node_id]); - let point = block_on(client.share_distribution_point( + let point = block_on(client.churp_share_distribution_point( status.id, status.next_handoff, self.node_id, ))?; let point = scalar_from_bytes(&point).ok_or(Error::PointDecodingFailed)?; - handoff.add_full_share_distribution_switch_point(id, point) + handoff.add_full_share_distribution_switch_point(x, point) } /// Tries to fetch proactive bivariate shares from the given nodes. @@ -713,33 +775,34 @@ impl Churp { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Handoff, client: &RemoteClient, ) -> Result where S: Suite, { - let id = ShareholderId(node_id.0); + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let x = encode_shareholder::(&node_id.0, &dst)?; - if !handoff.needs_bivariate_share(&id)? { + if !handoff.needs_bivariate_share(&x)? { return Err(Error::InvalidShareholder.into()); } // Fetch from the host node. if node_id == self.node_id { - let x = id.encode::()?; let kind = Self::handoff_kind(status); let dealer = self.get_dealer::(status.id, status.next_handoff)?; let share = dealer.make_share(x, kind); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); - return handoff.add_bivariate_share(id, verifiable_share); + return handoff.add_bivariate_share(&x, verifiable_share); } // Fetch from the remote node. client.set_nodes(vec![node_id]); - let share = block_on(client.bivariate_share(status.id, status.next_handoff, self.node_id))?; + let share = + block_on(client.churp_bivariate_share(status.id, status.next_handoff, self.node_id))?; // The remote verification matrix needs to be verified. let checksum = Self::checksum_verification_matrix_bytes( @@ -759,7 +822,7 @@ impl Churp { let verifiable_share: VerifiableSecretShare = share.try_into()?; - handoff.add_bivariate_share(id, verifiable_share) + handoff.add_bivariate_share(&x, verifiable_share) } /// Returns a signed confirmation request containing the checksum @@ -920,8 +983,9 @@ impl Churp { let share = share.ok_or(Error::ShareholderNotFound)?; // Verify that the host hasn't changed. - let me = ShareholderId(self.node_id.0).encode::()?; - if share.secret_share().coordinate_x() != &me { + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, churp_id); + let x = encode_shareholder::(&self.node_id.0, &dst)?; + if share.secret_share().coordinate_x() != &x { return Err(Error::InvalidHost.into()); } @@ -1094,20 +1158,20 @@ impl Churp { } /// Returns the handoff for the specified scheme and handoff epoch. - fn get_handoff(&self, churp_id: u8, epoch: EpochTime) -> Result>> + fn get_handoff(&self, churp_id: u8, epoch: EpochTime) -> Result>> where S: Suite + 'static, { - self._get_or_create_handoff(churp_id, epoch, None) + self._get_or_create_handoff::(churp_id, epoch, None) } /// Returns the handoff for the specified scheme and the next handoff epoch. /// If the handoff doesn't exist, a new one is created. - fn get_or_create_handoff(&self, status: &Status) -> Result>> + fn get_or_create_handoff(&self, status: &Status) -> Result>> where S: Suite + 'static, { - self._get_or_create_handoff(status.id, status.next_handoff, Some(status)) + self._get_or_create_handoff::(status.id, status.next_handoff, Some(status)) } fn _get_or_create_handoff( @@ -1115,7 +1179,7 @@ impl Churp { churp_id: u8, epoch: EpochTime, status: Option<&Status>, - ) -> Result>> + ) -> Result>> where S: Suite + 'static, { @@ -1132,7 +1196,7 @@ impl Churp { let handoff = data .object .clone() - .downcast::>() + .downcast::>() .or(Err(Error::HandoffDowncastFailed))?; return Ok(handoff); @@ -1146,13 +1210,13 @@ impl Churp { // Create a new handoff. let threshold = status.threshold; - let me = ShareholderId(self.node_id.0); - let shareholders = status - .applications - .keys() - .cloned() - .map(|id| ShareholderId(id.0)) - .collect(); + let dst = self.domain_separation_tag(ENCODE_SHAREHOLDER_CONTEXT, status.id); + let me = encode_shareholder::(&self.node_id.0, &dst)?; + let mut shareholders = Vec::with_capacity(status.applications.len()); + for id in status.applications.keys() { + let x = encode_shareholder::(&id.0, &dst)?; + shareholders.push(x); + } let kind = Self::handoff_kind(status); let handoff = Handoff::new(threshold, me, shareholders, kind)?; @@ -1234,28 +1298,30 @@ impl Churp { /// Verifies the node ID by comparing the session's runtime attestation /// key (RAK) with the one published in the consensus layer. - #[cfg(target_env = "sgx")] fn verify_node_id(&self, ctx: &RpcContext, node_id: &PublicKey) -> Result<()> { - let si = ctx.session_info.as_ref(); - let si = si.ok_or(Error::NotAuthenticated)?; - let session_rak = si.rak_binding.rak_pub(); + if !cfg!(any(target_env = "sgx", feature = "debug-mock-sgx")) { + // Skip verification in non-SGX environments because those + // nodes do not publish RAK in the consensus nor do they + // send RAK binding when establishing Noise sessions. + return Ok(()); + } + let remote_rak = Self::remote_rak(ctx)?; let rak = self .registry_state .rak(node_id, &self.runtime_id)? .ok_or(Error::NotAuthenticated)?; - if session_rak != rak { + if remote_rak != rak { return Err(Error::NotAuthorized.into()); } Ok(()) } - /// Authorizes the remote enclave so that secret data is never revealed - /// to an unauthorized enclave. - #[cfg(target_env = "sgx")] - fn verify_enclave(&self, ctx: &RpcContext, policy: &SignedPolicySGX) -> Result<()> { + /// Authorizes the remote key manager enclave so that secret data is never + /// revealed to an unauthorized enclave. + fn verify_km_enclave(&self, ctx: &RpcContext, policy: &SignedPolicySGX) -> Result<()> { if Self::ignore_policy() { return Ok(()); } @@ -1267,8 +1333,33 @@ impl Churp { Ok(()) } + /// Authorizes the remote runtime enclave so that secret data is never + /// revealed to an unauthorized enclave. + fn verify_rt_enclave( + &self, + ctx: &RpcContext, + policy: &SignedPolicySGX, + runtime_id: &Namespace, + ) -> Result<()> { + if Self::ignore_policy() { + return Ok(()); + } + let remote_enclave = Self::remote_enclave(ctx)?; + let policy = self.policies.verify(policy)?; + if !policy.may_query(remote_enclave, runtime_id) { + return Err(Error::NotAuthorized.into()); + } + Ok(()) + } + + /// Returns the session RAK of the remote enclave. + fn remote_rak(ctx: &RpcContext) -> Result { + let si = ctx.session_info.as_ref(); + let si = si.ok_or(Error::NotAuthenticated)?; + Ok(si.rak_binding.rak_pub()) + } + /// Returns the identity of the remote enclave. - #[cfg(target_env = "sgx")] fn remote_enclave(ctx: &RpcContext) -> Result<&EnclaveIdentity> { let si = ctx.session_info.as_ref(); let si = si.ok_or(Error::NotAuthenticated)?; @@ -1276,22 +1367,18 @@ impl Churp { } /// Returns true if key manager policies should be ignored. - #[cfg(target_env = "sgx")] fn ignore_policy() -> bool { option_env!("OASIS_UNSAFE_SKIP_KM_POLICY").is_some() } /// Returns a key manager client that connects only to enclaves eligible /// to form a new committee or to enclaves belonging the old committee. - fn key_manager_client(&self, _status: &Status, _new_committee: bool) -> Result { - #[cfg(not(target_env = "sgx"))] - let enclaves = None; - #[cfg(target_env = "sgx")] + fn key_manager_client(&self, status: &Status, new_committee: bool) -> Result { let enclaves = if Self::ignore_policy() { None } else { - let policy = self.policies.verify(&_status.policy)?; - let enclaves = match _new_committee { + let policy = self.policies.verify(&status.policy)?; + let enclaves = match new_committee { true => policy.may_join.clone(), false => policy.may_share.clone(), }; @@ -1360,6 +1447,17 @@ impl Churp { } HandoffKind::CommitteeChanged } + + /// Extends the given domain separation tag with key manager runtime ID + /// and churp ID. + fn domain_separation_tag(&self, context: &[u8], churp_id: u8) -> Vec { + let mut dst = context.to_vec(); + dst.extend(RUNTIME_CONTEXT_SEPARATOR); + dst.extend(&self.runtime_id.0); + dst.extend(CHURP_CONTEXT_SEPARATOR); + dst.extend(&[churp_id]); + dst + } } /// Replaces the given error with `Ok(None)`. diff --git a/keymanager/src/churp/methods.rs b/keymanager/src/churp/methods.rs index fbed2419701..b8212434e2d 100644 --- a/keymanager/src/churp/methods.rs +++ b/keymanager/src/churp/methods.rs @@ -28,6 +28,8 @@ pub const METHOD_SHARE_REDUCTION_POINT: &str = "churp/share_reduction_point"; pub const METHOD_SHARE_DISTRIBUTION_POINT: &str = "churp/share_distribution_point"; /// Name of the `bivariate_share` method. pub const METHOD_BIVARIATE_SHARE: &str = "churp/bivariate_share"; +/// Name of the `sgx_policy_key_share` method. +pub const METHOD_SGX_POLICY_KEY_SHARE: &str = "churp/sgx_policy_key_share"; impl RpcHandler for Churp { fn methods(&'static self) -> Vec { @@ -62,6 +64,13 @@ impl RpcHandler for Churp { }, move |ctx: &_, req: &_| self.bivariate_share(ctx, req), ), + RpcMethod::new( + RpcMethodDescriptor { + name: METHOD_SGX_POLICY_KEY_SHARE.to_string(), + kind: RpcKind::NoiseSession, + }, + move |ctx: &_, req: &_| self.sgx_policy_key_share(ctx, req), + ), /* Local queries */ RpcMethod::new( RpcMethodDescriptor { diff --git a/keymanager/src/churp/policy.rs b/keymanager/src/churp/policy.rs index 6098f29f16f..d2cd9825b5a 100644 --- a/keymanager/src/churp/policy.rs +++ b/keymanager/src/churp/policy.rs @@ -2,13 +2,14 @@ use std::{ cmp::Ordering, collections::{HashMap, HashSet}, + iter::FromIterator, sync::{Arc, Mutex}, }; use anyhow::Result; use oasis_core_runtime::{ - common::sgx::EnclaveIdentity, + common::{namespace::Namespace, sgx::EnclaveIdentity}, consensus::keymanager::churp::{PolicySGX, SignedPolicySGX}, }; @@ -69,9 +70,13 @@ pub struct VerifiedPolicy { /// during handouts. pub may_share: HashSet, - /// A hash of enclave identities that may form the new committee + /// A set of enclave identities that may form the new committee /// in the next handoffs. pub may_join: HashSet, + + /// A map of runtime identities and their respective sets of enclave + /// identities that are allowed to query key shares. + pub may_query: HashMap>, } impl VerifiedPolicy { @@ -80,20 +85,17 @@ impl VerifiedPolicy { /// The provided policy should be valid, signed by trusted signers, /// and published in the consensus layer state. fn new(verified_policy: &PolicySGX) -> Result { - let mut may_share = HashSet::new(); - for enclave_identity in &verified_policy.may_share { - may_share.insert(enclave_identity.clone()); - } - - let mut may_join = HashSet::new(); - for enclave_identity in &verified_policy.may_join { - may_join.insert(enclave_identity.clone()); - } + let may_share = HashSet::from_iter(verified_policy.may_share.iter().cloned()); + let may_join = HashSet::from_iter(verified_policy.may_join.iter().cloned()); + let may_query = HashMap::from_iter(verified_policy.may_query.iter().map( + |(runtime_id, enclaves)| (*runtime_id, HashSet::from_iter(enclaves.iter().cloned())), + )); Ok(Self { serial: verified_policy.serial, may_share, may_join, + may_query, }) } @@ -108,4 +110,13 @@ impl VerifiedPolicy { pub fn may_join(&self, remote_enclave: &EnclaveIdentity) -> bool { self.may_join.contains(remote_enclave) } + + /// Returns true iff the remote enclave is allowed to query key shares + /// for the given runtime. + pub fn may_query(&self, remote_enclave: &EnclaveIdentity, runtime_id: &Namespace) -> bool { + self.may_query + .get(runtime_id) + .map(|may_query_runtime| may_query_runtime.contains(remote_enclave)) + .unwrap_or(false) + } } diff --git a/keymanager/src/churp/types.rs b/keymanager/src/churp/types.rs index c436abd322f..e4b6990fc72 100644 --- a/keymanager/src/churp/types.rs +++ b/keymanager/src/churp/types.rs @@ -2,6 +2,7 @@ use std::convert::{TryFrom, TryInto}; use group::{ff::PrimeField, Group, GroupEncoding}; +use zeroize::Zeroize; use oasis_core_runtime::{ common::{ @@ -18,11 +19,13 @@ use secret_sharing::{ churp::{SecretShare, VerifiableSecretShare}, vss::{ matrix::VerificationMatrix, - polynomial::Polynomial, + polynomial::{EncryptedPoint, Polynomial}, scalar::{scalar_from_bytes, scalar_to_bytes}, }, }; +use crate::crypto::KeyPairId; + use super::Error; /// Handoff request. @@ -138,6 +141,25 @@ pub struct SignedConfirmationRequest { pub signature: Signature, } +/// Key share request. +#[derive(Clone, Default, cbor::Encode, cbor::Decode)] +pub struct KeyShareRequest { + /// A unique identifier within the key manager runtime. + pub id: u8, + + /// The identifier of the key manager runtime. + pub runtime_id: Namespace, + + /// The epoch of the handoff. + pub epoch: EpochTime, + + /// The identifier of the runtime for which the key share is requested. + pub key_runtime_id: Namespace, + + /// The identifier of the key. + pub key_id: KeyPairId, +} + /// Encoded verifiable secret share. #[derive(Clone, Default, cbor::Encode, cbor::Decode)] pub struct EncodedVerifiableSecretShare { @@ -211,3 +233,48 @@ where Ok(share) } } + +/// Encoded encrypted point. +#[derive(Clone, Default, cbor::Encode, cbor::Decode, Zeroize)] +pub struct EncodedEncryptedPoint { + /// Encoded x-coordinate. + pub x: Vec, + + /// Encoded y-coordinate in encrypted form. + pub z: Vec, +} + +impl From<&EncryptedPoint> for EncodedEncryptedPoint +where + G: Group + GroupEncoding, +{ + fn from(point: &EncryptedPoint) -> Self { + Self { + x: scalar_to_bytes(point.x()), + z: point.z().to_bytes().as_ref().to_vec(), + } + } +} + +impl TryFrom for EncryptedPoint +where + G: Group + GroupEncoding, +{ + type Error = Error; + + fn try_from(encoded: EncodedEncryptedPoint) -> Result { + let x = scalar_from_bytes(&encoded.x).ok_or(Error::IdentityDecodingFailed)?; + + let mut repr: G::Repr = Default::default(); + let slice = &mut repr.as_mut()[..]; + slice.copy_from_slice(&encoded.z); + + let z = match G::from_bytes(&repr).into() { + None => return Err(Error::IdentityDecodingFailed), + Some(z) => z, + }; + + let point = EncryptedPoint::new(x, z); + Ok(point) + } +} diff --git a/keymanager/src/client/interface.rs b/keymanager/src/client/interface.rs index d052ccbd5cc..407506b2231 100644 --- a/keymanager/src/client/interface.rs +++ b/keymanager/src/client/interface.rs @@ -8,7 +8,7 @@ use oasis_core_runtime::{common::crypto::signature::PublicKey, consensus::beacon use crate::{ api::KeyManagerError, churp::EncodedVerifiableSecretShare, - crypto::{KeyPair, KeyPairId, Secret, SignedPublicKey, VerifiableSecret}, + crypto::{KeyPair, KeyPairId, Secret, SignedPublicKey, StateKey, VerifiableSecret}, }; /// Key manager client interface. @@ -66,7 +66,7 @@ pub trait KeyManagerClient: Send + Sync { -> Result; /// Returns the verification matrix for the given handoff. - async fn verification_matrix( + async fn churp_verification_matrix( &self, churp_id: u8, epoch: EpochTime, @@ -74,7 +74,7 @@ pub trait KeyManagerClient: Send + Sync { /// Returns a switch point for the share reduction phase /// of the given handoff. - async fn share_reduction_point( + async fn churp_share_reduction_point( &self, churp_id: u8, epoch: EpochTime, @@ -83,7 +83,7 @@ pub trait KeyManagerClient: Send + Sync { /// Returns a switch point for the share distribution phase /// of the given handoff. - async fn share_distribution_point( + async fn churp_share_distribution_point( &self, churp_id: u8, epoch: EpochTime, @@ -91,12 +91,19 @@ pub trait KeyManagerClient: Send + Sync { ) -> Result, KeyManagerError>; /// Returns a bivariate share for the given handoff. - async fn bivariate_share( + async fn churp_bivariate_share( &self, churp_id: u8, epoch: EpochTime, node_id: PublicKey, ) -> Result; + + /// Returns state key. + async fn churp_state_key( + &self, + churp_id: u8, + key_id: KeyPairId, + ) -> Result; } #[async_trait] @@ -151,38 +158,46 @@ impl KeyManagerClient for Arc { KeyManagerClient::replicate_ephemeral_secret(&**self, epoch).await } - async fn verification_matrix( + async fn churp_verification_matrix( &self, churp_id: u8, epoch: EpochTime, ) -> Result, KeyManagerError> { - KeyManagerClient::verification_matrix(&**self, churp_id, epoch).await + KeyManagerClient::churp_verification_matrix(&**self, churp_id, epoch).await } - async fn share_reduction_point( + async fn churp_share_reduction_point( &self, churp_id: u8, epoch: EpochTime, node_id: PublicKey, ) -> Result, KeyManagerError> { - KeyManagerClient::share_reduction_point(&**self, churp_id, epoch, node_id).await + KeyManagerClient::churp_share_reduction_point(&**self, churp_id, epoch, node_id).await } - async fn share_distribution_point( + async fn churp_share_distribution_point( &self, churp_id: u8, epoch: EpochTime, node_id: PublicKey, ) -> Result, KeyManagerError> { - KeyManagerClient::share_distribution_point(&**self, churp_id, epoch, node_id).await + KeyManagerClient::churp_share_distribution_point(&**self, churp_id, epoch, node_id).await } - async fn bivariate_share( + async fn churp_bivariate_share( &self, churp_id: u8, epoch: EpochTime, node_id: PublicKey, ) -> Result { - KeyManagerClient::bivariate_share(&**self, churp_id, epoch, node_id).await + KeyManagerClient::churp_bivariate_share(&**self, churp_id, epoch, node_id).await + } + + async fn churp_state_key( + &self, + churp_id: u8, + key_id: KeyPairId, + ) -> Result { + KeyManagerClient::churp_state_key(&**self, churp_id, key_id).await } } diff --git a/keymanager/src/client/mock.rs b/keymanager/src/client/mock.rs index 228a77dd0f7..ba416096459 100644 --- a/keymanager/src/client/mock.rs +++ b/keymanager/src/client/mock.rs @@ -11,7 +11,7 @@ use oasis_core_runtime::{ use crate::{ api::KeyManagerError, churp::EncodedVerifiableSecretShare, - crypto::{KeyPair, KeyPairId, Secret, SignedPublicKey, VerifiableSecret}, + crypto::{KeyPair, KeyPairId, Secret, SignedPublicKey, StateKey, VerifiableSecret}, }; use super::KeyManagerClient; @@ -109,7 +109,7 @@ impl KeyManagerClient for MockClient { unimplemented!(); } - async fn verification_matrix( + async fn churp_verification_matrix( &self, _churp_id: u8, _epoch: EpochTime, @@ -117,7 +117,7 @@ impl KeyManagerClient for MockClient { unimplemented!(); } - async fn share_reduction_point( + async fn churp_share_reduction_point( &self, _churp_id: u8, _epoch: EpochTime, @@ -126,7 +126,7 @@ impl KeyManagerClient for MockClient { unimplemented!(); } - async fn share_distribution_point( + async fn churp_share_distribution_point( &self, _churp_id: u8, _epoch: EpochTime, @@ -135,7 +135,7 @@ impl KeyManagerClient for MockClient { unimplemented!(); } - async fn bivariate_share( + async fn churp_bivariate_share( &self, _churp_id: u8, _epoch: EpochTime, @@ -143,4 +143,12 @@ impl KeyManagerClient for MockClient { ) -> Result { unimplemented!(); } + + async fn churp_state_key( + &self, + _churp_id: u8, + _key_id: KeyPairId, + ) -> Result { + unimplemented!(); + } } diff --git a/keymanager/src/client/remote.rs b/keymanager/src/client/remote.rs index abf9f01468e..a8ef9ab6cfb 100644 --- a/keymanager/src/client/remote.rs +++ b/keymanager/src/client/remote.rs @@ -1,29 +1,44 @@ //! Key manager client which talks to a remote key manager enclave. use std::{ collections::HashSet, + convert::TryInto, iter::FromIterator, num::NonZeroUsize, sync::{Arc, RwLock}, }; +use anyhow::anyhow; use async_trait::async_trait; +use group::GroupEncoding; use lru::LruCache; +use rand::{prelude::SliceRandom, rngs::OsRng}; use oasis_core_runtime::{ common::{ crypto::signature::{self, PublicKey}, - namespace::Namespace, + namespace::{Namespace, NAMESPACE_SIZE}, sgx::{EnclaveIdentity, QuotePolicy}, }, consensus::{ beacon::EpochTime, - state::{beacon::ImmutableState as BeaconState, keymanager::Status as KeyManagerStatus}, + keymanager::churp::{self, Status as ChurpStatus, SuiteId}, + state::{ + beacon::ImmutableState as BeaconState, + keymanager::{churp::ImmutableState as ChurpState, Status as KeyManagerStatus}, + registry::ImmutableState as RegistryState, + }, verifier::Verifier, }, enclave_rpc::{client::RpcClient, session}, identity::Identity, protocol::Protocol, }; +use secret_sharing::{ + churp::{HandoffKind, Player}, + kdc::KeyRecoverer, + suites::{p384, Suite}, + vss::polynomial::EncryptedPoint, +}; use crate::{ api::{ @@ -34,10 +49,13 @@ use crate::{ METHOD_REPLICATE_EPHEMERAL_SECRET, METHOD_REPLICATE_MASTER_SECRET, }, churp::{ - EncodedVerifiableSecretShare, QueryRequest, METHOD_BIVARIATE_SHARE, - METHOD_SHARE_DISTRIBUTION_POINT, METHOD_SHARE_REDUCTION_POINT, METHOD_VERIFICATION_MATRIX, + EncodedEncryptedPoint, EncodedVerifiableSecretShare, Kdf, KeyShareRequest, QueryRequest, + METHOD_BIVARIATE_SHARE, METHOD_SGX_POLICY_KEY_SHARE, METHOD_SHARE_DISTRIBUTION_POINT, + METHOD_SHARE_REDUCTION_POINT, METHOD_VERIFICATION_MATRIX, + }, + crypto::{ + KeyPair, KeyPairId, Secret, SignedPublicKey, StateKey, VerifiableSecret, KEY_PAIR_ID_SIZE, }, - crypto::{KeyPair, KeyPairId, Secret, SignedPublicKey, VerifiableSecret}, policy::{set_trusted_signers, verify_data_and_trusted_signers, Policy, TrustedSigners}, }; @@ -62,6 +80,8 @@ pub struct RemoteClient { ephemeral_private_keys: RwLock>, /// Local cache for the ephemeral public keys. ephemeral_public_keys: RwLock>, + /// Local cache for the state keys. + state_keys: RwLock>, /// Key manager's runtime signing key. rsk: RwLock>, } @@ -83,6 +103,7 @@ impl RemoteClient { longterm_public_keys: RwLock::new(LruCache::new(cap)), ephemeral_private_keys: RwLock::new(LruCache::new(cap)), ephemeral_public_keys: RwLock::new(LruCache::new(cap)), + state_keys: RwLock::new(LruCache::new(cap)), rsk: RwLock::new(None), } } @@ -218,6 +239,85 @@ impl RemoteClient { key.verify(self.runtime_id, key_pair_id, epoch, now, pk) .map_err(KeyManagerError::InvalidSignature) } + + async fn churp_recover_state_key( + &self, + key_id: KeyPairId, + status: churp::Status, + ) -> Result { + // Fault detection and blame assignment are not supported, + // so the minimal number of key shares will suffice. + let kind = HandoffKind::CommitteeUnchanged; + let player = Player::new(status.threshold, kind); + let min_shares = player.min_shares(); + let mut shares = Vec::with_capacity(min_shares); + + // Fetch key shares in random order. + // TODO: Optimize by fetching key shares concurrently. + let mut committee = status.committee; + committee.shuffle(&mut OsRng); + + for node_id in committee { + // Stop fetching when enough shares are received. + if shares.len() == min_shares { + break; + } + + // Fetch key share from the current node. + self.rpc_client + .update_nodes_async(vec![node_id]) + .await + .map_err(|err| KeyManagerError::Other(err.into()))?; + + let response = self + .rpc_client + .secure_call( + METHOD_SGX_POLICY_KEY_SHARE, + KeyShareRequest { + id: status.id, + runtime_id: status.runtime_id, + epoch: status.handoff, + key_runtime_id: self.runtime_id, + key_id, + }, + ) + .await + .into_result_with_feedback() + .await; + + // Decode the response. + let encoded_share: EncodedEncryptedPoint = match response { + Ok(encoded_share) => encoded_share, + Err(_) => continue, // Ignore error and skip this share. + }; + let share: EncryptedPoint = match encoded_share.try_into() { + Ok(share) => share, + Err(_) => continue, // Ignore error and skip this share. + }; + + shares.push(share); + } + + // Abort if we don't have enough shares. + if shares.len() != min_shares { + return Err(KeyManagerError::InsufficientKeyShares); + } + + // Prepare salt for key derivation (runtime id || churp id || key id). + let mut salt = [0; NAMESPACE_SIZE + 1 + KEY_PAIR_ID_SIZE]; + salt[..NAMESPACE_SIZE].copy_from_slice(&status.runtime_id.0); + salt[NAMESPACE_SIZE] = status.id; + salt[NAMESPACE_SIZE + 1..].copy_from_slice(&key_id.0); + + // Recover the secret and derive the state key from it. + // NOTE: Elliptic curve points in projective form are first converted + // to affine form, and then encoded to bytes using point compression. + let key = player.recover_key(&shares)?; + let secret = key.to_bytes(); + let state_key = Kdf::state_key(secret.as_ref(), &salt); + + Ok(state_key) + } } #[async_trait] @@ -498,7 +598,7 @@ impl KeyManagerClient for RemoteClient { .map(|rsp: ReplicateEphemeralSecretResponse| rsp.ephemeral_secret) } - async fn verification_matrix( + async fn churp_verification_matrix( &self, churp_id: u8, epoch: EpochTime, @@ -519,7 +619,7 @@ impl KeyManagerClient for RemoteClient { .map_err(|err| KeyManagerError::Other(err.into())) } - async fn share_reduction_point( + async fn churp_share_reduction_point( &self, churp_id: u8, epoch: EpochTime, @@ -541,7 +641,7 @@ impl KeyManagerClient for RemoteClient { .map_err(|err| KeyManagerError::Other(err.into())) } - async fn share_distribution_point( + async fn churp_share_distribution_point( &self, churp_id: u8, epoch: EpochTime, @@ -563,7 +663,7 @@ impl KeyManagerClient for RemoteClient { .map_err(|err| KeyManagerError::Other(err.into())) } - async fn bivariate_share( + async fn churp_bivariate_share( &self, churp_id: u8, epoch: EpochTime, @@ -584,4 +684,51 @@ impl KeyManagerClient for RemoteClient { .await .map_err(|err| KeyManagerError::Other(err.into())) } + + async fn churp_state_key( + &self, + churp_id: u8, + key_id: KeyPairId, + ) -> Result { + let id = (key_id, churp_id); + + // First try to fetch from cache. + { + let mut cache = self.state_keys.write().unwrap(); + if let Some(key) = cache.get(&id) { + return Ok(key.clone()); + } + } + + // No entry in cache, fetch from key manager. + let consensus_state = self.consensus_verifier.latest_state().await?; + let registry_state = RegistryState::new(&consensus_state); + let churp_state = ChurpState::new(&consensus_state); + + let status = tokio::task::block_in_place(move || -> Result { + let runtime = registry_state + .runtime(&self.runtime_id)? + .ok_or(anyhow!("missing runtime descriptor"))?; + let key_manager_id = runtime + .key_manager + .ok_or(anyhow!("runtime doesn't use key manager"))?; + let status = churp_state + .status(key_manager_id, churp_id)? + .ok_or(anyhow!("churp status not found"))?; + Ok(status) + })?; + + let state_key = match status.suite_id { + SuiteId::NistP384Sha3_384 => { + self.churp_recover_state_key::(key_id, status) + .await? + } + }; + + // Cache key. + let mut cache = self.state_keys.write().unwrap(); + cache.put(id, state_key.clone()); + + Ok(state_key) + } } diff --git a/keymanager/src/crypto/types.rs b/keymanager/src/crypto/types.rs index 046e80cebe7..c97dc33470e 100644 --- a/keymanager/src/crypto/types.rs +++ b/keymanager/src/crypto/types.rs @@ -17,8 +17,6 @@ use oasis_core_runtime::{ impl_bytes, }; -impl_bytes!(KeyPairId, 32, "A 256-bit key pair identifier."); - /// Context used for the public key signature. const PUBLIC_KEY_SIGNATURE_CONTEXT: &[u8] = b"oasis-core/keymanager: pk signature"; @@ -34,6 +32,15 @@ pub const STATE_KEY_SIZE: usize = 32; /// The size of the key manager master and ephemeral secrets. pub const SECRET_SIZE: usize = 32; +/// The size of the key pair identifier. +pub const KEY_PAIR_ID_SIZE: usize = 32; + +impl_bytes!( + KeyPairId, + KEY_PAIR_ID_SIZE, + "A 256-bit key pair identifier." +); + /// A state encryption key. #[derive(Clone, Default, cbor::Encode, cbor::Decode, Zeroize, ZeroizeOnDrop)] #[cbor(transparent)] diff --git a/runtime/src/consensus/keymanager.rs b/runtime/src/consensus/keymanager.rs index 563fa23bc36..912088711c0 100644 --- a/runtime/src/consensus/keymanager.rs +++ b/runtime/src/consensus/keymanager.rs @@ -49,7 +49,14 @@ pub struct PolicySGX { /// Per enclave key manager access control policy. #[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] pub struct EnclavePolicySGX { + /// A map of runtime IDs to the vector of enclave IDs that may query + /// private key material. pub may_query: HashMap>, + + /// A vector of enclave IDs that may retrieve the master secret. + /// + /// NOTE: Each enclave ID may always implicitly replicate from other + /// instances of itself. pub may_replicate: Vec, } diff --git a/runtime/src/consensus/keymanager/churp.rs b/runtime/src/consensus/keymanager/churp.rs index f45a816230d..9daa4bf87b9 100644 --- a/runtime/src/consensus/keymanager/churp.rs +++ b/runtime/src/consensus/keymanager/churp.rs @@ -146,12 +146,16 @@ pub struct PolicySGX { pub serial: u32, /// A vector of enclave identities from which a share can be obtained - /// during handouts. + /// during handoffs. pub may_share: Vec, /// A vector of enclave identities that may form the new committee /// in the next handoffs. pub may_join: Vec, + + /// A map of runtime identities to the vector of enclave identities + /// that may query key shares. + pub may_query: HashMap>, } /// Signed key manager access control policy. diff --git a/runtime/src/consensus/state/keymanager/churp.rs b/runtime/src/consensus/state/keymanager/churp.rs index 9a05dceac35..acbdfa402a0 100644 --- a/runtime/src/consensus/state/keymanager/churp.rs +++ b/runtime/src/consensus/state/keymanager/churp.rs @@ -157,6 +157,7 @@ mod test { serial: 6, may_share: vec![enclave1], may_join: vec![enclave2], + may_query: HashMap::new(), }, signatures: vec![ SignatureBundle { diff --git a/runtime/src/enclave_rpc/client.rs b/runtime/src/enclave_rpc/client.rs index 42ebd46e6ce..c3bdeb0b4eb 100644 --- a/runtime/src/enclave_rpc/client.rs +++ b/runtime/src/enclave_rpc/client.rs @@ -556,6 +556,17 @@ impl RpcClient { .unwrap(); } + /// Update allowed nodes. + pub async fn update_nodes_async( + &self, + nodes: Vec, + ) -> Result<(), RpcClientError> { + self.cmdq + .send(Command::UpdateNodes(nodes)) + .await + .map_err(|_| RpcClientError::Dropped) + } + /// Wait for the controller to process all queued messages. #[cfg(test)] async fn flush_cmd_queue(&self) -> Result<(), RpcClientError> { diff --git a/secret-sharing/src/churp/handoff.rs b/secret-sharing/src/churp/handoff.rs index 303356da605..aa51f0c3754 100644 --- a/secret-sharing/src/churp/handoff.rs +++ b/secret-sharing/src/churp/handoff.rs @@ -1,10 +1,11 @@ -use std::{collections::HashSet, sync::Arc}; +use std::sync::Arc; use anyhow::Result; +use group::{Group, GroupEncoding}; -use crate::{suites::Suite, vss::matrix::VerificationMatrix}; +use crate::vss::matrix::VerificationMatrix; -use super::{DimensionSwitch, Error, Shareholder, ShareholderId, VerifiableSecretShare}; +use super::{DimensionSwitch, Error, Shareholder, VerifiableSecretShare}; /// Handoff kind. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -80,27 +81,27 @@ impl HandoffKind { /// Handoff proactivizes the shared secret (changes associated shares) while /// transferring the secret from an old committee to a new, possibly /// intersecting one. -pub struct Handoff { +pub struct Handoff { /// Handoff kind. kind: HandoffKind, /// The share reduction phase of the handoff. - share_reduction: Option>, + share_reduction: Option>, /// The share distribution phase of the handoff. - share_distribution: Option>, + share_distribution: Option>, } -impl Handoff +impl Handoff where - S: Suite, + G: Group + GroupEncoding, { /// Creates a new handoff using the given shareholders (new committee) /// to proactivize the shared secret. pub fn new( threshold: u8, - me: ShareholderId, - shareholders: HashSet, + me: G::Scalar, + shareholders: Vec, kind: HandoffKind, ) -> Result { let (share_reduction, share_distribution) = match kind { @@ -134,7 +135,7 @@ where let share_distribution = DimensionSwitch::new_full_share_distribution( threshold, me, - HashSet::new(), // Skip proactivization. + Vec::new(), // Skip proactivization. kind, )?; @@ -166,7 +167,7 @@ where } /// Sets the verification matrix from the previous handoff. - pub fn set_verification_matrix(&self, vm: VerificationMatrix) -> Result<()> { + pub fn set_verification_matrix(&self, vm: VerificationMatrix) -> Result<()> { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); } @@ -193,7 +194,7 @@ where } /// Sets the shareholder from the previous handoff. - pub fn set_shareholder(&self, shareholder: Arc>) -> Result<()> { + pub fn set_shareholder(&self, shareholder: Arc>) -> Result<()> { if self.kind != HandoffKind::CommitteeUnchanged { return Err(Error::InvalidKind.into()); } @@ -206,7 +207,7 @@ where /// Checks if share reduction needs a switch point from the given /// shareholder. - pub fn needs_share_reduction_switch_point(&self, id: &ShareholderId) -> Result { + pub fn needs_share_reduction_switch_point(&self, x: &G::Scalar) -> Result { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); } @@ -214,15 +215,11 @@ where self.share_reduction .as_ref() .ok_or(Error::InvalidState)? - .needs_switch_point(id) + .needs_switch_point(x) } /// Adds the given switch point to share reduction. - pub fn add_share_reduction_switch_point( - &self, - id: ShareholderId, - bij: S::PrimeField, - ) -> Result { + pub fn add_share_reduction_switch_point(&self, x: G::Scalar, bij: G::Scalar) -> Result { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); } @@ -230,12 +227,12 @@ where self.share_reduction .as_ref() .ok_or(Error::InvalidState)? - .add_switch_point(id, bij) + .add_switch_point(x, bij) } /// Checks if full share distribution needs a switch point from the given /// shareholder. - pub fn needs_full_share_distribution_switch_point(&self, id: &ShareholderId) -> Result { + pub fn needs_full_share_distribution_switch_point(&self, x: &G::Scalar) -> Result { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); } @@ -243,14 +240,14 @@ where self.share_distribution .as_ref() .ok_or(Error::InvalidState)? - .needs_switch_point(id) + .needs_switch_point(x) } /// Adds the given switch point to full share distribution. pub fn add_full_share_distribution_switch_point( &self, - id: ShareholderId, - bij: S::PrimeField, + x: G::Scalar, + bij: G::Scalar, ) -> Result { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); @@ -259,11 +256,11 @@ where self.share_distribution .as_ref() .ok_or(Error::InvalidState)? - .add_switch_point(id, bij) + .add_switch_point(x, bij) } /// Checks if bivariate share is needed from the given shareholder. - pub fn needs_bivariate_share(&self, id: &ShareholderId) -> Result { + pub fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { let ds = match self.kind { HandoffKind::DealingPhase => &self.share_distribution, HandoffKind::CommitteeUnchanged => &self.share_distribution, @@ -272,14 +269,14 @@ where ds.as_ref() .ok_or(Error::InvalidState)? - .needs_bivariate_share(id) + .needs_bivariate_share(x) } /// Adds the given bivariate share. pub fn add_bivariate_share( &self, - id: ShareholderId, - verifiable_share: VerifiableSecretShare, + x: &G::Scalar, + verifiable_share: VerifiableSecretShare, ) -> Result { let ds = match self.kind { HandoffKind::DealingPhase => &self.share_distribution, @@ -290,7 +287,7 @@ where let res = ds .as_ref() .ok_or(Error::InvalidState)? - .add_bivariate_share(id, verifiable_share); + .add_bivariate_share(x, verifiable_share); // Start full share distribution if share reduction has completed. if self.kind == HandoffKind::CommitteeChanged && res.as_ref().is_ok_and(|&done| done) { @@ -313,7 +310,7 @@ where } /// Returns the shareholder resulting from share reduction. - pub fn get_reduced_shareholder(&self) -> Result>> { + pub fn get_reduced_shareholder(&self) -> Result>> { if self.kind != HandoffKind::CommitteeChanged { return Err(Error::InvalidKind.into()); } @@ -325,7 +322,7 @@ where } /// Returns the shareholder resulting from full share distribution. - pub fn get_full_shareholder(&self) -> Result>> { + pub fn get_full_shareholder(&self) -> Result>> { self.share_distribution .as_ref() .ok_or(Error::InvalidState)? @@ -335,40 +332,38 @@ where #[cfg(test)] mod tests { - use std::{ - collections::{HashMap, HashSet}, - sync::Arc, - }; + use std::{collections::HashSet, iter::zip, sync::Arc}; use rand::{rngs::StdRng, RngCore, SeedableRng}; use crate::{ - churp::{self, HandoffKind, ShareholderId, VerifiableSecretShare}, + churp::{self, HandoffKind, VerifiableSecretShare}, suites::{self, p384}, }; type Suite = p384::Sha3_384; type Group = ::Group; + type PrimeField = ::PrimeField; type Shareholder = churp::Shareholder; type Dealer = churp::Dealer; - type Handoff = churp::Handoff; + type Handoff = churp::Handoff; - fn shareholder_id(id: u8) -> ShareholderId { - ShareholderId([id; 32]) + fn prepare_shareholders(ids: &[u64]) -> Vec { + ids.into_iter().map(|&id| id.into()).collect() } - fn shareholder_ids(ids: Vec) -> Vec { - ids.into_iter().map(shareholder_id).collect() - } - - fn verify_shareholders(shareholders: &HashMap>) { - // Verify that all shareholders have the same matrix. + fn verify_shareholders(shareholders: &[Arc], threshold: u8, full_share: bool) { let mut vms = HashSet::new(); - for shareholder in shareholders.values() { - let bytes = shareholder - .verifiable_share() - .verification_matrix() - .to_bytes(); + for shareholder in shareholders { + let share = shareholder.verifiable_share(); + + // Verify that the share is valid. + share + .verify(threshold, false, full_share) + .expect("share should be valid"); + + // Verify that all shareholders have the same matrix. + let bytes = share.verification_matrix().to_bytes(); vms.insert(bytes); if vms.len() != 1 { @@ -380,15 +375,12 @@ mod tests { fn prepare_dealers( threshold: u8, dealing_phase: bool, - committee: HashSet, + n: usize, rng: &mut impl RngCore, - ) -> HashMap { - let mut dealers = HashMap::new(); - for sh in committee.iter() { - let d = Dealer::create(threshold, dealing_phase, rng).unwrap(); - dealers.insert(sh.clone(), d); - } - dealers + ) -> Vec { + (0..n) + .map(|_| Dealer::create(threshold, dealing_phase, rng).unwrap()) + .collect() } #[test] @@ -397,61 +389,56 @@ mod tests { let threshold = 2; // Handoff 0: Dealing phase. - let committee = shareholder_ids(vec![1, 2, 3, 4]); // At least 4 (threshold + 2). - let committee: HashSet<_> = committee.iter().cloned().collect(); - let kind = HandoffKind::DealingPhase; - let dealers = prepare_dealers(threshold, true, committee.clone(), &mut rng); - let mut handoffs = HashMap::new(); + let committee = prepare_shareholders(&[1, 2, 3, 4]); // At least 4 (threshold + 2). + let dealers = prepare_dealers(threshold, true, committee.len(), &mut rng); + let mut handoffs = Vec::with_capacity(committee.len()); for alice in committee.iter() { let handoff = Handoff::new(threshold, alice.clone(), committee.clone(), kind).unwrap(); // Proactivization. - for (i, (bob, dealer)) in dealers.iter().enumerate() { - let x = alice.encode::().unwrap(); - let share = dealer.make_share(x, kind); + for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { + let share = dealer.make_share(alice.clone(), kind); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); - assert!(handoff.needs_bivariate_share(&bob).unwrap()); - let done = handoff - .add_bivariate_share(bob.clone(), verifiable_share) - .unwrap(); + assert!(handoff.needs_bivariate_share(bob).unwrap()); + let done = handoff.add_bivariate_share(bob, verifiable_share).unwrap(); - if i + 1 < dealers.len() { + if j + 1 < dealers.len() { // Proactivization still in progress. assert!(!done); - assert!(!handoff.needs_bivariate_share(&bob).unwrap()); + assert!(!handoff.needs_bivariate_share(bob).unwrap()); } else { // Proactivization done. assert!(done); - assert!(handoff.needs_bivariate_share(&bob).is_err()); + assert!(handoff.needs_bivariate_share(bob).is_err()); } } - handoffs.insert(alice.clone(), handoff); + handoffs.push(handoff); } // Extract and verify shareholders. - let mut shareholders = HashMap::new(); - for (alice, handoff) in handoffs.iter() { + let mut shareholders = Vec::with_capacity(committee.len()); + for handoff in handoffs.iter() { // Share reduction should be skipped. assert!(handoff.get_reduced_shareholder().is_err()); // Full share distribution should be completed. let shareholder = handoff.get_full_shareholder().unwrap(); - shareholders.insert(alice.clone(), shareholder); + shareholders.push(shareholder); } - verify_shareholders(&shareholders); + verify_shareholders(&shareholders, threshold, true); // Handoff 1: Committee remains unchanged. let kind = HandoffKind::CommitteeUnchanged; - let dealers = prepare_dealers(threshold, false, committee.clone(), &mut rng); - let mut handoffs = HashMap::new(); + let dealers = prepare_dealers(threshold, false, committee.len(), &mut rng); + let mut handoffs = Vec::with_capacity(committee.len()); - for alice in committee.iter() { + for (i, alice) in committee.iter().enumerate() { let handoff = Handoff::new( threshold, alice.clone(), @@ -460,24 +447,21 @@ mod tests { ) .unwrap(); - let shareholder = shareholders.get(&alice).unwrap().clone(); + let shareholder = shareholders.get(i).unwrap().clone(); assert!(handoff.needs_shareholder().unwrap()); handoff.set_shareholder(shareholder).unwrap(); // Proactivization. - for (i, (bob, dealer)) in dealers.iter().enumerate() { - let x = alice.encode::().unwrap(); - let share = dealer.make_share(x, kind); + for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { + let share = dealer.make_share(alice.clone(), kind); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); assert!(handoff.needs_bivariate_share(&bob).unwrap()); - let done = handoff - .add_bivariate_share(bob.clone(), verifiable_share) - .unwrap(); + let done = handoff.add_bivariate_share(&bob, verifiable_share).unwrap(); - if i + 1 < dealers.len() { + if j + 1 < dealers.len() { // Proactivization still in progress. assert!(!done); assert!(!handoff.needs_bivariate_share(&bob).unwrap()); @@ -488,29 +472,27 @@ mod tests { } } - handoffs.insert(alice.clone(), handoff); + handoffs.insert(i, handoff); } // Extract and verify shareholders. - let mut shareholders = HashMap::new(); - for (alice, handoff) in handoffs.iter() { + let mut shareholders = Vec::with_capacity(committee.len()); + for handoff in handoffs.iter() { // Share reduction should be skipped. assert!(handoff.get_reduced_shareholder().is_err()); // Full share distribution should be completed. let shareholder = handoff.get_full_shareholder().unwrap(); - shareholders.insert(alice.clone(), shareholder); + shareholders.push(shareholder); } - verify_shareholders(&shareholders); + verify_shareholders(&shareholders, threshold, true); // Handoff 2: Committee changed. - let committee = shareholder_ids(vec![3, 4, 5, 6, 7]); // At least 5 (2 * threshold + 1). - let committee: HashSet<_> = committee.iter().cloned().collect(); - let kind = HandoffKind::CommitteeChanged; - let dealers = prepare_dealers(threshold, false, committee.clone(), &mut rng); - let mut handoffs = HashMap::new(); + let committee = prepare_shareholders(&[3, 4, 5, 6, 7]); // At least 5 (2 * threshold + 1). + let dealers = prepare_dealers(threshold, false, committee.len(), &mut rng); + let mut handoffs = Vec::with_capacity(committee.len()); for alice in committee.iter() { let handoff = Handoff::new( @@ -523,11 +505,7 @@ mod tests { // Fetch verification matrix from the old committee. assert!(handoff.needs_verification_matrix().unwrap()); - let vm = shareholders - .iter() - .nth(0) - .unwrap() - .1 + let vm = shareholders[0] .verifiable_share() .verification_matrix() .clone(); @@ -535,15 +513,16 @@ mod tests { // Share reduction. let num_points = threshold as usize + 1; - for (i, (bob, shareholder)) in shareholders.iter().take(num_points).enumerate() { - assert!(handoff.needs_share_reduction_switch_point(bob).unwrap()); - let id = alice.encode::().unwrap(); - let bij = shareholder.switch_point(&id); + for (j, shareholder) in shareholders.iter().take(num_points).enumerate() { + let bob = shareholder.verifiable_share().share.x; + + assert!(handoff.needs_share_reduction_switch_point(&bob).unwrap()); + let bij = shareholder.switch_point(alice); let done = handoff .add_share_reduction_switch_point(bob.clone(), bij) .unwrap(); - if i + 1 < num_points { + if j + 1 < num_points { // Accumulation still in progress. assert!(!done); assert!(!handoff.needs_share_reduction_switch_point(&bob).unwrap()); @@ -555,18 +534,15 @@ mod tests { } // Proactivization. - for (i, (bob, dealer)) in dealers.iter().enumerate() { - let x = alice.encode::().unwrap(); - let share = dealer.make_share(x, kind); + for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { + let share = dealer.make_share(alice.clone(), kind); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); assert!(handoff.needs_bivariate_share(&bob).unwrap()); - let done = handoff - .add_bivariate_share(bob.clone(), verifiable_share) - .unwrap(); + let done = handoff.add_bivariate_share(&bob, verifiable_share).unwrap(); - if i + 1 < dealers.len() { + if j + 1 < dealers.len() { // Proactivization still in progress. assert!(!done); assert!(!handoff.needs_bivariate_share(&bob).unwrap()); @@ -577,38 +553,37 @@ mod tests { } } - handoffs.insert(alice.clone(), handoff); + handoffs.push(handoff); } // Extract and verify reduced shareholders. - let mut shareholders = HashMap::new(); - for (alice, handoff) in handoffs.iter() { + let mut shareholders = Vec::with_capacity(committee.len()); + for handoff in handoffs.iter() { // Share reduction should be completed. let shareholder = handoff.get_reduced_shareholder().unwrap(); - shareholders.insert(alice.clone(), shareholder); + shareholders.push(shareholder); // Full share distribution hasn't started. assert!(handoff.get_full_shareholder().is_err()); } - verify_shareholders(&shareholders); - - for alice in committee.iter() { - let handoff = handoffs.get(alice).unwrap(); + verify_shareholders(&shareholders, threshold, false); + for (alice, handoff) in zip(committee.iter(), handoffs.iter()) { // Share distribution. let num_points = 2 * threshold as usize + 1; - for (i, (bob, shareholder)) in shareholders.iter().take(num_points).enumerate() { + for (j, shareholder) in shareholders.iter().take(num_points).enumerate() { + let bob = shareholder.verifiable_share().share.x; + assert!(handoff - .needs_full_share_distribution_switch_point(bob) + .needs_full_share_distribution_switch_point(&bob) .unwrap()); - let id = alice.encode::().unwrap(); - let bij = shareholder.switch_point(&id); + let bij = shareholder.switch_point(&alice); let done = handoff .add_full_share_distribution_switch_point(bob.clone(), bij) .unwrap(); - if i + 1 < num_points { + if j + 1 < num_points { // Accumulation still in progress. assert!(!done); assert!(!handoff @@ -625,13 +600,13 @@ mod tests { } // Extract and verify full shareholders. - let mut shareholders = HashMap::new(); - for (alice, handoff) in handoffs.iter() { + let mut shareholders = Vec::with_capacity(committee.len()); + for handoff in handoffs.iter() { // Full share distribution should be completed. let shareholder = handoff.get_full_shareholder().unwrap(); - shareholders.insert(alice.clone(), shareholder); + shareholders.push(shareholder); } - verify_shareholders(&shareholders); + verify_shareholders(&shareholders, threshold, true); } } diff --git a/secret-sharing/src/churp/player.rs b/secret-sharing/src/churp/player.rs index 3b419b95afd..f026ad1ddce 100644 --- a/secret-sharing/src/churp/player.rs +++ b/secret-sharing/src/churp/player.rs @@ -147,8 +147,8 @@ mod tests { for kind in test_cases.into_iter() { // Prepare scheme. let threshold = 2; - let key_id = b"key identifier"; - let dst = b"shamir secret sharing scheme"; + let key_id = b"key id"; + let dst = b"encode key share"; let secret = PrimeField::from_u64(100); let hash = Suite::hash_to_group(key_id, dst).unwrap(); let key = hash * secret; diff --git a/secret-sharing/src/churp/shareholder.rs b/secret-sharing/src/churp/shareholder.rs index 9b6b7b642d8..34cbd3067a2 100644 --- a/secret-sharing/src/churp/shareholder.rs +++ b/secret-sharing/src/churp/shareholder.rs @@ -14,25 +14,15 @@ use crate::{ use super::Error; -/// Domain separation tag for encoding shareholder identifiers. -const SHAREHOLDER_ENC_DST: &[u8] = b"shareholder"; +/// Encodes the given shareholder ID to a non-zero element of the prime field. +pub fn encode_shareholder(id: &[u8], dst: &[u8]) -> Result { + let s = H::hash_to_field(id, dst).map_err(|_| Error::ShareholderEncodingFailed)?; -/// Shareholder identifier. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct ShareholderId(pub [u8; 32]); - -impl ShareholderId { - /// Encodes the given shareholder ID to a non-zero element of the prime field. - pub fn encode(&self) -> Result { - let s = H::hash_to_field(&self.0[..], SHAREHOLDER_ENC_DST) - .map_err(|_| Error::ShareholderEncodingFailed)?; - - if s.is_zero().into() { - return Err(Error::ZeroValueShareholder.into()); - } - - Ok(s) + if s.is_zero().into() { + return Err(Error::ZeroValueShareholder.into()); } + + Ok(s) } /// Shareholder is responsible for deriving key shares and generating diff --git a/secret-sharing/src/churp/switch.rs b/secret-sharing/src/churp/switch.rs index a51fe84b14a..ac9e6a7b695 100644 --- a/secret-sharing/src/churp/switch.rs +++ b/secret-sharing/src/churp/switch.rs @@ -1,19 +1,14 @@ -use std::{ - collections::HashSet, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use anyhow::Result; +use group::{Group, GroupEncoding}; -use crate::{ - suites::Suite, - vss::{ - lagrange::lagrange, matrix::VerificationMatrix, polynomial::Polynomial, - vector::VerificationVector, - }, +use crate::vss::{ + lagrange::lagrange, matrix::VerificationMatrix, polynomial::Polynomial, + vector::VerificationVector, }; -use super::{Error, HandoffKind, SecretShare, Shareholder, ShareholderId, VerifiableSecretShare}; +use super::{Error, HandoffKind, SecretShare, Shareholder, VerifiableSecretShare}; /// Dimension switch kind. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -40,9 +35,9 @@ impl DimensionSwitchKind { } /// Dimension switch state. -enum DimensionSwitchState +enum DimensionSwitchState where - S: Suite, + G: Group + GroupEncoding, { /// Represents the state where the dimension switch is waiting for /// the verification matrix from the previous switch, which is needed @@ -53,7 +48,7 @@ where /// Represents the state where the switch points are being accumulated. /// Upon collection of enough points, the state transitions to the Merging /// state if proactivization is required, or directly to the Serving state. - Accumulating(SwitchPoints), + Accumulating(SwitchPoints), /// Represents the state where the dimension switch is waiting /// for a shareholder to be proactivized with bivariate shares. @@ -66,17 +61,17 @@ where /// bivariate shares. Once enough shares are collected, the shareholder /// is proactivized, and the state transitions to the Serving state. /// If no shareholder was given, the combined shares define a new one. - Merging(BivariateShares), + Merging(BivariateShares), /// Represents the state where the dimension switch is completed, /// and a new shareholder is available to serve requests. - Serving(Arc>), + Serving(Arc>), } /// A dimension switch based on a share resharing technique. -pub struct DimensionSwitch +pub struct DimensionSwitch where - S: Suite, + G: Group + GroupEncoding, { /// The degree of the secret-sharing polynomial. threshold: u8, @@ -88,25 +83,25 @@ where kind: DimensionSwitchKind, /// The encoded identity. - me: S::PrimeField, + me: G::Scalar, /// The set of shareholders from which bivariate shares need to be fetched. /// If empty, proactivization is skipped. - shareholders: HashSet, + shareholders: Vec, /// Current state of the switch. - state: Mutex>, + state: Mutex>, } -impl DimensionSwitch +impl DimensionSwitch where - S: Suite, + G: Group + GroupEncoding, { /// Creates a new share reduction dimension switch. pub(crate) fn new_share_reduction( threshold: u8, - me: ShareholderId, - shareholders: HashSet, + me: G::Scalar, + shareholders: Vec, handoff: HandoffKind, ) -> Result { let kind = DimensionSwitchKind::ShareReduction; @@ -116,8 +111,8 @@ where /// Creates a new full share distribution dimension switch. pub(crate) fn new_full_share_distribution( threshold: u8, - me: ShareholderId, - shareholders: HashSet, + me: G::Scalar, + shareholders: Vec, handoff: HandoffKind, ) -> Result { let kind = DimensionSwitchKind::FullShareDistribution; @@ -127,12 +122,11 @@ where /// Creates a new dimension switch. fn new( threshold: u8, - me: ShareholderId, - shareholders: HashSet, + me: G::Scalar, + shareholders: Vec, kind: DimensionSwitchKind, handoff: HandoffKind, ) -> Result { - let me = me.encode::()?; let state = Mutex::new(DimensionSwitchState::WaitingForVerificationMatrix); Ok(Self { @@ -165,7 +159,7 @@ where /// Starts accumulating switch points using the provided verification /// matrix for point verification. - pub(crate) fn start_accumulating(&self, vm: VerificationMatrix) -> Result<()> { + pub(crate) fn start_accumulating(&self, vm: VerificationMatrix) -> Result<()> { let mut state = self.state.lock().unwrap(); match *state { DimensionSwitchState::WaitingForVerificationMatrix => (), @@ -179,7 +173,7 @@ where } /// Checks if a switch point is required from the given shareholder. - pub(crate) fn needs_switch_point(&self, id: &ShareholderId) -> Result { + pub(crate) fn needs_switch_point(&self, x: &G::Scalar) -> Result { let state = self.state.lock().unwrap(); let sp = match &*state { DimensionSwitchState::WaitingForVerificationMatrix => return Ok(true), @@ -187,7 +181,7 @@ where _ => return Err(Error::InvalidState.into()), }; - let needs = sp.needs_point(id); + let needs = sp.needs_point(x); Ok(needs) } @@ -195,14 +189,14 @@ where /// /// Returns true if enough points have been received and the switch /// transitioned to the next state. - pub(crate) fn add_switch_point(&self, id: ShareholderId, bij: S::PrimeField) -> Result { + pub(crate) fn add_switch_point(&self, x: G::Scalar, bij: G::Scalar) -> Result { let mut state = self.state.lock().unwrap(); let sp = match &mut *state { DimensionSwitchState::Accumulating(sp) => sp, _ => return Err(Error::InvalidState.into()), }; - let done = sp.add_point(id, bij)?; + let done = sp.add_point(x, bij)?; if done { let shareholder = sp.reconstruct_shareholder()?; let shareholder = Arc::new(shareholder); @@ -233,10 +227,7 @@ where /// Starts merging bivariate shares to be used for proactivization /// of the provided shareholder. - pub(crate) fn start_merging( - &self, - shareholder: Option>>, - ) -> Result<()> { + pub(crate) fn start_merging(&self, shareholder: Option>>) -> Result<()> { let mut state = self.state.lock().unwrap(); match &*state { DimensionSwitchState::WaitingForShareholder => (), @@ -257,14 +248,14 @@ where } /// Checks if a bivariate share is needed from the given shareholder. - pub(crate) fn needs_bivariate_share(&self, id: &ShareholderId) -> Result { + pub(crate) fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { let state = self.state.lock().unwrap(); let bs = match &*state { DimensionSwitchState::Merging(bs) => bs, _ => return Err(Error::InvalidState.into()), }; - let needs = bs.needs_bivariate_share(id); + let needs = bs.needs_bivariate_share(x); Ok(needs) } @@ -274,8 +265,8 @@ where /// transitioned to the next state. pub(crate) fn add_bivariate_share( &self, - id: ShareholderId, - verifiable_share: VerifiableSecretShare, + x: &G::Scalar, + verifiable_share: VerifiableSecretShare, ) -> Result { let mut state = self.state.lock().unwrap(); let shares = match &mut *state { @@ -283,7 +274,7 @@ where _ => return Err(Error::InvalidState.into()), }; - let done = shares.add_bivariate_share(id, verifiable_share)?; + let done = shares.add_bivariate_share(x, verifiable_share)?; if done { let shareholder = shares.proactivize_shareholder()?; let shareholder = Arc::new(shareholder); @@ -294,7 +285,7 @@ where } /// Returns the shareholder if the switch has completed. - pub(crate) fn get_shareholder(&self) -> Result>> { + pub(crate) fn get_shareholder(&self) -> Result>> { let state = self.state.lock().unwrap(); let shareholder = match &*state { DimensionSwitchState::Serving(p) => p.clone(), @@ -307,16 +298,16 @@ where /// An accumulator for switch points. #[derive(Debug)] -pub struct SwitchPoints +pub struct SwitchPoints where - S: Suite, + G: Group + GroupEncoding, { /// The minimum number of distinct points required to reconstruct /// the polynomial. n: usize, /// Field element representing the identity of the shareholder. - me: Option, + me: Option, /// The verification matrix for the bivariate polynomial of the source /// committee from the previous handoff. @@ -324,7 +315,7 @@ where /// It is used to validate incoming switch points `B(node_id, me)` /// or `B(me, node_id)` during the share reduction or full share /// distribution phase. - vm: Option>, + vm: Option>, /// The verification vector, derived from the verification matrix, /// is used to efficiently validate switch points. @@ -332,28 +323,25 @@ where /// The vector can verify switch points from univariate polynomials /// `B(x, me)` or `B(me, y)` during the share reduction or full share /// distribution phase. - vv: VerificationVector, - - /// A set of shareholders whose points have been received. - shareholders: HashSet, + vv: VerificationVector, /// A list of encoded shareholders' identities whose points have been /// received. - xs: Vec, + xs: Vec, /// A list of received switch points. - bijs: Vec, + bijs: Vec, } -impl SwitchPoints +impl SwitchPoints where - S: Suite, + G: Group + GroupEncoding, { /// Creates a new accumulator for switch points. fn new( threshold: u8, - me: S::PrimeField, - vm: VerificationMatrix, + me: G::Scalar, + vm: VerificationMatrix, kind: DimensionSwitchKind, ) -> Result { let threshold = threshold as usize; @@ -385,7 +373,6 @@ where let vm = Some(vm); // We need at least n points to reconstruct the polynomial share. - let shareholders = HashSet::with_capacity(n); let xs = Vec::with_capacity(n); let bijs = Vec::with_capacity(n); @@ -394,29 +381,28 @@ where me, vm, vv, - shareholders, xs, bijs, }) } /// Checks if a switch point is required from the given shareholder. - fn needs_point(&self, id: &ShareholderId) -> bool { - if self.shareholders.len() >= self.n { + fn needs_point(&self, x: &G::Scalar) -> bool { + if self.xs.len() >= self.n { return false; } - !self.shareholders.contains(id) + !self.xs.contains(x) } /// Verifies and adds the given switch point. /// /// Returns true if enough points have been received; otherwise, /// it returns false. - fn add_point(&mut self, id: ShareholderId, bij: S::PrimeField) -> Result { - if self.shareholders.len() >= self.n { + fn add_point(&mut self, x: G::Scalar, bij: G::Scalar) -> Result { + if self.xs.len() >= self.n { return Err(Error::TooManySwitchPoints.into()); } - if self.shareholders.contains(&id) { + if self.xs.contains(&x) { return Err(Error::DuplicateShareholder.into()); } @@ -424,16 +410,14 @@ where // If the point is valid, it doesn't matter if it came from a stranger. // However, since verification is costly, one could check if the point // came from a legitimate shareholder. - let x = id.encode::()?; if !self.vv.verify(&x, &bij) { return Err(Error::InvalidSwitchPoint.into()); } self.xs.push(x); self.bijs.push(bij); - self.shareholders.insert(id); - let done = self.shareholders.len() >= self.n; + let done = self.xs.len() >= self.n; Ok(done) } @@ -442,8 +426,8 @@ where /// /// The shareholder can be reconstructed only once, which avoids copying /// the verification matrix. - fn reconstruct_shareholder(&mut self) -> Result> { - if self.shareholders.len() < self.n { + fn reconstruct_shareholder(&mut self) -> Result> { + if self.xs.len() < self.n { return Err(Error::NotEnoughSwitchPoints.into()); } @@ -465,15 +449,15 @@ where } /// An accumulator for bivariate shares. -struct BivariateShares +struct BivariateShares where - S: Suite, + G: Group + GroupEncoding, { /// The degree of the secret-sharing polynomial. threshold: u8, /// Field element representing the identity of the shareholder. - me: S::PrimeField, + me: G::Scalar, /// Indicates whether bivariate shares should be derived from a zero-hole /// bivariate polynomial. @@ -483,32 +467,32 @@ where full_share: bool, /// A set of shareholders providing bivariate shares. - shareholders: HashSet, + shareholders: Vec, /// A set of shareholders whose bivariate share still needs to be received. - pending_shareholders: HashSet, + pending_shareholders: Vec, /// The sum of the received bivariate shares. - p: Option>, + p: Option>, /// The sum of the verification matrices of the received bivariate shares. - vm: Option>, + vm: Option>, /// The shareholder to be proactivized with bivariate shares. - shareholder: Option>>, + shareholder: Option>>, } -impl BivariateShares +impl BivariateShares where - S: Suite, + G: Group + GroupEncoding, { /// Creates a new accumulator for bivariate shares. fn new( threshold: u8, - me: S::PrimeField, - shareholders: HashSet, + me: G::Scalar, + shareholders: Vec, kind: DimensionSwitchKind, handoff: HandoffKind, - shareholder: Option>>, + shareholder: Option>>, ) -> Result { // During the dealing phase, the number of shares must be at least // threshold + 2, ensuring that even if t Byzantine dealers reveal @@ -540,9 +524,14 @@ where }) } + /// Checks if a bivariate share can be received from the given shareholder. + fn has_bivariate_share(&self, x: &G::Scalar) -> bool { + self.shareholders.contains(x) + } + /// Checks if a bivariate share is needed from the given shareholder. - fn needs_bivariate_share(&self, id: &ShareholderId) -> bool { - self.pending_shareholders.contains(id) + fn needs_bivariate_share(&self, x: &G::Scalar) -> bool { + self.pending_shareholders.contains(x) } /// Verifies and adds the given bivariate share. @@ -551,13 +540,13 @@ where /// it returns false. fn add_bivariate_share( &mut self, - id: ShareholderId, - verifiable_share: VerifiableSecretShare, + x: &G::Scalar, + verifiable_share: VerifiableSecretShare, ) -> Result { - if !self.shareholders.contains(&id) { + if !self.has_bivariate_share(x) { return Err(Error::UnknownShareholder.into()); } - if !self.pending_shareholders.contains(&id) { + if !self.needs_bivariate_share(x) { return Err(Error::DuplicateShareholder.into()); } @@ -578,7 +567,13 @@ where }; self.vm = Some(vm); - self.pending_shareholders.remove(&id); + let index = self + .pending_shareholders + .iter() + .position(|y| y == x) + .unwrap(); + self.pending_shareholders.swap_remove(index); + let done = self.pending_shareholders.is_empty(); Ok(done) @@ -586,7 +581,7 @@ where /// Proactivizes the shareholder with the combined polynomial /// and verification matrix. - fn proactivize_shareholder(&mut self) -> Result> { + fn proactivize_shareholder(&mut self) -> Result> { if !self.pending_shareholders.is_empty() { return Err(Error::NotEnoughBivariateShares.into()); } @@ -610,13 +605,11 @@ where #[cfg(test)] mod tests { - use std::collections::HashSet; - use anyhow::Result; use rand::{rngs::StdRng, SeedableRng}; use crate::{ - churp::{HandoffKind, SecretShare, ShareholderId, VerifiableSecretShare}, + churp::{HandoffKind, SecretShare, VerifiableSecretShare}, suites::{self, p384}, vss::{matrix, polynomial}, }; @@ -624,34 +617,34 @@ mod tests { use super::{BivariateShares, DimensionSwitchKind, Error, SwitchPoints}; type Suite = p384::Sha3_384; + type Group = ::Group; + type PrimeField = ::PrimeField; type BivariatePolynomial = polynomial::BivariatePolynomial<::PrimeField>; type VerificationMatrix = matrix::VerificationMatrix<::Group>; - fn shareholder(id: u8) -> ShareholderId { - ShareholderId([id; 32]) + fn prepare_shareholder(id: u64) -> PrimeField { + id.into() } - fn shareholders(ids: Vec) -> Vec { - ids.into_iter().map(shareholder).collect() + fn prepare_shareholders(ids: &[u64]) -> Vec { + ids.into_iter().map(|&id| id.into()).collect() } fn add_point( - me: u8, - sh: u8, + me: u64, + sh: u64, bp: &BivariatePolynomial, - sp: &mut SwitchPoints, + sp: &mut SwitchPoints, kind: DimensionSwitchKind, ) -> Result { - let me = shareholder(me); - let sh = shareholder(sh); - let x = sh.encode::().unwrap(); - let y = me.encode::().unwrap(); + let x = prepare_shareholder(sh); + let y = prepare_shareholder(me); let bij = match kind { DimensionSwitchKind::ShareReduction => bp.eval(&x, &y), DimensionSwitchKind::FullShareDistribution => bp.eval(&y, &x), }; - let res = sp.add_point(sh, bij); + let res = sp.add_point(x, bij); res } @@ -664,13 +657,13 @@ mod tests { let deg_y = 2 * threshold; let bp = BivariatePolynomial::random(deg_x, deg_y, &mut rng); let vm = VerificationMatrix::from(&bp); - let me = shareholder(1).encode::().unwrap(); + let me = prepare_shareholder(1); for kind in vec![ DimensionSwitchKind::ShareReduction, DimensionSwitchKind::FullShareDistribution, ] { - let mut sp = SwitchPoints::::new(threshold, me, vm.clone(), kind).unwrap(); + let mut sp = SwitchPoints::::new(threshold, me, vm.clone(), kind).unwrap(); let me = 1; let mut sh = 2; @@ -688,7 +681,7 @@ mod tests { assert!(!res.unwrap()); // Add another point twice. - assert!(sp.needs_point(&shareholder(sh))); + assert!(sp.needs_point(&prepare_shareholder(sh))); let res = add_point(me, sh, &bp, &mut sp, kind); assert!(res.is_ok()); @@ -701,7 +694,7 @@ mod tests { Error::DuplicateShareholder.to_string() ); - assert!(!sp.needs_point(&shareholder(sh))); + assert!(!sp.needs_point(&prepare_shareholder(sh))); sh += 1; // Try to reconstruct the polynomial. @@ -731,7 +724,7 @@ mod tests { sh += 1; // No more points needed. - assert!(!sp.needs_point(&shareholder(sh))); + assert!(!sp.needs_point(&prepare_shareholder(sh))); // Too many points. let res = add_point(me, sh, &bp, &mut sp, kind); @@ -749,9 +742,9 @@ mod tests { fn add_bivariate_shares( threshold: u8, - me: u8, - sh: u8, - bs: &mut BivariateShares, + me: u64, + sh: u64, + bs: &mut BivariateShares, dkind: DimensionSwitchKind, hkind: HandoffKind, ) -> Result { @@ -763,16 +756,15 @@ mod tests { bp.to_zero_hole(); }; let vm = VerificationMatrix::from(&bp); - let me = shareholder(me); - let sh = shareholder(sh); - let x = me.encode::().unwrap(); + let x = prepare_shareholder(me); let p = match dkind { DimensionSwitchKind::ShareReduction => bp.eval_y(&x), DimensionSwitchKind::FullShareDistribution => bp.eval_x(&x), }; let share = SecretShare::new(x, p); let verifiable_share = VerifiableSecretShare::new(share, vm); - bs.add_bivariate_share(sh, verifiable_share) + let x = prepare_shareholder(sh); + bs.add_bivariate_share(&x, verifiable_share) } #[test] @@ -780,12 +772,11 @@ mod tests { let threshold = 2; let hkind = HandoffKind::CommitteeChanged; - let me = shareholder(1).encode::().unwrap(); - let shs = shareholders(vec![1, 2, 3]); - let shareholders: HashSet = shs.iter().cloned().collect(); + let me = prepare_shareholder(1); + let shareholders = prepare_shareholders(&[1, 2, 3]); // Dealing phase requires at least threshold + 2 dealers. - let res = BivariateShares::::new( + let res = BivariateShares::::new( threshold, me, shareholders.clone(), @@ -806,7 +797,7 @@ mod tests { DimensionSwitchKind::ShareReduction, DimensionSwitchKind::FullShareDistribution, ] { - let mut bs = BivariateShares::::new( + let mut bs = BivariateShares::::new( threshold, me, shareholders.clone(), @@ -833,7 +824,7 @@ mod tests { assert!(!res.unwrap()); // Add another share twice. - assert!(bs.needs_bivariate_share(&shareholder(sh))); + assert!(bs.needs_bivariate_share(&prepare_shareholder(sh))); let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); assert!(res.is_ok()); @@ -846,7 +837,7 @@ mod tests { Error::DuplicateShareholder.to_string() ); - assert!(!bs.needs_bivariate_share(&shareholder(sh))); + assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); sh += 1; // Try to collect the polynomial and verification matrix. @@ -866,7 +857,7 @@ mod tests { sh += 1; // Unknown shareholder. - assert!(!bs.needs_bivariate_share(&shareholder(sh))); + assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); assert!(res.is_err()); diff --git a/secret-sharing/src/shamir/player.rs b/secret-sharing/src/shamir/player.rs index a3406afdbd8..c1540e58327 100644 --- a/secret-sharing/src/shamir/player.rs +++ b/secret-sharing/src/shamir/player.rs @@ -125,8 +125,8 @@ mod tests { fn test_kdc() { // Prepare scheme. let threshold = 2; - let key_id = b"key identifier"; - let dst = b"shamir secret sharing scheme"; + let key_id = b"key id"; + let dst = b"encode key share"; let secret = PrimeField::from_u64(100); let hash = Suite::hash_to_group(key_id, dst).unwrap(); let key = hash * secret; diff --git a/secret-sharing/src/suites/p384.rs b/secret-sharing/src/suites/p384.rs index 1b32f24eb2d..f09aedca927 100644 --- a/secret-sharing/src/suites/p384.rs +++ b/secret-sharing/src/suites/p384.rs @@ -7,10 +7,6 @@ use p384::{ use super::{FieldDigest, GroupDigest}; -/// Domain separation tag for encoding to NIST P-384 prime field or curve -/// using the SHA3-384 hash function. -const NIST_P384_SHA3_384_ENC_DST: &[u8] = b"P384_XMD:SHA3-384_SSWU_RO_"; - /// The NIST P-384 elliptic curve group with the SHA3-384 hash function used /// to encode arbitrary-length byte strings to elements of the underlying prime /// field or elliptic curve points. @@ -21,7 +17,7 @@ impl GroupDigest for Sha3_384 { fn hash_to_group(msg: &[u8], dst: &[u8]) -> Result { let msgs = [msg]; - let dsts = [NIST_P384_SHA3_384_ENC_DST, dst]; + let dsts = [dst]; let p = NistP384::hash_from_bytes::>(&msgs, &dsts)?; Ok(p) } @@ -32,7 +28,7 @@ impl FieldDigest for Sha3_384 { fn hash_to_field(msg: &[u8], dst: &[u8]) -> Result { let msgs = [msg]; - let dsts = [NIST_P384_SHA3_384_ENC_DST, dst]; + let dsts = [dst]; let s = NistP384::hash_to_scalar::>(&msgs, &dsts)?; Ok(s) } @@ -55,7 +51,7 @@ mod tests { b.iter(|| { rng.fill_bytes(&mut data); - let _ = Sha3_384::hash_to_field(&data[..32], &data[32..]).unwrap(); + let _ = Sha3_384::hash_to_field(&data[..32], &data[32..64]).unwrap(); }); } @@ -66,7 +62,7 @@ mod tests { b.iter(|| { rng.fill_bytes(&mut data); - let _ = Sha3_384::hash_to_group(&data[..32], &data[32..]).unwrap(); + let _ = Sha3_384::hash_to_group(&data[..32], &data[32..64]).unwrap(); }); } } diff --git a/secret-sharing/src/vss/polynomial/point.rs b/secret-sharing/src/vss/polynomial/point.rs index bf7883c3198..0dfeb0fb1c5 100644 --- a/secret-sharing/src/vss/polynomial/point.rs +++ b/secret-sharing/src/vss/polynomial/point.rs @@ -32,4 +32,14 @@ impl EncryptedPoint { pub fn new(x: G::Scalar, z: G) -> Self { Self { x, z } } + + /// Returns the x-coordinate of the point. + pub fn x(&self) -> &G::Scalar { + &self.x + } + + /// Returns the y-coordinate of the point in encrypted form. + pub fn z(&self) -> &G { + &self.z + } } diff --git a/tests/runtimes/simple-keyvalue/src/main.rs b/tests/runtimes/simple-keyvalue/src/main.rs index 326f8bb627b..7964b39593a 100644 --- a/tests/runtimes/simple-keyvalue/src/main.rs +++ b/tests/runtimes/simple-keyvalue/src/main.rs @@ -148,9 +148,12 @@ impl Dispatcher { "insert" => Self::dispatch_call(ctx, tx.args, Methods::insert), "get" => Self::dispatch_call(ctx, tx.args, Methods::get), "remove" => Self::dispatch_call(ctx, tx.args, Methods::remove), - "enc_insert" => Self::dispatch_call(ctx, tx.args, Methods::enc_insert), - "enc_get" => Self::dispatch_call(ctx, tx.args, Methods::enc_get), - "enc_remove" => Self::dispatch_call(ctx, tx.args, Methods::enc_remove), + "enc_insert" => Self::dispatch_call(ctx, tx.args, Methods::enc_insert_using_secrets), + "enc_get" => Self::dispatch_call(ctx, tx.args, Methods::enc_get_using_secrets), + "enc_remove" => Self::dispatch_call(ctx, tx.args, Methods::enc_remove_using_secrets), + "churp_insert" => Self::dispatch_call(ctx, tx.args, Methods::enc_insert_using_churp), + "churp_get" => Self::dispatch_call(ctx, tx.args, Methods::enc_get_using_churp), + "churp_remove" => Self::dispatch_call(ctx, tx.args, Methods::enc_remove_using_churp), "encrypt" => Self::dispatch_call(ctx, tx.args, Methods::encrypt), "decrypt" => Self::dispatch_call(ctx, tx.args, Methods::decrypt), _ => Err("method not found".to_string()), diff --git a/tests/runtimes/simple-keyvalue/src/methods.rs b/tests/runtimes/simple-keyvalue/src/methods.rs index 3b6422e280a..d1558c137c2 100644 --- a/tests/runtimes/simple-keyvalue/src/methods.rs +++ b/tests/runtimes/simple-keyvalue/src/methods.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeMap, convert::TryInto}; use super::{crypto::EncryptionContext, types::*, Context, TxContext}; -use oasis_core_keymanager::crypto::KeyPairId; +use oasis_core_keymanager::crypto::{KeyPairId, StateKey}; use oasis_core_runtime::{ common::{ crypto::{ @@ -199,7 +199,7 @@ impl Methods { } /// Insert a key/value pair. - pub fn insert(ctx: &mut TxContext, args: KeyValue) -> Result, String> { + pub fn insert(ctx: &mut TxContext, args: Insert) -> Result, String> { if args.value.as_bytes().len() > 128 { return Err("Value too big to be inserted.".to_string()); } @@ -224,7 +224,7 @@ impl Methods { } /// Retrieve a key/value pair. - pub fn get(ctx: &mut TxContext, args: Key) -> Result, String> { + pub fn get(ctx: &mut TxContext, args: Get) -> Result, String> { if ctx.is_check_only() { return Ok(None); } @@ -239,7 +239,7 @@ impl Methods { } /// Remove a key/value pair. - pub fn remove(ctx: &mut TxContext, args: Key) -> Result, String> { + pub fn remove(ctx: &mut TxContext, args: Remove) -> Result, String> { if ctx.is_check_only() { return Ok(None); } @@ -253,41 +253,57 @@ impl Methods { .map_err(|err| err.to_string()) } - /// Helper for doing encrypted MKVS operations. - fn get_encryption_context( + /// Fetches state key derived from a master secret. + fn state_key_from_secrets( ctx: &mut TxContext, key: &[u8], generation: u64, - ) -> Result { + ) -> Result { // Derive key pair ID based on key. let key_pair_id = KeyPairId::from(Hash::digest_bytes(key).as_ref()); // Fetch encryption keys. - let result = ctx + let future = ctx .parent .key_manager .get_or_create_keys(key_pair_id, generation); - let key = tokio::runtime::Handle::current() - .block_on(result) - .map_err(|err| err.to_string())?; + let key = block_on(future).map_err(|err| err.to_string())?; - Ok(EncryptionContext::new(key.state_key.as_ref())) + Ok(key.state_key) } - /// (encrypted) Insert a key/value pair. - pub fn enc_insert(ctx: &mut TxContext, args: KeyValue) -> Result, String> { - if ctx.is_check_only() { - return Ok(None); - } + /// Fetches state key derived from a CHURP secret. + fn state_key_from_churp( + ctx: &mut TxContext, + key: &[u8], + churp_id: u8, + ) -> Result { + // Derive key ID based on key. + let key_id = KeyPairId::from(Hash::digest_bytes(key).as_ref()); + + // Fetch encryption key. + let future = ctx.parent.key_manager.churp_state_key(churp_id, key_id); + let state_key = block_on(future).map_err(|err| err.to_string())?; + + Ok(state_key) + } + + /// Encrypts a key/value pair and inserts it into the runtime state. + fn enc_insert( + ctx: &mut TxContext, + key: String, + value: String, + state_key: StateKey, + ) -> Result, String> { // NOTE: This is only for example purposes, the correct way would be // to also generate a (deterministic) nonce. let nonce = [0u8; NONCE_SIZE]; - let enc_ctx = Self::get_encryption_context(ctx, args.key.as_bytes(), args.generation)?; + let enc_ctx = EncryptionContext::new(state_key.as_ref()); let existing = enc_ctx.insert( ctx.parent.core.runtime_state, - args.key.as_bytes(), - args.value.as_bytes(), + key.as_bytes(), + value.as_bytes(), &nonce, ); existing @@ -296,32 +312,112 @@ impl Methods { .map_err(|err| err.to_string()) } - /// (encrypted) Retrieve a key/value pair. - pub fn enc_get(ctx: &mut TxContext, args: Key) -> Result, String> { - if ctx.is_check_only() { - return Ok(None); - } - let enc_ctx = Self::get_encryption_context(ctx, args.key.as_bytes(), args.generation)?; - let existing = enc_ctx.get(ctx.parent.core.runtime_state, args.key.as_bytes()); + /// Retrieves and decrypts a key/value from the runtime state. + fn enc_get( + ctx: &mut TxContext, + key: String, + state_key: StateKey, + ) -> Result, String> { + let enc_ctx = EncryptionContext::new(state_key.as_ref()); + let existing = enc_ctx.get(ctx.parent.core.runtime_state, key.as_bytes()); existing .map(String::from_utf8) .transpose() .map_err(|err| err.to_string()) } - /// (encrypted) Remove a key/value pair. - pub fn enc_remove(ctx: &mut TxContext, args: Key) -> Result, String> { - if ctx.is_check_only() { - return Ok(None); - } - let enc_ctx = Self::get_encryption_context(ctx, args.key.as_bytes(), args.generation)?; - let existing = enc_ctx.remove(ctx.parent.core.runtime_state, args.key.as_bytes()); + /// Removes encrypted key/value from the runtime state. + fn enc_remove( + ctx: &mut TxContext, + key: String, + state_key: StateKey, + ) -> Result, String> { + let enc_ctx = EncryptionContext::new(state_key.as_ref()); + let existing = enc_ctx.remove(ctx.parent.core.runtime_state, key.as_bytes()); existing .map(String::from_utf8) .transpose() .map_err(|err| err.to_string()) } + /// Encrypts a key/value pair and inserts it into the runtime state + /// using master secrets. + pub fn enc_insert_using_secrets( + ctx: &mut TxContext, + args: Insert, + ) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_secrets(ctx, args.key.as_bytes(), args.generation)?; + Self::enc_insert(ctx, args.key, args.value, state_key) + } + + /// Retrieves and decrypts a key/value pair from the runtime state + /// using master secrets. + pub fn enc_get_using_secrets(ctx: &mut TxContext, args: Get) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_secrets(ctx, args.key.as_bytes(), args.generation)?; + Self::enc_get(ctx, args.key, state_key) + } + + /// Removes encrypted key/value pair from the runtime state + /// using master secrets. + pub fn enc_remove_using_secrets( + ctx: &mut TxContext, + args: Remove, + ) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_secrets(ctx, args.key.as_bytes(), args.generation)?; + Self::enc_remove(ctx, args.key, state_key) + } + + /// Encrypts a key/value pair and inserts it into the runtime state + /// using CHURP secrets. + pub fn enc_insert_using_churp( + ctx: &mut TxContext, + args: Insert, + ) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_churp(ctx, args.key.as_bytes(), args.churp_id)?; + Self::enc_insert(ctx, args.key, args.value, state_key) + } + + /// Retrieves and decrypts a key/value from the runtime state + /// using CHURP secrets. + pub fn enc_get_using_churp(ctx: &mut TxContext, args: Get) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_churp(ctx, args.key.as_bytes(), args.churp_id)?; + Self::enc_get(ctx, args.key, state_key) + } + + /// Removes encrypted key/value from the runtime state + /// using CHURP secret. + pub fn enc_remove_using_churp( + ctx: &mut TxContext, + args: Remove, + ) -> Result, String> { + if ctx.is_check_only() { + return Ok(None); + } + + let state_key = Self::state_key_from_churp(ctx, args.key.as_bytes(), args.churp_id)?; + Self::enc_remove(ctx, args.key, state_key) + } + /// ElGamal encryption. pub fn encrypt(ctx: &mut TxContext, args: Encrypt) -> Result>, String> { if ctx.is_check_only() { @@ -333,13 +429,11 @@ impl Methods { let key_pair_id = KeyPairId::from(hash.as_ref()); // Fetch public key. - let result = ctx + let future = ctx .parent .key_manager .get_public_ephemeral_key(key_pair_id, args.epoch); - let long_term_pk = tokio::runtime::Handle::current() - .block_on(result) - .map_err(|err| err.to_string())?; + let long_term_pk = block_on(future).map_err(|err| err.to_string())?; // Generate ephemeral key. Not secure, but good enough for testing purposes. let ephemeral_sk = x25519::PrivateKey::from(hash); @@ -373,12 +467,11 @@ impl Methods { let key_pair_id = KeyPairId::from(hash.as_ref()); // Fetch private key. - let result = ctx + let future = ctx .parent .key_manager .get_or_create_ephemeral_keys(key_pair_id, args.epoch); - let long_term_sk = tokio::runtime::Handle::current() - .block_on(result) + let long_term_sk = block_on(future) .map_err(|err| format!("private ephemeral key not available: {err}"))?; // Decode ephemeral_pk || ciphertext. diff --git a/tests/runtimes/simple-keyvalue/src/types.rs b/tests/runtimes/simple-keyvalue/src/types.rs index 6fa5ec5fc68..2604509766c 100644 --- a/tests/runtimes/simple-keyvalue/src/types.rs +++ b/tests/runtimes/simple-keyvalue/src/types.rs @@ -29,19 +29,32 @@ pub enum CallOutput { Error(String), } +/// Get key-value pair call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] -pub struct Key { +pub struct Get { pub key: String, pub generation: u64, + pub churp_id: u8, } +/// Remove key-value pair call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] -pub struct KeyValue { +pub struct Remove { + pub key: String, + pub generation: u64, + pub churp_id: u8, +} + +/// Insert key-value pair call. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct Insert { pub key: String, pub value: String, pub generation: u64, + pub churp_id: u8, } +/// Encrypt plaintext call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct Encrypt { pub epoch: u64, @@ -49,6 +62,7 @@ pub struct Encrypt { pub plaintext: Vec, } +/// Decrypt plaintext call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct Decrypt { pub epoch: u64, @@ -56,26 +70,31 @@ pub struct Decrypt { pub ciphertext: Vec, } +/// Withdraw call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct Withdraw { pub withdraw: staking::Withdraw, } +/// Transfer call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct Transfer { pub transfer: staking::Transfer, } +/// Add escrow call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct AddEscrow { pub escrow: staking::Escrow, } +/// Reclaim escrow call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct ReclaimEscrow { pub reclaim_escrow: staking::ReclaimEscrow, } +/// Update runtime call. #[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] pub struct UpdateRuntime { pub update_runtime: registry::Runtime,