Skip to content

Commit

Permalink
Merge pull request #257 from attestantio/multi_sign_contrib_proof
Browse files Browse the repository at this point in the history
Multi-sign contributions and proofs
  • Loading branch information
Bez625 authored Oct 1, 2024
2 parents 949dd0a + 71f62a4 commit 028c36a
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 126 deletions.
11 changes: 11 additions & 0 deletions services/signer/mock/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ func (*Service) SignContributionAndProof(_ context.Context,
return phase0.BLSSignature{}, nil
}

// SignContributionAndProofs signs multiple sync committee contributions for multiple accounts.
func (*Service) SignContributionAndProofs(_ context.Context,
_ []e2wtypes.Account,
_ []*altair.ContributionAndProof,
) (
[]phase0.BLSSignature,
error,
) {
return []phase0.BLSSignature{}, nil
}

// SignSyncCommitteeRoot returns a root signature.
// This signs a beacon block root with the "sync committee" domain.
func (*Service) SignSyncCommitteeRoot(_ context.Context,
Expand Down
12 changes: 10 additions & 2 deletions services/signer/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ type SyncCommitteeSelectionSigner interface {
// SignSyncCommitteeSelections returns multiple sync committee selection signatures.
// This signs a slot and subcommittee with the "sync committee selection proof" domain.
SignSyncCommitteeSelections(ctx context.Context,
account []e2wtypes.Account,
accounts []e2wtypes.Account,
slot phase0.Slot,
subcommitteeIndex []uint64,
subcommitteeIndices []uint64,
) (
[]phase0.BLSSignature,
error,
Expand All @@ -188,6 +188,14 @@ type ContributionAndProofSigner interface {
phase0.BLSSignature,
error,
)
// SignContributionAndProofs signs multiple sync committee contributions for multiple accounts.
SignContributionAndProofs(ctx context.Context,
accounts []e2wtypes.Account,
contributionAndProofs []*altair.ContributionAndProof,
) (
[]phase0.BLSSignature,
error,
)
}

// ValidatorRegistrationSigner provides methods to sign validator registrations.
Expand Down
68 changes: 52 additions & 16 deletions services/signer/standard/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,6 @@ func (*Service) sign(ctx context.Context,
return signature, nil
}

// signRootMulti signs the same root for multiple accounts, using protected methods if possible.
func (s *Service) signRootMulti(ctx context.Context,
accounts []e2wtypes.Account,
root phase0.Root,
domain phase0.Domain,
) (
[]phase0.BLSSignature,
error,
) {
roots := make([]phase0.Root, len(accounts))
for i := range accounts {
roots[i] = root
}
return s.signRootsMulti(ctx, accounts, roots, domain)
}

// signRootsMulti signs multiple roots for multiple accounts, using protected methods if possible.
func (*Service) signRootsMulti(ctx context.Context,
accounts []e2wtypes.Account,
Expand Down Expand Up @@ -129,3 +113,55 @@ func (*Service) signRootsMulti(ctx context.Context,
}
return sigs, nil
}

// signRootsByAccountType collect roots by account type and multi-sign each type.
func (s *Service) signRootsByAccountType(ctx context.Context, accounts []e2wtypes.Account, roots []phase0.Root, domain phase0.Domain) ([]phase0.BLSSignature, error) {
if len(accounts) != len(roots) {
return []phase0.BLSSignature{}, errors.New("number of accounts and roots do not match")
}
// Need to break the single request in to two: those for accounts and those for distributed accounts.
// This is because they operate differently (single shot Vs. threshold signing).
// We also keep a map to allow us to reassemble the signatures in the correct order.
signingAccountRoots := make([]phase0.Root, 0, len(roots))
accountSigMap := make(map[int]int)
signingAccounts := make([]e2wtypes.Account, 0, len(accounts))
distributedAccountRoots := make([]phase0.Root, 0, len(roots))
distributedAccountSigMap := make(map[int]int)
signingDistributedAccounts := make([]e2wtypes.Account, 0, len(accounts))
for i := range accounts {
if _, isDistributedAccount := accounts[i].(e2wtypes.DistributedAccount); isDistributedAccount {
signingDistributedAccounts = append(signingDistributedAccounts, accounts[i])
distributedAccountSigMap[len(signingDistributedAccounts)-1] = i
distributedAccountRoots = append(distributedAccountRoots, roots[i])
} else {
signingAccounts = append(signingAccounts, accounts[i])
accountSigMap[len(signingAccounts)-1] = i

signingAccountRoots = append(signingAccountRoots, roots[i])
}
}

// Because this function returns all or none of the signatures we run these in series. This ensures that we don't
// end up in a situation where one Vouch instance obtains signatures for individual accounts and the other for distributed accounts,
// which would result in neither of them returning the full set of signatures and hence both erroring out.
sigs := make([]phase0.BLSSignature, len(accounts))
if len(signingAccounts) > 0 {
signatures, err := s.signRootsMulti(ctx, signingAccounts, signingAccountRoots, domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign for individual accounts")
}
for i := range signatures {
sigs[accountSigMap[i]] = signatures[i]
}
}
if len(signingDistributedAccounts) > 0 {
signatures, err := s.signRootsMulti(ctx, signingDistributedAccounts, distributedAccountRoots, domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign for distributed accounts")
}
for i := range signatures {
sigs[distributedAccountSigMap[i]] = signatures[i]
}
}
return sigs, nil
}
43 changes: 43 additions & 0 deletions services/signer/standard/signcontributonandproof.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,46 @@ func (s *Service) SignContributionAndProof(ctx context.Context,

return sig, nil
}

// SignContributionAndProofs signs multiple sync committee contributions for multiple accounts.
func (s *Service) SignContributionAndProofs(ctx context.Context,
accounts []e2wtypes.Account,
contributionAndProofs []*altair.ContributionAndProof,
) (
[]phase0.BLSSignature,
error,
) {
ctx, span := otel.Tracer("attestantio.vouch.services.signer.standard").Start(ctx, "SignContributionAndProofs")
defer span.End()

if s.contributionAndProofDomainType == nil {
return []phase0.BLSSignature{}, errors.New("no contribution and proof domain type available; cannot sign")
}

if len(accounts) != len(contributionAndProofs) {
return []phase0.BLSSignature{}, errors.New("number of accounts and contribution and proofs do not match")
}

// Calculate the domain.
epoch := phase0.Epoch(contributionAndProofs[0].Contribution.Slot / s.slotsPerEpoch)
domain, err := s.domainProvider.Domain(ctx, *s.contributionAndProofDomainType, epoch)
if err != nil {
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for contribution and proof")
}

roots := make([]phase0.Root, len(contributionAndProofs))
for i := range contributionAndProofs {
root, err := contributionAndProofs[i].HashTreeRoot()
if err != nil {
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to calculate hash tree root")
}
roots[i] = root
}

sigs, err := s.signRootsByAccountType(ctx, accounts, roots, domain)
if err != nil {
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to sign contribution and proofs")
}

return sigs, nil
}
45 changes: 6 additions & 39 deletions services/signer/standard/signsynccommitteeroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,47 +76,14 @@ func (s *Service) SignSyncCommitteeRoots(ctx context.Context,
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for sync committee")
}

// Need to break the single request in to two: those for accounts and those for distributed accounts.
// This is because they operate differently (single shot Vs. threshold signing).
// We also keep a map to allow us to reassemble the signatures in the correct order.
accountSigMap := make(map[int]int)
signingAccounts := make([]e2wtypes.Account, 0, len(accounts))
distributedAccountSigMap := make(map[int]int)
signingDistributedAccounts := make([]e2wtypes.Account, 0, len(accounts))
for i, account := range accounts {
if account == nil {
continue
}
if _, isDistributedAccount := account.(e2wtypes.DistributedAccount); isDistributedAccount {
signingDistributedAccounts = append(signingDistributedAccounts, account)
distributedAccountSigMap[len(signingDistributedAccounts)-1] = i
} else {
signingAccounts = append(signingAccounts, account)
accountSigMap[len(signingAccounts)-1] = i
}
roots := make([]phase0.Root, len(accounts))
for i := range accounts {
roots[i] = root
}

// Because this function returns all or none of the signatures we run these in series. This ensures that we don't
// end up in a situation where one Vouch instance obtains signatures for individual accounts and the other for distributed accounts,
// which would result in neither of them returning the full set of signatures and hence both erroring out.
sigs := make([]phase0.BLSSignature, len(accounts))
if len(signingAccounts) > 0 {
signatures, err := s.signRootMulti(ctx, signingAccounts, root, domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign for individual accounts")
}
for i := range signatures {
sigs[accountSigMap[i]] = signatures[i]
}
}
if len(signingDistributedAccounts) > 0 {
signatures, err := s.signRootMulti(ctx, signingDistributedAccounts, root, domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign for distributed accounts")
}
for i := range signatures {
sigs[distributedAccountSigMap[i]] = signatures[i]
}
sigs, err := s.signRootsByAccountType(ctx, accounts, roots, domain)
if err != nil {
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to sign sync committee roots")
}

return sigs, nil
Expand Down
68 changes: 11 additions & 57 deletions services/signer/standard/signsynccommitteeselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,69 +90,23 @@ func (s *Service) SignSyncCommitteeSelections(ctx context.Context,
return []phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for sync committee selection proof")
}

// Need to break the single request in to two: those for accounts and those for distributed accounts.
// This is because they operate differently (single shot Vs. threshold signing).
// We also keep a map to allow us to reassemble the signatures in the correct order.
signingAccountRoots := make([]phase0.Root, 0, len(subcommitteeIndices))
accountSigMap := make(map[int]int)
signingAccounts := make([]e2wtypes.Account, 0, len(accounts))
distributedAccountRoots := make([]phase0.Root, 0, len(subcommitteeIndices))
distributedAccountSigMap := make(map[int]int)
signingDistributedAccounts := make([]e2wtypes.Account, 0, len(accounts))
roots := make([]phase0.Root, len(accounts))
for i := range accounts {
if _, isDistributedAccount := accounts[i].(e2wtypes.DistributedAccount); isDistributedAccount {
signingDistributedAccounts = append(signingDistributedAccounts, accounts[i])
distributedAccountSigMap[len(signingDistributedAccounts)-1] = i
root, err := getSyncCommitteeSelectionRoot(slot, subcommitteeIndices[i])
if err != nil {
return nil, err
}
distributedAccountRoots = append(distributedAccountRoots, root)
} else {
signingAccounts = append(signingAccounts, accounts[i])
accountSigMap[len(signingAccounts)-1] = i
root, err := getSyncCommitteeSelectionRoot(slot, subcommitteeIndices[i])
if err != nil {
return nil, err
}
signingAccountRoots = append(signingAccountRoots, root)
selectionData := &altair.SyncAggregatorSelectionData{
Slot: slot,
SubcommitteeIndex: subcommitteeIndices[i],
}
}

// Because this function returns all or none of the signatures we run these in series. This ensures that we don't
// end up in a situation where one Vouch instance obtains signatures for individual accounts and the other for distributed accounts,
// which would result in neither of them returning the full set of signatures and hence both erroring out.
sigs := make([]phase0.BLSSignature, len(accounts))
if len(signingAccounts) > 0 {
signatures, err := s.signRootsMulti(ctx, signingAccounts, signingAccountRoots, domain)
root, err := selectionData.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to sign for individual accounts")
}
for i := range signatures {
sigs[accountSigMap[i]] = signatures[i]
}
}
if len(signingDistributedAccounts) > 0 {
signatures, err := s.signRootsMulti(ctx, signingDistributedAccounts, distributedAccountRoots, domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign for distributed accounts")
}
for i := range signatures {
sigs[distributedAccountSigMap[i]] = signatures[i]
return nil, errors.Wrap(err, "failed to obtain hash tree root of sync aggregator selection data")
}
roots[i] = root
}

return sigs, nil
}

func getSyncCommitteeSelectionRoot(slot phase0.Slot, subcommitteeIndex uint64) (phase0.Root, error) {
selectionData := &altair.SyncAggregatorSelectionData{
Slot: slot,
SubcommitteeIndex: subcommitteeIndex,
}
root, err := selectionData.HashTreeRoot()
sigs, err := s.signRootsByAccountType(ctx, accounts, roots, domain)
if err != nil {
return phase0.Root{}, errors.Wrap(err, "failed to obtain hash tree root of sync aggregator selection data")
return nil, errors.Wrap(err, "failed to sign sync committee selections")
}
return root, nil

return sigs, nil
}
31 changes: 19 additions & 12 deletions services/synccommitteeaggregator/standard/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
"go.opentelemetry.io/otel"
)

Expand Down Expand Up @@ -171,7 +172,8 @@ func (s *Service) Aggregate(ctx context.Context, duty *synccommitteeaggregator.D
}
log.Trace().Dur("elapsed", time.Since(started)).Str("beacon_block_root", fmt.Sprintf("%#x", *beaconBlockRoot)).Msg("Obtained beacon block root")

signedContributionAndProofs := make([]*altair.SignedContributionAndProof, 0)
contributionAndProofs := make([]*altair.ContributionAndProof, 0)
accounts := make([]e2wtypes.Account, 0)
for _, validatorIndex := range duty.ValidatorIndices {
for subcommitteeIndex := range duty.SelectionProofs[validatorIndex] {
log.Trace().Uint64("validator_index", uint64(validatorIndex)).Uint64("subcommittee_index", subcommitteeIndex).Str("beacon_block_root", fmt.Sprintf("%#x", *beaconBlockRoot)).Msg("Aggregating")
Expand All @@ -191,26 +193,31 @@ func (s *Service) Aggregate(ctx context.Context, duty *synccommitteeaggregator.D
Contribution: contribution,
SelectionProof: duty.SelectionProofs[validatorIndex][subcommitteeIndex],
}
contributionAndProofs = append(contributionAndProofs, contributionAndProof)
account, exists := duty.Accounts[validatorIndex]
if !exists {
log.Debug().Msg("Account nil; likely exited validator still in sync committee")
monitorSyncCommitteeAggregationsCompleted(started, duty.Slot, len(duty.ValidatorIndices), "exited", startOfSlot)
return
}
sig, err := s.contributionAndProofSigner.SignContributionAndProof(ctx, account, contributionAndProof)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain signature of contribution and proof")
monitorSyncCommitteeAggregationsCompleted(started, duty.Slot, len(duty.ValidatorIndices), "failed", startOfSlot)
return
}
accounts = append(accounts, account)
}
}

signedContributionAndProof := &altair.SignedContributionAndProof{
Message: contributionAndProof,
Signature: sig,
}
sigs, err := s.contributionAndProofSigner.SignContributionAndProofs(ctx, accounts, contributionAndProofs)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain signatures of contribution and proofs")
monitorSyncCommitteeAggregationsCompleted(started, duty.Slot, len(duty.ValidatorIndices), "failed", startOfSlot)
return
}

signedContributionAndProofs = append(signedContributionAndProofs, signedContributionAndProof)
signedContributionAndProofs := make([]*altair.SignedContributionAndProof, 0)
for i := range sigs {
signedContributionAndProof := &altair.SignedContributionAndProof{
Message: contributionAndProofs[i],
Signature: sigs[i],
}
signedContributionAndProofs = append(signedContributionAndProofs, signedContributionAndProof)
}

if err := s.syncCommitteeContributionsSubmitter.SubmitSyncCommitteeContributions(ctx, signedContributionAndProofs); err != nil {
Expand Down

0 comments on commit 028c36a

Please sign in to comment.