Skip to content

Commit

Permalink
Merge pull request #5568 from oasisprotocol/peternose/feature/churp-a…
Browse files Browse the repository at this point in the history
…pply

go/keymanager/churp: Allow nodes to apply for a new committee
  • Loading branch information
peternose authored Feb 24, 2024
2 parents d309216 + 2a1baa1 commit b0e2b95
Show file tree
Hide file tree
Showing 28 changed files with 975 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .changelog/5551.feature.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
go/keymanager/churp: Add create and update methods
go/keymanager/churp: Allow key managers to create/update scheme
1 change: 1 addition & 0 deletions .changelog/5568.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/keymanager/churp: Allow nodes to apply for a new committee
5 changes: 3 additions & 2 deletions go/consensus/cometbft/apps/keymanager/churp/epoch.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ func (ext *churpExt) onEpochChange(ctx *tmapi.Context, epoch beacon.EpochTime) e
continue
}

// The handoff failed, so postpone the round to the next epoch, giving
// nodes one epoch time to submit applications.
// The handoff failed. Start another round in the next epoch,
// giving nodes one epoch time to submit applications.
status.Applications = nil
status.Checksum = nil
status.NextHandoff = epoch + 1
status.Round++

if err := state.SetStatus(ctx, status); err != nil {
ctx.Logger().Error("keymanager: churp: failed to set status",
Expand Down
6 changes: 6 additions & 0 deletions go/consensus/cometbft/apps/keymanager/churp/ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ func (ext *churpExt) ExecuteTx(ctx *tmapi.Context, tx *transaction.Transaction)
return api.ErrInvalidArgument
}
return ext.update(ctx, &cfg)
case churp.MethodApply:
var reg churp.SignedApplicationRequest
if err := cbor.Unmarshal(tx.Body, &reg); err != nil {
return api.ErrInvalidArgument
}
return ext.apply(ctx, &reg)
default:
panic(fmt.Sprintf("keymanager: churp: invalid method: %s", tx.Method))
}
Expand Down
36 changes: 36 additions & 0 deletions go/consensus/cometbft/apps/keymanager/churp/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package churp

import (
"context"

"github.com/oasisprotocol/oasis-core/go/common"
churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state"
"github.com/oasisprotocol/oasis-core/go/keymanager/churp"
)

// Query is the key manager query interface.
type Query interface {
Status(context.Context, common.Namespace, uint8) (*churp.Status, error)
Statuses(context.Context, common.Namespace) ([]*churp.Status, error)
AllStatuses(context.Context) ([]*churp.Status, error)
}

type querier struct {
state *churpState.ImmutableState
}

func (kq *querier) Status(ctx context.Context, runtimeID common.Namespace, churpID uint8) (*churp.Status, error) {
return kq.state.Status(ctx, runtimeID, churpID)
}

func (kq *querier) Statuses(ctx context.Context, runtimeID common.Namespace) ([]*churp.Status, error) {
return kq.state.Statuses(ctx, runtimeID)
}

func (kq *querier) AllStatuses(ctx context.Context) ([]*churp.Status, error) {
return kq.state.AllStatuses(ctx)
}

func NewQuery(state *churpState.ImmutableState) Query {
return &querier{state}
}
122 changes: 122 additions & 0 deletions go/consensus/cometbft/apps/keymanager/churp/state/interop/interop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package interop

import (
"context"
"fmt"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory"
"github.com/oasisprotocol/oasis-core/go/common/sgx"
churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state"
"github.com/oasisprotocol/oasis-core/go/keymanager/churp"
"github.com/oasisprotocol/oasis-core/go/keymanager/secrets"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
)

// InitializeTestKeyManagerSecretsState must be kept in sync with tests in runtimes/consensus/state/keymanager/churp.rs.
func InitializeTestKeyManagerSecretsState(ctx context.Context, mkvs mkvs.Tree) error {
state := churpState.NewMutableState(mkvs)

// One runtime.
var runtime common.Namespace
if err := runtime.UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000000"); err != nil {
return err
}

// Two enclave identities.
var enclave1, enclave2 sgx.EnclaveIdentity
if err := enclave1.MrEnclave.UnmarshalHex("c9a589851b1f35627177fd70378ed778170f737611e4dfbf0b6d25bdff55b474"); err != nil {
return err
}
if err := enclave1.MrSigner.UnmarshalHex("7d310664780931ae103ab30a90171c201af385a72757bb4683578fdebde9adf5"); err != nil {
return err
}
if err := enclave2.MrEnclave.UnmarshalHex("756eaf76f5482c5345808b1eaccdd5c60f864bb2aa2d2b870df00ce435af4e23"); err != nil {
return err
}
if err := enclave2.MrSigner.UnmarshalHex("3597a2ff0743016f28e5d7e129304ee1c43dbdae3dba94e19cee3549038a5a32"); err != nil {
return err
}

// CHURP identity.
identity := churp.Identity{
ID: 1,
RuntimeID: runtime,
}

// Signed policy.
policy := churp.PolicySGX{
Identity: identity,
Serial: 6,
MayShare: []sgx.EnclaveIdentity{enclave1},
MayJoin: []sgx.EnclaveIdentity{enclave2},
}
sigPolicy := churp.SignedPolicySGX{
Policy: policy,
Signatures: []signature.Signature{},
}

// Two signers.
signers := []signature.Signer{
memorySigner.NewTestSigner("first signer"),
memorySigner.NewTestSigner("second signer"),
}

for _, signer := range signers {
sig, err := signature.Sign(signer, secrets.PolicySGXSignatureContext, cbor.Marshal(policy))
if err != nil {
return fmt.Errorf("failed to sign policy: %w", err)
}
sigPolicy.Signatures = append(sigPolicy.Signatures, *sig)
}

// Random checksum.
var checksum hash.Hash
if err := checksum.UnmarshalHex("1bff211fae98c88ba82388ae954b88a71d3bbe327e162e9fa711fe7a1b759c3e"); err != nil {
return err
}

// Committee.
committee := []signature.PublicKey{signers[0].Public(), signers[1].Public()}

// Applications.
applications := map[signature.PublicKey]churp.Application{
signers[0].Public(): {
Checksum: checksum,
Reconstructed: false,
},
signers[1].Public(): {
Checksum: checksum,
Reconstructed: true,
},
}

// Empty status.
var status churp.Status
if err := state.SetStatus(ctx, &status); err != nil {
return fmt.Errorf("failed to set key CHURP status: %w", err)
}

// Non-empty status.
status = churp.Status{
Identity: identity,
GroupID: churp.EccNistP384,
Threshold: 2,
Round: 3,
NextHandoff: 4,
HandoffInterval: 5,
Policy: sigPolicy,
Committee: committee,
Applications: applications,
Checksum: &checksum,
}

if err := state.SetStatus(ctx, &status); err != nil {
return fmt.Errorf("failed to set key CHURP status: %w", err)
}

return nil
}
111 changes: 111 additions & 0 deletions go/consensus/cometbft/apps/keymanager/churp/txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"fmt"

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/node"
tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state"
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/common"
registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state"
"github.com/oasisprotocol/oasis-core/go/keymanager/churp"
)

Expand Down Expand Up @@ -192,6 +195,114 @@ func (ext *churpExt) update(ctx *tmapi.Context, req *churp.UpdateRequest) error
return nil
}

func (ext *churpExt) apply(ctx *tmapi.Context, req *churp.SignedApplicationRequest) error {
// Prepare states.
state := churpState.NewMutableState(ctx.State())
regState := registryState.NewMutableState(ctx.State())

// Ensure that the runtime exists and is a key manager.
kmRt, err := common.KeyManagerRuntime(ctx, req.Application.RuntimeID)
if err != nil {
return err
}

// Get the existing status.
status, err := state.Status(ctx, req.Application.RuntimeID, req.Application.ID)
if err != nil {
return fmt.Errorf("keymanager: churp: non-existing ID: %d", req.Application.ID)
}

// Allow applications one epoch before the next handoff.
now, err := ext.state.GetCurrentEpoch(ctx)
if err != nil {
return err
}

switch status.NextHandoff {
case churp.HandoffsDisabled:
return fmt.Errorf("keymanager: churp: handoffs disabled")
case now + 1:
default:
return fmt.Errorf("keymanager: churp: submissions closed")
}

if status.Round != req.Application.Round {
return fmt.Errorf("keymanager: churp: invalid round: got %d, expected %d", req.Application.Round, status.Round)
}

// Allow only one application per round, to ensure the node's
// verification matrix (commitment) doesn't change.
nodeID := ctx.TxSigner()
if _, ok := status.Applications[nodeID]; ok {
return fmt.Errorf("keymanager: churp: application already submitted")
}

// Verify the node.
n, err := regState.Node(ctx, nodeID)
if err != nil {
return err
}
if n.IsExpired(uint64(now)) {
return fmt.Errorf("keymanager: churp: node registration expired")
}
if !n.HasRoles(node.RoleKeyManager) {
return fmt.Errorf("keymanager: churp: node not key manager")
}

// Verify RAK signature.
nodeRt, err := common.NodeRuntime(n, kmRt.ID)
if err != nil {
return err
}
rak, err := common.RuntimeAttestationKey(nodeRt, kmRt)
if err != nil {
return fmt.Errorf("keymanager: churp: failed to fetch node's rak: %w", err)
}
if err = req.VerifyRAK(rak); err != nil {
return fmt.Errorf("keymanager: churp: invalid signature: %w", err)
}

if ctx.IsCheckOnly() {
return nil
}

// Charge gas for this operation.
kmParams, err := state.ConsensusParameters(ctx)
if err != nil {
return err
}
if err = ctx.Gas().UseGas(1, churp.GasOpApply, kmParams.GasCosts); err != nil {
return err
}

// Return early if simulating since this is just estimating gas.
if ctx.IsSimulation() {
return nil
}

// Ok, as far as we can tell the application is valid, apply it.
if status.Applications == nil {
status.Applications = make(map[signature.PublicKey]churp.Application)
}
status.Applications[nodeID] = churp.Application{
Checksum: req.Application.Checksum,
Reconstructed: false,
}

if err := state.SetStatus(ctx, status); err != nil {
ctx.Logger().Error("keymanager: churp: failed to set status",
"err", err,
)
return fmt.Errorf("keymanager: churp: failed to set status: %w", err)
}

ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&churp.UpdateEvent{
Status: status,
}))

return nil
}

func (ext *churpExt) computeNextHandoff(ctx *tmapi.Context) (beacon.EpochTime, error) {
// The next handoff will start at the beginning of the next epoch,
// meaning that nodes need to send their applications until the end
Expand Down
Loading

0 comments on commit b0e2b95

Please sign in to comment.