From fb69e031bb669d5ca0a4ab468383a84938270b9d Mon Sep 17 00:00:00 2001 From: nareshmmr Date: Tue, 26 Nov 2024 11:43:36 +0100 Subject: [PATCH] base 2 commit --- .github/workflows/go.yaml | 94 +++++++++++- .gitignore | 5 + .../config/config_types.pb.go | 15 +- .../config/config_types.proto | 1 + go/ocr2/decryptionplugin/decryption.go | 141 +++++++++++++----- go/ocr2/decryptionplugin/decryption_test.go | 60 +++++++- go/ocr2/decryptionplugin/go.mod | 12 +- go/ocr2/decryptionplugin/go.sum | 23 ++- go/ocr2/decryptionplugin/queue.go | 25 +++- go/tdh2/tdh2/tdh2.go | 22 ++- go/tdh2/tdh2/tdh2_test.go | 61 ++++++-- go/tdh2/tdh2easy/sym.go | 3 + go/tdh2/tdh2easy/tdh2easy.go | 44 +++++- js/tdh2/tdh2.js | 3 + sonar-project.properties | 12 ++ 15 files changed, 422 insertions(+), 99 deletions(-) create mode 100644 sonar-project.properties diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml index baef4ad..82727e4 100644 --- a/.github/workflows/go.yaml +++ b/.github/workflows/go.yaml @@ -3,22 +3,56 @@ name: Go package on: [push] jobs: - build: + golangci-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Install golangci-lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3 + + # go.work makes it necessary to find go.mod files to run linter in the corresponding dirs + - name: Run golangci-lint + run: > + find . -name "go.mod" -execdir $(go env GOPATH)/bin/golangci-lint run + --timeout=2m0s + --out-format=checkstyle:golangci-lint-report.xml + --skip-dirs="internal/group/" \; + + - name: Check golangci-lint report for errors + run: find . -name "golangci-lint-report.xml" -exec grep "error" {} + && exit 1 || true + + - name: Upload golangci-lint report + if: always() + uses: actions/upload-artifact@v3 + with: + name: golangci-lint-report + path: | + ./go/ocr2/decryptionplugin/golangci-lint-report.xml + ./go/tdh2/golangci-lint-report.xml + + build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.20' - - name: Build and test plugin + - name: Build and test OCR2 plugin working-directory: ./go/ocr2/decryptionplugin run: | go build -v ./... - go test -v ./... + go test -v ./... -coverpkg=./... -coverprofile=ocr2_decryptionplugin_coverage.txt - name: Download npm deps working-directory: ./js/tdh2 @@ -28,4 +62,54 @@ jobs: working-directory: ./go/tdh2 run: | go build -v ./... - go test -v ./... + go test -v ./... -coverpkg=./... -coverprofile=tdh_coverage.txt + + - name: Upload Go test reports + if: always() + uses: actions/upload-artifact@v3 + with: + name: go-test-results + path: | + ./go/ocr2/decryptionplugin/ocr2_decryptionplugin_coverage.txt + ./go/tdh2/tdh_coverage.txt + + + sonar-scan: + name: SonarQube + needs: [golangci-lint, build-and-test] + runs-on: ubuntu-latest + if: always() + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetch all history for all tags and branches to provide more metadata for sonar reports + + - name: Download all workflow run artifacts + uses: actions/download-artifact@v3 + + - name: Update golangci-lint report symlinks + # When golangci-lint is run in a multimodule project, it creates a report with relative paths to the files which should be updated + # The command returns true to avoid failing the workflow if the report is not found + continue-on-error: true + run: | + sed -i 's@file\ name="@file\ name="/github/workspace/go/ocr2/decryptionplugin/@' ./golangci-lint-report/ocr2/decryptionplugin/golangci-lint-report.xml && echo "OCR2 golangci-lint report symlinks updated" + sed -i 's@file\ name="@file\ name="/github/workspace/go/tdh2/@' ./golangci-lint-report/tdh2/golangci-lint-report.xml && echo "TDH2 golangci-lint report symlinks updated" + + - name: Set SonarQube Report Paths + id: sonarqube_report_paths + shell: bash + run: | + echo "sonarqube_coverage_report_paths=$(find -type f -name '*coverage.txt' -printf "%p,")" >> $GITHUB_OUTPUT + echo "sonarqube_golangci_report_paths=$(find -type f -name 'golangci-lint-report.xml' -printf "%p,")" >> $GITHUB_OUTPUT + + - name: SonarQube Scan + uses: sonarsource/sonarqube-scan-action@v1.2.0 + with: + args: > + -Dsonar.go.coverage.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_coverage_report_paths }} + -Dsonar.go.golangci-lint.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_golangci_report_paths }} + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + diff --git a/.gitignore b/.gitignore index e69de29..424ef20 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,5 @@ +# Test & linter reports +*report.xml +*report.txt +*report.json +*.out \ No newline at end of file diff --git a/go/ocr2/decryptionplugin/config/config_types.pb.go b/go/ocr2/decryptionplugin/config/config_types.pb.go index a1b5bf5..925f3e9 100644 --- a/go/ocr2/decryptionplugin/config/config_types.pb.go +++ b/go/ocr2/decryptionplugin/config/config_types.pb.go @@ -86,6 +86,7 @@ type ReportingPluginConfig struct { RequestCountLimit uint32 `protobuf:"varint,4,opt,name=request_count_limit,json=requestCountLimit,proto3" json:"request_count_limit,omitempty"` RequestTotalBytesLimit uint32 `protobuf:"varint,5,opt,name=request_total_bytes_limit,json=requestTotalBytesLimit,proto3" json:"request_total_bytes_limit,omitempty"` RequireLocalRequestCheck bool `protobuf:"varint,6,opt,name=require_local_request_check,json=requireLocalRequestCheck,proto3" json:"require_local_request_check,omitempty"` + K uint32 `protobuf:"varint,7,opt,name=k,proto3" json:"k,omitempty"` // Number of decryption shares required for assembling plaintext. } func (x *ReportingPluginConfig) Reset() { @@ -162,6 +163,13 @@ func (x *ReportingPluginConfig) GetRequireLocalRequestCheck() bool { return false } +func (x *ReportingPluginConfig) GetK() uint32 { + if x != nil { + return x.K + } + return 0 +} + var File_config_config_types_proto protoreflect.FileDescriptor var file_config_config_types_proto_rawDesc = []byte{ @@ -173,7 +181,7 @@ var file_config_config_types_proto_rawDesc = []byte{ 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x53, - 0x68, 0x61, 0x72, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xee, 0x02, 0x0a, 0x15, 0x52, 0x65, + 0x68, 0x61, 0x72, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xfc, 0x02, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, @@ -196,8 +204,9 @@ var file_config_config_types_proto_rawDesc = []byte{ 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, - 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x01, 0x6b, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/ocr2/decryptionplugin/config/config_types.proto b/go/ocr2/decryptionplugin/config/config_types.proto index df9269d..e761f0f 100644 --- a/go/ocr2/decryptionplugin/config/config_types.proto +++ b/go/ocr2/decryptionplugin/config/config_types.proto @@ -16,4 +16,5 @@ message ReportingPluginConfig { uint32 request_count_limit = 4; uint32 request_total_bytes_limit = 5; bool require_local_request_check = 6; + uint32 k = 7; // Number of decryption shares required for assembling plaintext. } \ No newline at end of file diff --git a/go/ocr2/decryptionplugin/decryption.go b/go/ocr2/decryptionplugin/decryption.go index f05de25..a214f8c 100644 --- a/go/ocr2/decryptionplugin/decryption.go +++ b/go/ocr2/decryptionplugin/decryption.go @@ -36,15 +36,25 @@ type decryptionPlugin struct { func (f DecryptionReportingPluginFactory) NewReportingPlugin(rpConfig types.ReportingPluginConfig) (types.ReportingPlugin, types.ReportingPluginInfo, error) { pluginConfig, err := f.ConfigParser.ParseConfig(rpConfig.OffchainConfig) if err != nil { - f.Logger.Error("unable to decode reporting plugin config", commontypes.LogFields{ - "configDigest": rpConfig.ConfigDigest.String(), - }) - return nil, types.ReportingPluginInfo{}, fmt.Errorf("unable to decode reporting plugin config: %w", err) + return nil, types.ReportingPluginInfo{}, + fmt.Errorf("unable to decode reporting plugin config: %w", err) + } + + // The number of decryption shares K needed to reconstruct the plaintext should satisfy FF+1 liveness is not always satisfied as the leader might + // include an observation from a malicious party, whose decryption share is invalid. + // However, this configuration that favours safety over liveness might be desirable in certain use cases. + if int(pluginConfig.Config.K) <= rpConfig.F || int(pluginConfig.Config.K) > 2*rpConfig.F+1 { + return nil, types.ReportingPluginInfo{}, + fmt.Errorf("invalid configuration with K=%d and F=%d: decryption threshold K must satisfy F < K <= 2F+1", pluginConfig.Config.K, rpConfig.F) } info := types.ReportingPluginInfo{ Name: "ThresholdDecryption", - UniqueReports: false, // Aggregating any f+1 valid decryption shares result in the same plaintext. Must match setting in OCR2Base.sol. + UniqueReports: false, // Aggregating any k valid decryption shares results in the same plaintext. Must match setting in OCR2Base.sol. // TODO calculate limits based on the maximum size of the plaintext and ciphertextID Limits: types.ReportingPluginLimits{ MaxQueryLength: int(pluginConfig.Config.GetMaxQueryLengthBytes()), @@ -79,12 +89,23 @@ func (dp *decryptionPlugin) Query(ctx context.Context, ts types.ReportTimestamp) ) queryProto := Query{} + ciphertextIDs := make(map[string]bool) + allIDs := []string{} for _, request := range decryptionRequests { + if _, ok := ciphertextIDs[string(request.CiphertextId)]; ok { + dp.logger.Error("DecryptionReporting Query: duplicate request, skipping it", commontypes.LogFields{ + "ciphertextID": request.CiphertextId.String(), + }) + continue + } + ciphertextIDs[string(request.CiphertextId)] = true + ciphertext := &tdh2easy.Ciphertext{} if err := ciphertext.UnmarshalVerify(request.Ciphertext, dp.publicKey); err != nil { + dp.decryptionQueue.SetResult(request.CiphertextId, nil, ErrUnmarshalling) dp.logger.Error("DecryptionReporting Query: cannot unmarshal the ciphertext, skipping it", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": request.CiphertextId.String(), }) continue } @@ -92,12 +113,14 @@ func (dp *decryptionPlugin) Query(ctx context.Context, ts types.ReportTimestamp) CiphertextId: request.CiphertextId, Ciphertext: request.Ciphertext, }) + allIDs = append(allIDs, request.CiphertextId.String()) } dp.logger.Debug("DecryptionReporting Query: end", commontypes.LogFields{ - "epoch": ts.Epoch, - "round": ts.Round, - "queryLen": len(queryProto.DecryptionRequests), + "epoch": ts.Epoch, + "round": ts.Round, + "queryLen": len(queryProto.DecryptionRequests), + "ciphertextIDs": allIDs, }) queryProtoBytes, err := proto.Marshal(&queryProto) if err != nil { @@ -121,34 +144,45 @@ func (dp *decryptionPlugin) Observation(ctx context.Context, ts types.ReportTime } observationProto := Observation{} + ciphertextIDs := make(map[string]bool) + decryptedIDs := []string{} for _, request := range queryProto.DecryptionRequests { + ciphertextId := CiphertextId(request.CiphertextId) + if _, ok := ciphertextIDs[string(ciphertextId)]; ok { + dp.logger.Error("DecryptionReporting Observation: duplicate request in the same query, the leader is faulty", commontypes.LogFields{ + "ciphertextID": ciphertextId.String(), + }) + return nil, fmt.Errorf("duplicate request in the same query") + } + ciphertextIDs[string(ciphertextId)] = true + ciphertext := &tdh2easy.Ciphertext{} ciphertextBytes := request.Ciphertext if err := ciphertext.UnmarshalVerify(ciphertextBytes, dp.publicKey); err != nil { dp.logger.Error("DecryptionReporting Observation: cannot unmarshal and verify the ciphertext, the leader is faulty", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) return nil, fmt.Errorf("cannot unmarshal and verify the ciphertext: %w", err) } if dp.specificConfig.Config.RequireLocalRequestCheck { - queueCiphertextBytes, err := dp.decryptionQueue.GetCiphertext(request.CiphertextId) + queueCiphertextBytes, err := dp.decryptionQueue.GetCiphertext(ciphertextId) if err != nil && errors.Is(err, ErrNotFound) { dp.logger.Warn("DecryptionReporting Observation: cannot find ciphertext locally, skipping it", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) continue } else if err != nil { dp.logger.Error("DecryptionReporting Observation: failed when looking for ciphertext locally, skipping it", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) continue } if !bytes.Equal(queueCiphertextBytes, ciphertextBytes) { dp.logger.Error("DecryptionReporting Observation: local ciphertext does not match the query ciphertext, skipping it", commontypes.LogFields{ - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) continue } @@ -156,9 +190,10 @@ func (dp *decryptionPlugin) Observation(ctx context.Context, ts types.ReportTime decryptionShare, err := tdh2easy.Decrypt(ciphertext, dp.privKeyShare) if err != nil { - dp.logger.Error("DecryptionReporting Observation: cannot decrypt the ciphertext", commontypes.LogFields{ + dp.decryptionQueue.SetResult(ciphertextId, nil, ErrDecryption) + dp.logger.Error("DecryptionReporting Observation: cannot decrypt the ciphertext with the private key share", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) continue } @@ -166,14 +201,15 @@ func (dp *decryptionPlugin) Observation(ctx context.Context, ts types.ReportTime if err != nil { dp.logger.Error("DecryptionReporting Observation: cannot marshal the decryption share, skipping it", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) continue } observationProto.DecryptionShares = append(observationProto.DecryptionShares, &DecryptionShareWithID{ - CiphertextId: request.CiphertextId, + CiphertextId: ciphertextId, DecryptionShare: decryptionShareBytes, }) + decryptedIDs = append(decryptedIDs, ciphertextId.String()) } dp.logger.Debug("DecryptionReporting Observation: end", commontypes.LogFields{ @@ -181,6 +217,7 @@ func (dp *decryptionPlugin) Observation(ctx context.Context, ts types.ReportTime "round": ts.Round, "decryptedRequests": len(observationProto.DecryptionShares), "totalRequests": len(queryProto.DecryptionRequests), + "ciphertextIDs": decryptedIDs, }) observationProtoBytes, err := proto.Marshal(&observationProto) if err != nil { @@ -203,18 +240,18 @@ func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp } ciphertexts := make(map[string]*tdh2easy.Ciphertext) for _, request := range queryProto.DecryptionRequests { + ciphertextId := CiphertextId(request.CiphertextId) ciphertext := &tdh2easy.Ciphertext{} if err := ciphertext.UnmarshalVerify(request.Ciphertext, dp.publicKey); err != nil { dp.logger.Error("DecryptionReporting Report: cannot unmarshal and verify the ciphertext, the leader is faulty", commontypes.LogFields{ "error": err, - "ciphertextID": request.CiphertextId, + "ciphertextID": ciphertextId.String(), }) return false, nil, fmt.Errorf("cannot unmarshal and verify the ciphertext: %w", err) } - ciphertexts[string(request.CiphertextId)] = ciphertext + ciphertexts[string(ciphertextId)] = ciphertext } - fPlusOne := dp.genericConfig.F + 1 validDecryptionShares := make(map[string][]*tdh2easy.DecryptionShare) for _, ob := range obs { observationProto := &Observation{} @@ -226,12 +263,23 @@ func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp continue } + ciphertextIDs := make(map[string]bool) for _, decryptionShareWithID := range observationProto.DecryptionShares { - ciphertextID := string(decryptionShareWithID.CiphertextId) - ciphertext, ok := ciphertexts[ciphertextID] + ciphertextId := CiphertextId(decryptionShareWithID.CiphertextId) + ciphertextIdRawStr := string(ciphertextId) + if _, ok := ciphertextIDs[ciphertextIdRawStr]; ok { + dp.logger.Error("DecryptionReporting Report: the observation has multiple decryption shares for the same ciphertext id", commontypes.LogFields{ + "ciphertextID": ciphertextId.String(), + "observer": ob.Observer, + }) + continue + } + ciphertextIDs[ciphertextIdRawStr] = true + + ciphertext, ok := ciphertexts[ciphertextIdRawStr] if !ok { dp.logger.Error("DecryptionReporting Report: there is not ciphertext in the query with matching id", commontypes.LogFields{ - "ciphertextID": ciphertextID, + "ciphertextID": ciphertextId.String(), "observer": ob.Observer, }) continue @@ -242,17 +290,17 @@ func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp if err != nil { dp.logger.Error("DecryptionReporting Report: invalid decryption share", commontypes.LogFields{ "error": err, - "ciphertextID": ciphertextID, + "ciphertextID": ciphertextId.String(), "observer": ob.Observer, }) continue } - if len(validDecryptionShares[ciphertextID]) < fPlusOne { - validDecryptionShares[ciphertextID] = append(validDecryptionShares[ciphertextID], validDecryptionShare) + if len(validDecryptionShares[ciphertextIdRawStr]) < int(dp.specificConfig.Config.K) { + validDecryptionShares[ciphertextIdRawStr] = append(validDecryptionShares[ciphertextIdRawStr], validDecryptionShare) } else { - dp.logger.Trace("DecryptionReporting Report: we have already f+1 valid decryption shares", commontypes.LogFields{ - "ciphertextID": ciphertextID, + dp.logger.Trace("DecryptionReporting Report: we have already k valid decryption shares", commontypes.LogFields{ + "ciphertextID": ciphertextId.String(), "observer": ob.Observer, }) } @@ -260,23 +308,38 @@ func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp } reportProto := Report{} - for id, decrShares := range validDecryptionShares { - ciphertext, ok := ciphertexts[id] + for _, request := range queryProto.DecryptionRequests { + ciphertextId := CiphertextId(request.CiphertextId) + ciphertextIdRawStr := string(ciphertextId) + decrShares, ok := validDecryptionShares[ciphertextIdRawStr] + if !ok { + // Request not included in any observation in the current round. + dp.logger.Debug("DecryptionReporting Report: ciphertextID was not included in any observation in the current round", commontypes.LogFields{ + "ciphertextID": ciphertextId.String(), + }) + continue + } + ciphertext, ok := ciphertexts[ciphertextIdRawStr] if !ok { dp.logger.Error("DecryptionReporting Report: there is not ciphertext in the query with matching id, skipping aggregation of decryption shares", commontypes.LogFields{ - "ciphertextID": id, + "ciphertextID": ciphertextId.String(), + }) + continue + } + + if len(decrShares) < int(dp.specificConfig.Config.K) { + dp.logger.Debug("DecryptionReporting Report: not enough valid decryption shares after processing all observations, skipping aggregation of decryption shares", commontypes.LogFields{ + "ciphertextID": ciphertextId.String(), }) continue } - // OCR2.0 guaranties 2f+1 observations are from distinct oracles - // which guaranties f+1 valid observations and, hence, f+1 valid decryption shares. - // Therefore, here it is guaranteed that len(decrShares) > f. plaintext, err := tdh2easy.Aggregate(ciphertext, decrShares, dp.genericConfig.N) if err != nil { + dp.decryptionQueue.SetResult(ciphertextId, nil, ErrAggregation) dp.logger.Error("DecryptionReporting Report: cannot aggregate decryption shares", commontypes.LogFields{ "error": err, - "ciphertextID": id, + "ciphertextID": ciphertextId.String(), }) continue } @@ -284,10 +347,10 @@ func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp dp.logger.Debug("DecryptionReporting Report: plaintext aggregated successfully", commontypes.LogFields{ "epoch": ts.Epoch, "round": ts.Round, - "ciphertextID": id, + "ciphertextID": ciphertextId.String(), }) reportProto.ProcessedDecryptedRequests = append(reportProto.ProcessedDecryptedRequests, &ProcessedDecryptionRequest{ - CiphertextId: []byte(id), + CiphertextId: ciphertextId, Plaintext: plaintext, }) } @@ -346,7 +409,7 @@ func (dp *decryptionPlugin) ShouldAcceptFinalizedReport(ctx context.Context, ts } for _, item := range reportProto.ProcessedDecryptedRequests { - dp.decryptionQueue.SetResult(item.CiphertextId, item.Plaintext) + dp.decryptionQueue.SetResult(item.CiphertextId, item.Plaintext, nil) } dp.logger.Debug("DecryptionReporting ShouldAcceptFinalizedReport: end", commontypes.LogFields{ diff --git a/go/ocr2/decryptionplugin/decryption_test.go b/go/ocr2/decryptionplugin/decryption_test.go index caeb20a..c7c5327 100644 --- a/go/ocr2/decryptionplugin/decryption_test.go +++ b/go/ocr2/decryptionplugin/decryption_test.go @@ -64,7 +64,7 @@ func (q *queue) GetCiphertext(ciphertextId CiphertextId) ([]byte, error) { return nil, ErrNotFound } -func (q *queue) SetResult(ciphertextId CiphertextId, plaintext []byte) { +func (q *queue) SetResult(ciphertextId CiphertextId, plaintext []byte, err error) { q.res = append(q.res, ciphertextId) q.res = append(q.res, plaintext) } @@ -97,11 +97,14 @@ func TestNewReportingPlugin(t *testing.T) { MaxQueryLengthBytes: 1, MaxObservationLengthBytes: 2, MaxReportLengthBytes: 3, + K: 1, }), }, { name: "ok minimal", - conf: makeConfig(t, &config.ReportingPluginConfig{}), + conf: makeConfig(t, &config.ReportingPluginConfig{ + K: 1, + }), }, { name: "broken conf", @@ -110,6 +113,13 @@ func TestNewReportingPlugin(t *testing.T) { }, err: cmpopts.AnyError, }, + { + name: "invalid threshold", + conf: makeConfig(t, &config.ReportingPluginConfig{ + K: 0, + }), + err: cmpopts.AnyError, + }, } { t.Run(tc.name, func(t *testing.T) { factory := DecryptionReportingPluginFactory{ @@ -281,6 +291,11 @@ func TestQuery(t *testing.T) { }), want: ctxts, }, + { + name: "duplicate request", + in: append(ctxts, ctxts[1]), + want: ctxts, + }, } { t.Run(tc.name, func(t *testing.T) { q := &queue{} @@ -498,6 +513,11 @@ func TestObservation(t *testing.T) { })), err: cmpopts.AnyError, }, + { + name: "duplicate query", + query: makeQuery(t, append(ctxtsRaw[:3], ctxtsRaw[1])), + err: cmpopts.AnyError, + }, } { t.Run(tc.name, func(t *testing.T) { dp := &decryptionPlugin{ @@ -570,7 +590,8 @@ func makeObservations(t *testing.T, oracle2ids map[int][]string, id2shares map[s } func TestReport(t *testing.T) { - _, pk, sh, err := tdh2easy.GenerateKeys(3, 5) + k := uint32(3) + _, pk, sh, err := tdh2easy.GenerateKeys(int(k), 5) if err != nil { t.Fatalf("GenerateKeys: %v", err) } @@ -714,14 +735,34 @@ func TestReport(t *testing.T) { wantProcessed: true, want: want, }, + { + name: "all processed, duplicate decryption shares in a single observation", + query: makeQuery(t, ctxts), + obs: makeObservations(t, map[int][]string{ + 0: {"id0", "id0", "id2"}, + 1: {"id0", "id1", "id2"}, + 2: {"id0", "id1", "id2"}, + 3: {"id0", "id1", "id2"}, + }, shares), + wantProcessed: true, + want: want, + }, } { t.Run(tc.name, func(t *testing.T) { + conf, err := config.DecodeReportingPluginConfig(makeConfig(t, &config.ReportingPluginConfig{ + K: k, + }).OffchainConfig) + if err != nil { + t.Fatalf("DecodeReportingPluginConfig: %v", err) + } dp := &decryptionPlugin{ - logger: dummyLogger{}, - publicKey: pk, + logger: dummyLogger{}, + decryptionQueue: &queue{}, + publicKey: pk, genericConfig: &types.ReportingPluginConfig{ F: 2, }, + specificConfig: conf, oracleToKeyShare: map[commontypes.OracleID]int{ 0: 0, 1: 1, @@ -739,6 +780,9 @@ func TestReport(t *testing.T) { if processed != tc.wantProcessed { t.Errorf("got processed=%v, want=%v", processed, tc.wantProcessed) } + // make sure Report() output is deterministic + _, secondReportBytes, _ := dp.Report(context.Background(), types.ReportTimestamp{}, tc.query, tc.obs) + require.Equal(t, reportBytes, secondReportBytes) var report Report if err := proto.Unmarshal(reportBytes, &report); err != nil { t.Errorf("Unmarshal: %v", err) @@ -762,7 +806,11 @@ func TestNewReportingPlugin_CustomConfigParser(t *testing.T) { Logger: loggers.MakeLogrusLogger(), } - customParser.On("ParseConfig", mock.Anything).Return(&config.ReportingPluginConfigWrapper{}, nil).Once() + customParser.On("ParseConfig", mock.Anything).Return(&config.ReportingPluginConfigWrapper{ + Config: &config.ReportingPluginConfig{ + K: 1, + }, + }, nil).Once() _, _, err := factory.NewReportingPlugin(types.ReportingPluginConfig{}) require.NoError(t, err) diff --git a/go/ocr2/decryptionplugin/go.mod b/go/ocr2/decryptionplugin/go.mod index b9e72ac..fccd9b9 100644 --- a/go/ocr2/decryptionplugin/go.mod +++ b/go/ocr2/decryptionplugin/go.mod @@ -6,16 +6,20 @@ require ( github.com/google/go-cmp v0.5.9 //github.com/goplugin/plugin-libocr v0.0.0-20230503222226-29f534b2de1a github.com/goplugin/plugin-libocr v0.1.1-beta //plugin update changes - //github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230524070358-28006f3fdc99 - github.com/goplugin/tdh2/go/tdh2 v0.0.1 //plugin update changes + //github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166 + github.com/goplugin/tdh2/go/tdh2 v0.1.1 //plugin update changes + github.com/stretchr/testify v1.3.0 google.golang.org/protobuf v1.30.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect - go.dedis.ch/fixbuf v1.0.3 // indirect - go.dedis.ch/kyber/v3 v3.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.6.0 // indirect + github.com/stretchr/objx v0.1.0 // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect ) diff --git a/go/ocr2/decryptionplugin/go.sum b/go/ocr2/decryptionplugin/go.sum index c82349c..a783bee 100644 --- a/go/ocr2/decryptionplugin/go.sum +++ b/go/ocr2/decryptionplugin/go.sum @@ -1,35 +1,32 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/goplugin/plugin-libocr v0.0.0-20230503222226-29f534b2de1a h1:NFTlIAjSwx9vBYZeUerd0DDsvg+zZ5TSESw4dPNdwJk= github.com/goplugin/plugin-libocr v0.0.0-20230503222226-29f534b2de1a/go.mod h1:5JnCHuYgmIP9ZyXzgAfI5Iwu0WxBtBKp+ApeT5o1Cjw= -github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230524070358-28006f3fdc99 h1:XkM9YPlI0uUxp4INWXk/Nxc+k/QhSPpi04owatSR3t4= -github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230524070358-28006f3fdc99/go.mod h1:Jf9J8VVTgeONnXq/Dtv634P+JxbSn5IK2lNww84PiIY= +github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166 h1:cNH0nQjRfmWj173L8exDkQratcFVQ8AAj8RZ8a+suaI= +github.com/goplugin/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= -go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= -go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= -go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= -go.dedis.ch/kyber/v3 v3.1.0 h1:ghu+kiRgM5JyD9TJ0hTIxTLQlJBR/ehjWvWwYW3XsC0= -go.dedis.ch/kyber/v3 v3.1.0/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= -go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= -go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= -go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= -golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go/ocr2/decryptionplugin/queue.go b/go/ocr2/decryptionplugin/queue.go index d356d30..0425574 100644 --- a/go/ocr2/decryptionplugin/queue.go +++ b/go/ocr2/decryptionplugin/queue.go @@ -1,10 +1,22 @@ package decryptionplugin -import "errors" +import ( + "encoding/hex" + "fmt" +) -var ErrNotFound = errors.New("not found") +var ( + ErrNotFound = fmt.Errorf("not found") + ErrUnmarshalling = fmt.Errorf("cannot unmarshal the ciphertext in the query plugin function") + ErrDecryption = fmt.Errorf("cannot decrypt the ciphertext with the private key share in observation plugin function") + ErrAggregation = fmt.Errorf("cannot aggregate valid decryption shares in report plugn function") +) -type CiphertextId = []byte +type CiphertextId []byte + +func (c CiphertextId) String() string { + return "0x" + hex.EncodeToString(c) +} type DecryptionRequest struct { CiphertextId CiphertextId @@ -12,7 +24,7 @@ type DecryptionRequest struct { } type DecryptionQueuingService interface { - // GetRequests returns up to requestCountLimit oldest pending requests + // GetRequests returns up to requestCountLimit oldest pending unique requests // with total size up to totalBytesLimit bytes size. GetRequests(requestCountLimit int, totalBytesLimit int) []DecryptionRequest @@ -21,6 +33,7 @@ type DecryptionQueuingService interface { // If the ciphertext does not exist it returns ErrNotFound. GetCiphertext(ciphertextId CiphertextId) ([]byte, error) - // SetResult sets the plaintext (decrypted ciphertext) which corresponds to ciphertextId. - SetResult(ciphertextId CiphertextId, plaintext []byte) + // SetResult sets the plaintext (decrypted ciphertext) which corresponds to ciphertextId + // or returns an error if the decrypted ciphertext is invalid. + SetResult(ciphertextId CiphertextId, plaintext []byte, err error) } diff --git a/go/tdh2/tdh2/tdh2.go b/go/tdh2/tdh2/tdh2.go index d171994..7d50fb7 100644 --- a/go/tdh2/tdh2/tdh2.go +++ b/go/tdh2/tdh2/tdh2.go @@ -26,10 +26,6 @@ func parseGroup(group string) (group.Group, error) { switch group { case nist.NewP256().String(): return nist.NewP256(), nil - case nist.NewP384().String(): - return nist.NewP384(), nil - case nist.NewP521().String(): - return nist.NewP521(), nil } return nil, fmt.Errorf("unsupported group: %q", group) } @@ -60,6 +56,12 @@ func (s *PrivateShare) mulScalar(a group.Scalar) group.Scalar { return s.group.Scalar().Mul(s.v, a) } +func (p *PrivateShare) Clear() { + p.group = nil + p.index = 0 + p.v.Zero() +} + // privateShareRaw is used for PrivateShare (un)marshaling. type privateShareRaw struct { Group string @@ -128,10 +130,15 @@ type MasterSecret struct { s group.Scalar } -func (m MasterSecret) String() string { +func (m *MasterSecret) String() string { return fmt.Sprintf("group:%s value:hidden", m.group) } +func (m *MasterSecret) Clear() { + m.group = nil + m.s.Zero() +} + // masterSecretRaw is used for MasterSecret (un)marshaling. type masterSecretRaw struct { Group string @@ -362,7 +369,7 @@ func VerifyShare(pk *PublicKey, ctxt *Ciphertext, share *DecryptionShare) error func checkEi(pk *PublicKey, ctxt *Ciphertext, share *DecryptionShare) error { g := pk.group ui_hat := g.Point().Sub(g.Point().Mul(share.f_i, ctxt.u), g.Point().Mul(share.e_i, share.u_i)) - if share.index >= len(pk.hArray) { + if share.index < 0 || share.index >= len(pk.hArray) { return fmt.Errorf("invalid share index") } hi_hat := g.Point().Sub(g.Point().Mul(share.f_i, nil), g.Point().Mul(share.e_i, pk.hArray[share.index])) @@ -448,7 +455,8 @@ func (ctxt *Ciphertext) Decrypt(group group.Group, x_i *PrivateShare, rand ciphe } // CombineShares combines a set of decryption shares and returns the decrypted message. -// The caller has to ensure that the ciphertext is validated. +// The caller has to ensure that the ciphertext is validated, the decryption shares are valid, +// all the shares are distinct and the number of them is at least k. func (c *Ciphertext) CombineShares(group group.Group, shares []*DecryptionShare, k, n int) ([]byte, error) { if group.String() != c.group.String() { return nil, fmt.Errorf("incorrect ciphertext group: %q", c.group) diff --git a/go/tdh2/tdh2/tdh2_test.go b/go/tdh2/tdh2/tdh2_test.go index e315f6d..4bb1dc7 100644 --- a/go/tdh2/tdh2/tdh2_test.go +++ b/go/tdh2/tdh2/tdh2_test.go @@ -10,6 +10,8 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/goplugin/tdh2/go/tdh2/internal/group" @@ -18,8 +20,6 @@ import ( var supportedGroups = []string{ nist.NewP256().String(), - nist.NewP384().String(), - nist.NewP521().String(), } // unsupported implements an unsupported group @@ -563,6 +563,17 @@ func TestCheckEi(t *testing.T) { }, err: cmpopts.AnyError, }, + { + name: "negative share index", + ctxt: ctxt, + share: &DecryptionShare{ + index: -1, + u_i: ds.u_i, + e_i: ds.e_i, + f_i: ds.f_i, + }, + err: cmpopts.AnyError, + }, { name: "broken U", ctxt: ctxt, @@ -839,14 +850,6 @@ func TestParseGroup(t *testing.T) { group: nist.NewP256().String(), want: nist.NewP256(), }, - { - group: nist.NewP384().String(), - want: nist.NewP384(), - }, - { - group: nist.NewP521().String(), - want: nist.NewP521(), - }, { group: "wrong", err: cmpopts.AnyError, @@ -1442,27 +1445,37 @@ func BenchmarkAll(b *testing.B) { // run actual benchmarks b.Run(fmt.Sprintf("%v %d out of %d Generate", typ, tc.k, tc.n), func(b *testing.B) { for i := 0; i < b.N; i++ { - GenerateKeys(group, nil, tc.k, tc.n, rand) + if _, _, _, err := GenerateKeys(group, nil, tc.k, tc.n, rand); err != nil { + b.Fatalf("GenerateKeys: %v", err) + } } }) b.Run(fmt.Sprintf("%v %d out of %d Encrypt", typ, tc.k, tc.n), func(b *testing.B) { for i := 0; i < b.N; i++ { - Encrypt(pk, msg, label, rand) + if _, err := Encrypt(pk, msg, label, rand); err != nil { + b.Fatalf("Encrypt: %v", err) + } } }) b.Run(fmt.Sprintf("%v %d out of %d Decrypt", typ, tc.k, tc.n), func(b *testing.B) { for i := 0; i < b.N; i++ { - ctxt.Decrypt(group, shares[i%len(shares)], rand) + if _, err := ctxt.Decrypt(group, shares[i%len(shares)], rand); err != nil { + b.Fatalf("Decrypt: %v", err) + } } }) b.Run(fmt.Sprintf("%v %d out of %d VerifyShare", typ, tc.k, tc.n), func(b *testing.B) { for i := 0; i < b.N; i++ { - VerifyShare(pk, ctxt, decShares[i%len(decShares)]) + if err := VerifyShare(pk, ctxt, decShares[i%len(decShares)]); err != nil { + b.Fatalf("VerifyShare: %v", err) + } } }) b.Run(fmt.Sprintf("%v %d out of %d CombineShares", typ, tc.k, tc.n), func(b *testing.B) { for i := 0; i < b.N; i++ { - ctxt.CombineShares(group, decShares[:tc.k], tc.k, tc.n) + if _, err := ctxt.CombineShares(group, decShares[:tc.k], tc.k, tc.n); err != nil { + b.Fatalf("CombineShares: %v", err) + } } }) } @@ -1755,6 +1768,24 @@ func TestRedealReuseOldShares(t *testing.T) { } } +func TestClear(t *testing.T) { + for _, typ := range supportedGroups { + t.Run(typ, func(t *testing.T) { + g, r, _, _ := params(t, typ) + ms, _, shares, err := GenerateKeys(g, nil, 2, 3, r) + require.NoError(t, err) + originalScalar := ms.s.Clone() + ms.Clear() + require.NotEqual(t, originalScalar, ms.s) + for _, sh := range shares { + originalScalar = sh.v.Clone() + sh.Clear() + require.NotEqual(t, originalScalar, sh.v) + } + }) + } +} + func toJSON(t *testing.T, v interface{}) []byte { t.Helper() blob, err := json.Marshal(v) diff --git a/go/tdh2/tdh2easy/sym.go b/go/tdh2/tdh2easy/sym.go index ec57b81..f7fc4d9 100644 --- a/go/tdh2/tdh2easy/sym.go +++ b/go/tdh2/tdh2easy/sym.go @@ -22,6 +22,9 @@ func symEncrypt(msg, key []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, fmt.Errorf("cannot use AES: %v", err) } + if uint64(len(msg)) > ((1<<32)-2)*uint64(block.BlockSize()) { + return nil, nil, fmt.Errorf("message too long") + } gcm, err := cipher.NewGCM(block) if err != nil { return nil, nil, fmt.Errorf("cannot use GCM mode: %v", err) diff --git a/go/tdh2/tdh2easy/tdh2easy.go b/go/tdh2/tdh2easy/tdh2easy.go index d93bfe2..03be835 100644 --- a/go/tdh2/tdh2easy/tdh2easy.go +++ b/go/tdh2/tdh2easy/tdh2easy.go @@ -33,11 +33,23 @@ func (p PrivateShare) Marshal() ([]byte, error) { return p.p.Marshal() } +func (p *PrivateShare) MarshalJSON() ([]byte, error) { + return p.Marshal() +} + func (p *PrivateShare) Unmarshal(data []byte) error { p.p = &tdh2.PrivateShare{} return p.p.Unmarshal(data) } +func (p *PrivateShare) UnmarshalJSON(data []byte) error { + return p.Unmarshal(data) +} + +func (p *PrivateShare) Clear() { + p.p.Clear() +} + // DecryptionShare encodes TDH2 decryption share. type DecryptionShare struct { d *tdh2.DecryptionShare @@ -52,11 +64,19 @@ func (d DecryptionShare) Marshal() ([]byte, error) { return d.d.Marshal() } +func (d DecryptionShare) MarshalJSON() ([]byte, error) { + return d.Marshal() +} + func (d *DecryptionShare) Unmarshal(data []byte) error { d.d = &tdh2.DecryptionShare{} return d.d.Unmarshal(data) } +func (d *DecryptionShare) UnmarshalJSON(data []byte) error { + return d.Unmarshal(data) +} + // PublicKey encodes TDH2 public key. type PublicKey struct { p *tdh2.PublicKey @@ -66,11 +86,19 @@ func (p PublicKey) Marshal() ([]byte, error) { return p.p.Marshal() } +func (p *PublicKey) MarshalJSON() ([]byte, error) { + return p.Marshal() +} + func (p *PublicKey) Unmarshal(data []byte) error { p.p = &tdh2.PublicKey{} return p.p.Unmarshal(data) } +func (p *PublicKey) UnmarshalJSON(data []byte) error { + return p.Unmarshal(data) +} + // MasterSecret encodes TDH2 master key. type MasterSecret struct { m *tdh2.MasterSecret @@ -80,11 +108,23 @@ func (m MasterSecret) Marshal() ([]byte, error) { return m.m.Marshal() } +func (m MasterSecret) MarshalJSON() ([]byte, error) { + return m.Marshal() +} + func (m *MasterSecret) Unmarshal(data []byte) error { m.m = &tdh2.MasterSecret{} return m.m.Unmarshal(data) } +func (m MasterSecret) UnmarshalJSON(data []byte) error { + return m.Unmarshal(data) +} + +func (m *MasterSecret) Clear() { + m.m.Clear() +} + // Ciphertext encodes hybrid ciphertext. type Ciphertext struct { tdh2Ctxt *tdh2.Ciphertext @@ -113,7 +153,9 @@ func VerifyShare(c *Ciphertext, pk *PublicKey, share *DecryptionShare) error { // Aggregate decrypts the TDH2-encrypted key and using it recovers the // symmetrically encrypted plaintext. It takes decryption shares and // the total number of participants as the arguments. -// Ciphertext and shares MUST be verified before calling Aggregate. +// Ciphertext and shares MUST be verified before calling Aggregate, +// all the shares have to be distinct and their number has to be +// at least k (the scheme's threshold). func Aggregate(c *Ciphertext, shares []*DecryptionShare, n int) ([]byte, error) { sh := []*tdh2.DecryptionShare{} for _, s := range shares { diff --git a/js/tdh2/tdh2.js b/js/tdh2/tdh2.js index 6181cbd..d580b2a 100644 --- a/js/tdh2/tdh2.js +++ b/js/tdh2/tdh2.js @@ -104,10 +104,13 @@ function xor(a, b) { function encrypt(pub, msg) { const ciph = new Cipher('AES-256-GCM'); + const blockSize = 16; const key = rnd.randomBytes(tdh2InputSize); const nonce = rnd.randomBytes(12); ciph.init(key, nonce); + if (msg.length > ((2**32)-2)*blockSize) + throw new Error('message too long'); const ctxt = Buffer.concat([ ciph.update(msg), ciph.final(), diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..1ce14db --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +# projectKey is required (may be found under "Project Information" in Sonar or in project url) +sonar.projectKey=goplugin_tdh2 +sonar.sources=. + +# Full exclusions from the static analysis +sonar.exclusions=**/mocks/**/*, **/testdata/**/*, **/script/**/*, **/generated/**/*, **/fixtures/**/*, **/docs/**/*, **/tools/**/*, **/*.pb.go, **/*report.xml, **/*.txt, **/*.abi, **/*.bin +# Coverage exclusions +sonar.coverage.exclusions=**/*_test.go, **/config/**/*, **/test/**/* + +# Tests' root folder, inclusions (tests to check and count) and exclusions +sonar.tests=. +sonar.test.inclusions=**/*_test.go