├── .github └── workflows │ └── go.yaml ├── .gitignore ├── LICENSE ├── README.md ├── go ├── go.work ├── go.work.sum ├── ocr2 │ └── decryptionplugin │ │ ├── config │ │ ├── config.go │ │ ├── config_types.pb.go │ │ ├── config_types.proto │ │ └── mocks │ │ │ └── config_parser.go │ │ ├── decryption.go │ │ ├── decryption_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── go_generate.go │ │ ├── queue.go │ │ ├── types.pb.go │ │ └── types.proto └── tdh2 │ ├── go.mod │ ├── go.sum │ ├── lib │ └── group │ │ ├── LICENSE │ │ ├── group.go │ │ ├── mod │ │ ├── int.go │ │ └── int_test.go │ │ ├── nist │ │ ├── curve.go │ │ └── group_test.go │ │ ├── share │ │ ├── poly.go │ │ └── poly_test.go │ │ └── test │ │ ├── group.go │ │ └── test.go │ ├── tdh2 │ ├── tdh2.go │ └── tdh2_test.go │ └── tdh2easy │ ├── js_test.go │ ├── sym.go │ ├── sym_test.go │ ├── tdh2easy.go │ └── tdh2easy_test.go ├── js └── tdh2 │ ├── README.md │ ├── decs.d.ts │ ├── package-lock.json │ ├── package.json │ ├── tdh2.js │ └── test │ ├── package.json │ └── test.js └── sonar-project.properties /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: Go package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | golangci-lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | 12 | - name: Set up Go 13 | uses: actions/setup-go@v4 14 | with: 15 | go-version: '1.21' 16 | 17 | - name: Install golangci-lint 18 | run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.57.2 19 | 20 | # go.work makes it necessary to find go.mod files to run linter in the corresponding dirs 21 | - name: Run golangci-lint 22 | run: > 23 | find . -name "go.mod" -execdir $(go env GOPATH)/bin/golangci-lint run 24 | --timeout=2m0s 25 | --out-format=checkstyle:golangci-lint-report.xml 26 | --skip-dirs="lib/group/" \; 27 | 28 | - name: Check golangci-lint report for errors 29 | run: find . -name "golangci-lint-report.xml" -exec grep "error" {} + && exit 1 || true 30 | 31 | - name: Upload golangci-lint report 32 | if: always() 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: golangci-lint-report 36 | path: | 37 | ./go/ocr2/decryptionplugin/golangci-lint-report.xml 38 | ./go/tdh2/golangci-lint-report.xml 39 | 40 | build-and-test: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v3 45 | 46 | - name: Set up Go 47 | uses: actions/setup-go@v4 48 | with: 49 | go-version: '1.21' 50 | 51 | - name: Ensure dependencies synced & tidy 52 | working-directory: ./go 53 | run: | 54 | go work sync 55 | pushd tdh2 56 | go mod tidy 57 | popd 58 | pushd ocr2/decryptionplugin 59 | go mod tidy 60 | popd 61 | git diff --minimal --exit-code 62 | 63 | - name: Build and test OCR2 plugin 64 | working-directory: ./go/ocr2/decryptionplugin 65 | run: | 66 | go build -v ./... 67 | go test -v ./... -coverpkg=./... -coverprofile=ocr2_decryptionplugin_coverage.txt 68 | 69 | - name: Race test OCR2 plugin 70 | working-directory: ./go/ocr2/decryptionplugin 71 | run: | 72 | go test -race -v ./... -coverpkg=./... -coverprofile=ocr2_decryptionplugin_race_coverage.txt 73 | 74 | - name: Download npm deps 75 | working-directory: ./js/tdh2 76 | run: npm install 77 | 78 | - name: Build and test TDH2 79 | working-directory: ./go/tdh2 80 | run: | 81 | go build -v ./... 82 | go test -v ./... -coverpkg=./... -coverprofile=tdh_coverage.txt 83 | 84 | - name: Race test TDH2 85 | working-directory: ./go/tdh2 86 | run: | 87 | go test -race -v ./... -coverpkg=./... -coverprofile=tdh_race_coverage.txt 88 | 89 | - name: Upload Go test reports 90 | if: always() 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: go-test-results 94 | path: | 95 | ./go/ocr2/decryptionplugin/ocr2_decryptionplugin_coverage.txt 96 | ./go/ocr2/decryptionplugin/ocr2_decryptionplugin_race_coverage.txt 97 | ./go/tdh2/tdh_coverage.txt 98 | ./go/tdh2/tdh_race_coverage.txt 99 | 100 | 101 | sonar-scan: 102 | name: SonarQube 103 | needs: [golangci-lint, build-and-test] 104 | runs-on: ubuntu-latest 105 | if: always() 106 | steps: 107 | - name: Checkout the repo 108 | uses: actions/checkout@v3 109 | with: 110 | fetch-depth: 0 # fetch all history for all tags and branches to provide more metadata for sonar reports 111 | 112 | - name: Download all workflow run artifacts 113 | uses: actions/download-artifact@v4 114 | 115 | - name: Update golangci-lint report symlinks 116 | # When golangci-lint is run in a multimodule project, it creates a report with relative paths to the files which should be updated 117 | # The command returns true to avoid failing the workflow if the report is not found 118 | continue-on-error: true 119 | run: | 120 | 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" 121 | 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" 122 | 123 | - name: Set SonarQube Report Paths 124 | id: sonarqube_report_paths 125 | shell: bash 126 | run: | 127 | echo "sonarqube_coverage_report_paths=$(find -type f -name '*coverage.txt' -printf "%p,")" >> $GITHUB_OUTPUT 128 | echo "sonarqube_golangci_report_paths=$(find -type f -name 'golangci-lint-report.xml' -printf "%p,")" >> $GITHUB_OUTPUT 129 | 130 | - name: SonarQube Scan 131 | uses: sonarsource/sonarqube-scan-action@69c1a75940dec6249b86dace6b630d3a2ae9d2a7 # v2.0.1 132 | with: 133 | args: > 134 | -Dsonar.go.coverage.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_coverage_report_paths }} 135 | -Dsonar.go.golangci-lint.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_golangci_report_paths }} 136 | env: 137 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 138 | SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test & linter reports 2 | *report.xml 3 | *report.txt 4 | *report.json 5 | *.out 6 | 7 | # logs 8 | *.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SmartContract 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tdh2 2 | A Go & JavaScript implementation of the TDH2 protocol from [Securing Threshold Cryptosystems against Chosen Ciphertext Attack](https://www.shoup.net/papers/thresh1.pdf) by Shoup & Gennaro. 3 | -------------------------------------------------------------------------------- /go/go.work: -------------------------------------------------------------------------------- 1 | go 1.21 2 | 3 | toolchain go1.21.13 4 | 5 | use ( 6 | ./ocr2/decryptionplugin 7 | ./tdh2 8 | ) 9 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "google.golang.org/protobuf/proto" 5 | ) 6 | 7 | // This config is stored in the Oracle contract (set via SetConfig()). 8 | // Every SetConfig() call reloads the reporting plugin (DirectRequestReportingPluginFactory.NewReportingPlugin()) 9 | type ReportingPluginConfigWrapper struct { 10 | Config *ReportingPluginConfig 11 | } 12 | 13 | func DecodeReportingPluginConfig(raw []byte) (*ReportingPluginConfigWrapper, error) { 14 | configProto := &ReportingPluginConfig{} 15 | if err := proto.Unmarshal(raw, configProto); err != nil { 16 | return nil, err 17 | } 18 | return &ReportingPluginConfigWrapper{Config: configProto}, nil 19 | } 20 | 21 | func EncodeReportingPluginConfig(rpConfig *ReportingPluginConfigWrapper) ([]byte, error) { 22 | return proto.Marshal(rpConfig.Config) 23 | } 24 | 25 | //go:generate mockery --quiet --name ConfigParser --output ./mocks/ --case=underscore 26 | type ConfigParser interface { 27 | ParseConfig(offchainConfig []byte) (*ReportingPluginConfigWrapper, error) 28 | } 29 | 30 | type DefaultConfigParser struct { 31 | } 32 | 33 | func (p *DefaultConfigParser) ParseConfig(offchainConfig []byte) (*ReportingPluginConfigWrapper, error) { 34 | return DecodeReportingPluginConfig(offchainConfig) 35 | } 36 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/config/config_types.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.20.0 5 | // source: config/config_types.proto 6 | 7 | package config 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type OracleIDtoKeyShareIndex struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | OracleId uint32 `protobuf:"varint,1,opt,name=oracle_id,json=oracleId,proto3" json:"oracle_id,omitempty"` 29 | KeyShareIndex uint32 `protobuf:"varint,2,opt,name=key_share_index,json=keyShareIndex,proto3" json:"key_share_index,omitempty"` 30 | } 31 | 32 | func (x *OracleIDtoKeyShareIndex) Reset() { 33 | *x = OracleIDtoKeyShareIndex{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_config_config_types_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *OracleIDtoKeyShareIndex) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*OracleIDtoKeyShareIndex) ProtoMessage() {} 46 | 47 | func (x *OracleIDtoKeyShareIndex) ProtoReflect() protoreflect.Message { 48 | mi := &file_config_config_types_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use OracleIDtoKeyShareIndex.ProtoReflect.Descriptor instead. 60 | func (*OracleIDtoKeyShareIndex) Descriptor() ([]byte, []int) { 61 | return file_config_config_types_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *OracleIDtoKeyShareIndex) GetOracleId() uint32 { 65 | if x != nil { 66 | return x.OracleId 67 | } 68 | return 0 69 | } 70 | 71 | func (x *OracleIDtoKeyShareIndex) GetKeyShareIndex() uint32 { 72 | if x != nil { 73 | return x.KeyShareIndex 74 | } 75 | return 0 76 | } 77 | 78 | type ReportingPluginConfig struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | MaxQueryLengthBytes uint32 `protobuf:"varint,1,opt,name=max_query_length_bytes,json=maxQueryLengthBytes,proto3" json:"max_query_length_bytes,omitempty"` 84 | MaxObservationLengthBytes uint32 `protobuf:"varint,2,opt,name=max_observation_length_bytes,json=maxObservationLengthBytes,proto3" json:"max_observation_length_bytes,omitempty"` 85 | MaxReportLengthBytes uint32 `protobuf:"varint,3,opt,name=max_report_length_bytes,json=maxReportLengthBytes,proto3" json:"max_report_length_bytes,omitempty"` 86 | RequestCountLimit uint32 `protobuf:"varint,4,opt,name=request_count_limit,json=requestCountLimit,proto3" json:"request_count_limit,omitempty"` 87 | RequestTotalBytesLimit uint32 `protobuf:"varint,5,opt,name=request_total_bytes_limit,json=requestTotalBytesLimit,proto3" json:"request_total_bytes_limit,omitempty"` 88 | RequireLocalRequestCheck bool `protobuf:"varint,6,opt,name=require_local_request_check,json=requireLocalRequestCheck,proto3" json:"require_local_request_check,omitempty"` 89 | K uint32 `protobuf:"varint,7,opt,name=k,proto3" json:"k,omitempty"` // Number of decryption shares required for assembling plaintext. 90 | } 91 | 92 | func (x *ReportingPluginConfig) Reset() { 93 | *x = ReportingPluginConfig{} 94 | if protoimpl.UnsafeEnabled { 95 | mi := &file_config_config_types_proto_msgTypes[1] 96 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 97 | ms.StoreMessageInfo(mi) 98 | } 99 | } 100 | 101 | func (x *ReportingPluginConfig) String() string { 102 | return protoimpl.X.MessageStringOf(x) 103 | } 104 | 105 | func (*ReportingPluginConfig) ProtoMessage() {} 106 | 107 | func (x *ReportingPluginConfig) ProtoReflect() protoreflect.Message { 108 | mi := &file_config_config_types_proto_msgTypes[1] 109 | if protoimpl.UnsafeEnabled && x != nil { 110 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 111 | if ms.LoadMessageInfo() == nil { 112 | ms.StoreMessageInfo(mi) 113 | } 114 | return ms 115 | } 116 | return mi.MessageOf(x) 117 | } 118 | 119 | // Deprecated: Use ReportingPluginConfig.ProtoReflect.Descriptor instead. 120 | func (*ReportingPluginConfig) Descriptor() ([]byte, []int) { 121 | return file_config_config_types_proto_rawDescGZIP(), []int{1} 122 | } 123 | 124 | func (x *ReportingPluginConfig) GetMaxQueryLengthBytes() uint32 { 125 | if x != nil { 126 | return x.MaxQueryLengthBytes 127 | } 128 | return 0 129 | } 130 | 131 | func (x *ReportingPluginConfig) GetMaxObservationLengthBytes() uint32 { 132 | if x != nil { 133 | return x.MaxObservationLengthBytes 134 | } 135 | return 0 136 | } 137 | 138 | func (x *ReportingPluginConfig) GetMaxReportLengthBytes() uint32 { 139 | if x != nil { 140 | return x.MaxReportLengthBytes 141 | } 142 | return 0 143 | } 144 | 145 | func (x *ReportingPluginConfig) GetRequestCountLimit() uint32 { 146 | if x != nil { 147 | return x.RequestCountLimit 148 | } 149 | return 0 150 | } 151 | 152 | func (x *ReportingPluginConfig) GetRequestTotalBytesLimit() uint32 { 153 | if x != nil { 154 | return x.RequestTotalBytesLimit 155 | } 156 | return 0 157 | } 158 | 159 | func (x *ReportingPluginConfig) GetRequireLocalRequestCheck() bool { 160 | if x != nil { 161 | return x.RequireLocalRequestCheck 162 | } 163 | return false 164 | } 165 | 166 | func (x *ReportingPluginConfig) GetK() uint32 { 167 | if x != nil { 168 | return x.K 169 | } 170 | return 0 171 | } 172 | 173 | var File_config_config_types_proto protoreflect.FileDescriptor 174 | 175 | var file_config_config_types_proto_rawDesc = []byte{ 176 | 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 177 | 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x63, 0x6f, 0x6e, 178 | 0x66, 0x69, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x5e, 0x0a, 0x17, 0x4f, 0x72, 0x61, 179 | 0x63, 0x6c, 0x65, 0x49, 0x44, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x49, 180 | 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x5f, 0x69, 181 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x49, 182 | 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x69, 183 | 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x53, 184 | 0x68, 0x61, 0x72, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xfc, 0x02, 0x0a, 0x15, 0x52, 0x65, 185 | 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 186 | 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 187 | 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 188 | 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x65, 0x6e, 189 | 0x67, 0x74, 0x68, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x6d, 0x61, 0x78, 0x5f, 190 | 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 191 | 0x74, 0x68, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 192 | 0x6d, 0x61, 0x78, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 193 | 0x6e, 0x67, 0x74, 0x68, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 194 | 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x5f, 0x62, 195 | 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x52, 196 | 0x65, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x79, 0x74, 0x65, 0x73, 197 | 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 198 | 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x72, 199 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 200 | 0x12, 0x39, 0x0a, 0x19, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 201 | 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 202 | 0x01, 0x28, 0x0d, 0x52, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x6f, 0x74, 0x61, 203 | 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x3d, 0x0a, 0x1b, 0x72, 204 | 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x71, 205 | 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 206 | 0x52, 0x18, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 207 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, 208 | 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x01, 0x6b, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x63, 209 | 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 210 | } 211 | 212 | var ( 213 | file_config_config_types_proto_rawDescOnce sync.Once 214 | file_config_config_types_proto_rawDescData = file_config_config_types_proto_rawDesc 215 | ) 216 | 217 | func file_config_config_types_proto_rawDescGZIP() []byte { 218 | file_config_config_types_proto_rawDescOnce.Do(func() { 219 | file_config_config_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_config_types_proto_rawDescData) 220 | }) 221 | return file_config_config_types_proto_rawDescData 222 | } 223 | 224 | var file_config_config_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 225 | var file_config_config_types_proto_goTypes = []interface{}{ 226 | (*OracleIDtoKeyShareIndex)(nil), // 0: config_types.OracleIDtoKeyShareIndex 227 | (*ReportingPluginConfig)(nil), // 1: config_types.ReportingPluginConfig 228 | } 229 | var file_config_config_types_proto_depIdxs = []int32{ 230 | 0, // [0:0] is the sub-list for method output_type 231 | 0, // [0:0] is the sub-list for method input_type 232 | 0, // [0:0] is the sub-list for extension type_name 233 | 0, // [0:0] is the sub-list for extension extendee 234 | 0, // [0:0] is the sub-list for field type_name 235 | } 236 | 237 | func init() { file_config_config_types_proto_init() } 238 | func file_config_config_types_proto_init() { 239 | if File_config_config_types_proto != nil { 240 | return 241 | } 242 | if !protoimpl.UnsafeEnabled { 243 | file_config_config_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 244 | switch v := v.(*OracleIDtoKeyShareIndex); i { 245 | case 0: 246 | return &v.state 247 | case 1: 248 | return &v.sizeCache 249 | case 2: 250 | return &v.unknownFields 251 | default: 252 | return nil 253 | } 254 | } 255 | file_config_config_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 256 | switch v := v.(*ReportingPluginConfig); i { 257 | case 0: 258 | return &v.state 259 | case 1: 260 | return &v.sizeCache 261 | case 2: 262 | return &v.unknownFields 263 | default: 264 | return nil 265 | } 266 | } 267 | } 268 | type x struct{} 269 | out := protoimpl.TypeBuilder{ 270 | File: protoimpl.DescBuilder{ 271 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 272 | RawDescriptor: file_config_config_types_proto_rawDesc, 273 | NumEnums: 0, 274 | NumMessages: 2, 275 | NumExtensions: 0, 276 | NumServices: 0, 277 | }, 278 | GoTypes: file_config_config_types_proto_goTypes, 279 | DependencyIndexes: file_config_config_types_proto_depIdxs, 280 | MessageInfos: file_config_config_types_proto_msgTypes, 281 | }.Build() 282 | File_config_config_types_proto = out.File 283 | file_config_config_types_proto_rawDesc = nil 284 | file_config_config_types_proto_goTypes = nil 285 | file_config_config_types_proto_depIdxs = nil 286 | } 287 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/config/config_types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./;config"; 4 | 5 | package config_types; 6 | 7 | message OracleIDtoKeyShareIndex { 8 | uint32 oracle_id = 1; 9 | uint32 key_share_index = 2; 10 | } 11 | 12 | message ReportingPluginConfig { 13 | uint32 max_query_length_bytes = 1; 14 | uint32 max_observation_length_bytes = 2; 15 | uint32 max_report_length_bytes = 3; 16 | uint32 request_count_limit = 4; 17 | uint32 request_total_bytes_limit = 5; 18 | bool require_local_request_check = 6; 19 | uint32 k = 7; // Number of decryption shares required for assembling plaintext. 20 | } -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/config/mocks/config_parser.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.28.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | config "github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin/config" 7 | mock "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | // ConfigParser is an autogenerated mock type for the ConfigParser type 11 | type ConfigParser struct { 12 | mock.Mock 13 | } 14 | 15 | // ParseConfig provides a mock function with given fields: offchainConfig 16 | func (_m *ConfigParser) ParseConfig(offchainConfig []byte) (*config.ReportingPluginConfigWrapper, error) { 17 | ret := _m.Called(offchainConfig) 18 | 19 | var r0 *config.ReportingPluginConfigWrapper 20 | var r1 error 21 | if rf, ok := ret.Get(0).(func([]byte) (*config.ReportingPluginConfigWrapper, error)); ok { 22 | return rf(offchainConfig) 23 | } 24 | if rf, ok := ret.Get(0).(func([]byte) *config.ReportingPluginConfigWrapper); ok { 25 | r0 = rf(offchainConfig) 26 | } else { 27 | if ret.Get(0) != nil { 28 | r0 = ret.Get(0).(*config.ReportingPluginConfigWrapper) 29 | } 30 | } 31 | 32 | if rf, ok := ret.Get(1).(func([]byte) error); ok { 33 | r1 = rf(offchainConfig) 34 | } else { 35 | r1 = ret.Error(1) 36 | } 37 | 38 | return r0, r1 39 | } 40 | 41 | type mockConstructorTestingTNewConfigParser interface { 42 | mock.TestingT 43 | Cleanup(func()) 44 | } 45 | 46 | // NewConfigParser creates a new instance of ConfigParser. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 47 | func NewConfigParser(t mockConstructorTestingTNewConfigParser) *ConfigParser { 48 | mock := &ConfigParser{} 49 | mock.Mock.Test(t) 50 | 51 | t.Cleanup(func() { mock.AssertExpectations(t) }) 52 | 53 | return mock 54 | } 55 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/decryption.go: -------------------------------------------------------------------------------- 1 | package decryptionplugin 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/smartcontractkit/libocr/commontypes" 10 | "github.com/smartcontractkit/libocr/offchainreporting2/types" 11 | "github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin/config" 12 | "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" 13 | "google.golang.org/protobuf/proto" 14 | ) 15 | 16 | type DecryptionReportingPluginFactory struct { 17 | DecryptionQueue DecryptionQueuingService 18 | ConfigParser config.ConfigParser 19 | PublicKey *tdh2easy.PublicKey 20 | PrivKeyShare *tdh2easy.PrivateShare 21 | OracleToKeyShare map[commontypes.OracleID]int 22 | Logger commontypes.Logger 23 | } 24 | 25 | type decryptionPlugin struct { 26 | logger commontypes.Logger 27 | decryptionQueue DecryptionQueuingService 28 | publicKey *tdh2easy.PublicKey 29 | privKeyShare *tdh2easy.PrivateShare 30 | oracleToKeyShare map[commontypes.OracleID]int 31 | genericConfig *types.ReportingPluginConfig 32 | specificConfig *config.ReportingPluginConfigWrapper 33 | } 34 | 35 | // NewReportingPlugin complies with ReportingPluginFactory. 36 | func (f DecryptionReportingPluginFactory) NewReportingPlugin(ctx context.Context, rpConfig types.ReportingPluginConfig) (types.ReportingPlugin, types.ReportingPluginInfo, error) { 37 | pluginConfig, err := f.ConfigParser.ParseConfig(rpConfig.OffchainConfig) 38 | if err != nil { 39 | return nil, types.ReportingPluginInfo{}, 40 | fmt.Errorf("unable to decode reporting plugin config: %w", err) 41 | } 42 | 43 | // The number of decryption shares K needed to reconstruct the plaintext should satisfy FF+1 liveness is not always satisfied as the leader might 48 | // include an observation from a malicious party, whose decryption share is invalid. 49 | // However, this configuration that favours safety over liveness might be desirable in certain use cases. 50 | if int(pluginConfig.Config.K) <= rpConfig.F || int(pluginConfig.Config.K) > 2*rpConfig.F+1 { 51 | return nil, types.ReportingPluginInfo{}, 52 | fmt.Errorf("invalid configuration with K=%d and F=%d: decryption threshold K must satisfy F < K <= 2F+1", pluginConfig.Config.K, rpConfig.F) 53 | } 54 | 55 | info := types.ReportingPluginInfo{ 56 | Name: "ThresholdDecryption", 57 | UniqueReports: false, // Aggregating any k valid decryption shares results in the same plaintext. Must match setting in OCR2Base.sol. 58 | // TODO calculate limits based on the maximum size of the plaintext and ciphertextID 59 | Limits: types.ReportingPluginLimits{ 60 | MaxQueryLength: int(pluginConfig.Config.GetMaxQueryLengthBytes()), 61 | MaxObservationLength: int(pluginConfig.Config.GetMaxObservationLengthBytes()), 62 | MaxReportLength: int(pluginConfig.Config.GetMaxReportLengthBytes()), 63 | }, 64 | } 65 | 66 | plugin := decryptionPlugin{ 67 | f.Logger, 68 | f.DecryptionQueue, 69 | f.PublicKey, 70 | f.PrivKeyShare, 71 | f.OracleToKeyShare, 72 | &rpConfig, 73 | pluginConfig, 74 | } 75 | 76 | return &plugin, info, nil 77 | } 78 | 79 | // Query creates a query with the oldest pending decryption requests. 80 | func (dp *decryptionPlugin) Query(ctx context.Context, ts types.ReportTimestamp) (types.Query, error) { 81 | dp.logger.Debug("DecryptionReporting Query: start", commontypes.LogFields{ 82 | "epoch": ts.Epoch, 83 | "round": ts.Round, 84 | }) 85 | 86 | decryptionRequests := dp.decryptionQueue.GetRequests( 87 | int(dp.specificConfig.Config.RequestCountLimit), 88 | int(dp.specificConfig.Config.RequestTotalBytesLimit), 89 | ) 90 | 91 | queryProto := Query{} 92 | ciphertextIDs := make(map[string]bool) 93 | allIDs := []string{} 94 | for _, request := range decryptionRequests { 95 | if _, ok := ciphertextIDs[string(request.CiphertextId)]; ok { 96 | dp.logger.Error("DecryptionReporting Query: duplicate request, skipping it", commontypes.LogFields{ 97 | "ciphertextID": request.CiphertextId.String(), 98 | }) 99 | continue 100 | } 101 | ciphertextIDs[string(request.CiphertextId)] = true 102 | 103 | ciphertext := &tdh2easy.Ciphertext{} 104 | if err := ciphertext.UnmarshalVerify(request.Ciphertext, dp.publicKey); err != nil { 105 | dp.decryptionQueue.SetResult(request.CiphertextId, nil, ErrUnmarshalling) 106 | dp.logger.Error("DecryptionReporting Query: cannot unmarshal the ciphertext, skipping it", commontypes.LogFields{ 107 | "error": err, 108 | "ciphertextID": request.CiphertextId.String(), 109 | }) 110 | continue 111 | } 112 | queryProto.DecryptionRequests = append(queryProto.GetDecryptionRequests(), &CiphertextWithID{ 113 | CiphertextId: request.CiphertextId, 114 | Ciphertext: request.Ciphertext, 115 | }) 116 | allIDs = append(allIDs, request.CiphertextId.String()) 117 | } 118 | 119 | dp.logger.Debug("DecryptionReporting Query: end", commontypes.LogFields{ 120 | "epoch": ts.Epoch, 121 | "round": ts.Round, 122 | "queryLen": len(queryProto.DecryptionRequests), 123 | "ciphertextIDs": allIDs, 124 | }) 125 | queryProtoBytes, err := proto.Marshal(&queryProto) 126 | if err != nil { 127 | return nil, fmt.Errorf("cannot marshal query: %w", err) 128 | } 129 | return queryProtoBytes, nil 130 | } 131 | 132 | // Observation creates a decryption share for each request in the query. 133 | // If dp.specificConfig.Config.LocalRequest is true, then the oracle 134 | // only creates a decryption share for the decryption requests which it has locally. 135 | func (dp *decryptionPlugin) Observation(ctx context.Context, ts types.ReportTimestamp, query types.Query) (types.Observation, error) { 136 | dp.logger.Debug("DecryptionReporting Observation: start", commontypes.LogFields{ 137 | "epoch": ts.Epoch, 138 | "round": ts.Round, 139 | }) 140 | 141 | queryProto := &Query{} 142 | if err := proto.Unmarshal(query, queryProto); err != nil { 143 | return nil, fmt.Errorf("cannot unmarshal query: %w", err) 144 | } 145 | 146 | observationProto := Observation{} 147 | ciphertextIDs := make(map[string]bool) 148 | decryptedIDs := []string{} 149 | for _, request := range queryProto.DecryptionRequests { 150 | ciphertextId := CiphertextId(request.CiphertextId) 151 | if _, ok := ciphertextIDs[string(ciphertextId)]; ok { 152 | dp.logger.Error("DecryptionReporting Observation: duplicate request in the same query, the leader is faulty", commontypes.LogFields{ 153 | "ciphertextID": ciphertextId.String(), 154 | }) 155 | return nil, fmt.Errorf("duplicate request in the same query") 156 | } 157 | ciphertextIDs[string(ciphertextId)] = true 158 | 159 | ciphertext := &tdh2easy.Ciphertext{} 160 | ciphertextBytes := request.Ciphertext 161 | if err := ciphertext.UnmarshalVerify(ciphertextBytes, dp.publicKey); err != nil { 162 | dp.logger.Error("DecryptionReporting Observation: cannot unmarshal and verify the ciphertext, the leader is faulty", commontypes.LogFields{ 163 | "error": err, 164 | "ciphertextID": ciphertextId.String(), 165 | }) 166 | return nil, fmt.Errorf("cannot unmarshal and verify the ciphertext: %w", err) 167 | } 168 | if dp.specificConfig.Config.RequireLocalRequestCheck { 169 | queueCiphertextBytes, err := dp.decryptionQueue.GetCiphertext(ciphertextId) 170 | if err != nil && errors.Is(err, ErrNotFound) { 171 | dp.logger.Warn("DecryptionReporting Observation: cannot find ciphertext locally, skipping it", commontypes.LogFields{ 172 | "error": err, 173 | "ciphertextID": ciphertextId.String(), 174 | }) 175 | continue 176 | } else if err != nil { 177 | dp.logger.Error("DecryptionReporting Observation: failed when looking for ciphertext locally, skipping it", commontypes.LogFields{ 178 | "error": err, 179 | "ciphertextID": ciphertextId.String(), 180 | }) 181 | continue 182 | } 183 | if !bytes.Equal(queueCiphertextBytes, ciphertextBytes) { 184 | dp.logger.Error("DecryptionReporting Observation: local ciphertext does not match the query ciphertext, skipping it", commontypes.LogFields{ 185 | "ciphertextID": ciphertextId.String(), 186 | }) 187 | continue 188 | } 189 | } 190 | 191 | decryptionShare, err := tdh2easy.Decrypt(ciphertext, dp.privKeyShare) 192 | if err != nil { 193 | dp.decryptionQueue.SetResult(ciphertextId, nil, ErrDecryption) 194 | dp.logger.Error("DecryptionReporting Observation: cannot decrypt the ciphertext with the private key share", commontypes.LogFields{ 195 | "error": err, 196 | "ciphertextID": ciphertextId.String(), 197 | }) 198 | continue 199 | } 200 | decryptionShareBytes, err := decryptionShare.Marshal() 201 | if err != nil { 202 | dp.logger.Error("DecryptionReporting Observation: cannot marshal the decryption share, skipping it", commontypes.LogFields{ 203 | "error": err, 204 | "ciphertextID": ciphertextId.String(), 205 | }) 206 | continue 207 | } 208 | observationProto.DecryptionShares = append(observationProto.DecryptionShares, &DecryptionShareWithID{ 209 | CiphertextId: ciphertextId, 210 | DecryptionShare: decryptionShareBytes, 211 | }) 212 | decryptedIDs = append(decryptedIDs, ciphertextId.String()) 213 | } 214 | 215 | dp.logger.Debug("DecryptionReporting Observation: end", commontypes.LogFields{ 216 | "epoch": ts.Epoch, 217 | "round": ts.Round, 218 | "decryptedRequests": len(observationProto.DecryptionShares), 219 | "totalRequests": len(queryProto.DecryptionRequests), 220 | "ciphertextIDs": decryptedIDs, 221 | }) 222 | observationProtoBytes, err := proto.Marshal(&observationProto) 223 | if err != nil { 224 | return nil, fmt.Errorf("cannot marshal observation: %w", err) 225 | } 226 | return observationProtoBytes, nil 227 | } 228 | 229 | // Report aggregates decryption shares from Observations to derive the plaintext. 230 | func (dp *decryptionPlugin) Report(ctx context.Context, ts types.ReportTimestamp, query types.Query, obs []types.AttributedObservation) (bool, types.Report, error) { 231 | dp.logger.Debug("DecryptionReporting Report: start", commontypes.LogFields{ 232 | "epoch": ts.Epoch, 233 | "round": ts.Round, 234 | "nObservations": len(obs), 235 | }) 236 | 237 | queryProto := &Query{} 238 | if err := proto.Unmarshal(query, queryProto); err != nil { 239 | return false, nil, fmt.Errorf("cannot unmarshal query: %w ", err) 240 | } 241 | ciphertexts := make(map[string]*tdh2easy.Ciphertext) 242 | for _, request := range queryProto.DecryptionRequests { 243 | ciphertextId := CiphertextId(request.CiphertextId) 244 | ciphertext := &tdh2easy.Ciphertext{} 245 | if err := ciphertext.UnmarshalVerify(request.Ciphertext, dp.publicKey); err != nil { 246 | dp.logger.Error("DecryptionReporting Report: cannot unmarshal and verify the ciphertext, the leader is faulty", commontypes.LogFields{ 247 | "error": err, 248 | "ciphertextID": ciphertextId.String(), 249 | }) 250 | return false, nil, fmt.Errorf("cannot unmarshal and verify the ciphertext: %w", err) 251 | } 252 | ciphertexts[string(ciphertextId)] = ciphertext 253 | } 254 | 255 | validDecryptionShares := make(map[string][]*tdh2easy.DecryptionShare) 256 | for _, ob := range obs { 257 | observationProto := &Observation{} 258 | if err := proto.Unmarshal(ob.Observation, observationProto); err != nil { 259 | dp.logger.Error("DecryptionReporting Report: cannot unmarshal observation, skipping it", commontypes.LogFields{ 260 | "error": err, 261 | "observer": ob.Observer, 262 | }) 263 | continue 264 | } 265 | 266 | ciphertextIDs := make(map[string]bool) 267 | for _, decryptionShareWithID := range observationProto.DecryptionShares { 268 | ciphertextId := CiphertextId(decryptionShareWithID.CiphertextId) 269 | ciphertextIdRawStr := string(ciphertextId) 270 | if _, ok := ciphertextIDs[ciphertextIdRawStr]; ok { 271 | dp.logger.Error("DecryptionReporting Report: the observation has multiple decryption shares for the same ciphertext id", commontypes.LogFields{ 272 | "ciphertextID": ciphertextId.String(), 273 | "observer": ob.Observer, 274 | }) 275 | continue 276 | } 277 | ciphertextIDs[ciphertextIdRawStr] = true 278 | 279 | ciphertext, ok := ciphertexts[ciphertextIdRawStr] 280 | if !ok { 281 | dp.logger.Error("DecryptionReporting Report: there is not ciphertext in the query with matching id", commontypes.LogFields{ 282 | "ciphertextID": ciphertextId.String(), 283 | "observer": ob.Observer, 284 | }) 285 | continue 286 | } 287 | 288 | validDecryptionShare, err := dp.getValidDecryptionShare(ob.Observer, 289 | ciphertext, decryptionShareWithID.DecryptionShare) 290 | if err != nil { 291 | dp.logger.Error("DecryptionReporting Report: invalid decryption share", commontypes.LogFields{ 292 | "error": err, 293 | "ciphertextID": ciphertextId.String(), 294 | "observer": ob.Observer, 295 | }) 296 | continue 297 | } 298 | 299 | if len(validDecryptionShares[ciphertextIdRawStr]) < int(dp.specificConfig.Config.K) { 300 | validDecryptionShares[ciphertextIdRawStr] = append(validDecryptionShares[ciphertextIdRawStr], validDecryptionShare) 301 | } else { 302 | dp.logger.Trace("DecryptionReporting Report: we have already k valid decryption shares", commontypes.LogFields{ 303 | "ciphertextID": ciphertextId.String(), 304 | "observer": ob.Observer, 305 | }) 306 | } 307 | } 308 | } 309 | 310 | reportProto := Report{} 311 | for _, request := range queryProto.DecryptionRequests { 312 | ciphertextId := CiphertextId(request.CiphertextId) 313 | ciphertextIdRawStr := string(ciphertextId) 314 | decrShares, ok := validDecryptionShares[ciphertextIdRawStr] 315 | if !ok { 316 | // Request not included in any observation in the current round. 317 | dp.logger.Debug("DecryptionReporting Report: ciphertextID was not included in any observation in the current round", commontypes.LogFields{ 318 | "ciphertextID": ciphertextId.String(), 319 | }) 320 | continue 321 | } 322 | ciphertext, ok := ciphertexts[ciphertextIdRawStr] 323 | if !ok { 324 | dp.logger.Error("DecryptionReporting Report: there is not ciphertext in the query with matching id, skipping aggregation of decryption shares", commontypes.LogFields{ 325 | "ciphertextID": ciphertextId.String(), 326 | }) 327 | continue 328 | } 329 | 330 | if len(decrShares) < int(dp.specificConfig.Config.K) { 331 | dp.logger.Debug("DecryptionReporting Report: not enough valid decryption shares after processing all observations, skipping aggregation of decryption shares", commontypes.LogFields{ 332 | "ciphertextID": ciphertextId.String(), 333 | }) 334 | continue 335 | } 336 | 337 | plaintext, err := tdh2easy.Aggregate(ciphertext, decrShares, dp.genericConfig.N) 338 | if err != nil { 339 | dp.decryptionQueue.SetResult(ciphertextId, nil, ErrAggregation) 340 | dp.logger.Error("DecryptionReporting Report: cannot aggregate decryption shares", commontypes.LogFields{ 341 | "error": err, 342 | "ciphertextID": ciphertextId.String(), 343 | }) 344 | continue 345 | } 346 | 347 | dp.logger.Debug("DecryptionReporting Report: plaintext aggregated successfully", commontypes.LogFields{ 348 | "epoch": ts.Epoch, 349 | "round": ts.Round, 350 | "ciphertextID": ciphertextId.String(), 351 | }) 352 | reportProto.ProcessedDecryptedRequests = append(reportProto.ProcessedDecryptedRequests, &ProcessedDecryptionRequest{ 353 | CiphertextId: ciphertextId, 354 | Plaintext: plaintext, 355 | }) 356 | } 357 | 358 | dp.logger.Debug("DecryptionReporting Report: end", commontypes.LogFields{ 359 | "epoch": ts.Epoch, 360 | "round": ts.Round, 361 | "aggregatedDecryptionShares": len(reportProto.ProcessedDecryptedRequests), 362 | "reporting": len(reportProto.ProcessedDecryptedRequests) > 0, 363 | }) 364 | 365 | if len(reportProto.ProcessedDecryptedRequests) == 0 { 366 | return false, nil, nil 367 | } 368 | 369 | reportBytes, err := proto.Marshal(&reportProto) 370 | if err != nil { 371 | return false, nil, fmt.Errorf("cannot marshal report: %w", err) 372 | } 373 | return true, reportBytes, nil 374 | } 375 | 376 | func (dp *decryptionPlugin) getValidDecryptionShare(observer commontypes.OracleID, 377 | ciphertext *tdh2easy.Ciphertext, decryptionShareBytes []byte) (*tdh2easy.DecryptionShare, error) { 378 | decryptionShare := &tdh2easy.DecryptionShare{} 379 | if err := decryptionShare.Unmarshal(decryptionShareBytes); err != nil { 380 | return nil, fmt.Errorf("cannot unmarshal decryption share: %w", err) 381 | } 382 | 383 | expectedKeyShareIndex, ok := dp.oracleToKeyShare[observer] 384 | if !ok { 385 | return nil, fmt.Errorf("invalid observer ID") 386 | } 387 | 388 | if expectedKeyShareIndex != decryptionShare.Index() { 389 | return nil, fmt.Errorf("invalid decryption share index: expected %d and got %d", expectedKeyShareIndex, decryptionShare.Index()) 390 | } 391 | 392 | if err := tdh2easy.VerifyShare(ciphertext, dp.publicKey, decryptionShare); err != nil { 393 | return nil, fmt.Errorf("decryption share verification failed: %w", err) 394 | } 395 | return decryptionShare, nil 396 | } 397 | 398 | // ShouldAcceptFinalizedReport updates the decryption queue. 399 | // Returns always false as the report will not be transmitted on-chain. 400 | func (dp *decryptionPlugin) ShouldAcceptFinalizedReport(ctx context.Context, ts types.ReportTimestamp, report types.Report) (bool, error) { 401 | dp.logger.Debug("DecryptionReporting ShouldAcceptFinalizedReport: start", commontypes.LogFields{ 402 | "epoch": ts.Epoch, 403 | "round": ts.Round, 404 | }) 405 | 406 | reportProto := &Report{} 407 | if err := proto.Unmarshal(report, reportProto); err != nil { 408 | return false, fmt.Errorf("cannot unmarshal report: %w", err) 409 | } 410 | 411 | for _, item := range reportProto.ProcessedDecryptedRequests { 412 | dp.decryptionQueue.SetResult(item.CiphertextId, item.Plaintext, nil) 413 | } 414 | 415 | dp.logger.Debug("DecryptionReporting ShouldAcceptFinalizedReport: end", commontypes.LogFields{ 416 | "epoch": ts.Epoch, 417 | "round": ts.Round, 418 | "accepting": false, 419 | }) 420 | 421 | return false, nil 422 | } 423 | 424 | // ShouldTransmitAcceptedReport is a no-op 425 | func (dp *decryptionPlugin) ShouldTransmitAcceptedReport(ctx context.Context, ts types.ReportTimestamp, report types.Report) (bool, error) { 426 | return false, nil 427 | } 428 | 429 | // Close complies with ReportingPlugin 430 | func (dp *decryptionPlugin) Close() error { 431 | dp.logger.Debug("DecryptionReporting Close", nil) 432 | return nil 433 | } 434 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/decryption_test.go: -------------------------------------------------------------------------------- 1 | package decryptionplugin 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | "sort" 10 | "testing" 11 | 12 | "github.com/google/go-cmp/cmp" 13 | "github.com/google/go-cmp/cmp/cmpopts" 14 | "github.com/smartcontractkit/libocr/commontypes" 15 | "github.com/smartcontractkit/libocr/offchainreporting2/types" 16 | "github.com/smartcontractkit/libocr/ragep2p/loggers" 17 | "github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin/config" 18 | "github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin/config/mocks" 19 | "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" 20 | "github.com/stretchr/testify/mock" 21 | "github.com/stretchr/testify/require" 22 | "google.golang.org/protobuf/proto" 23 | ) 24 | 25 | // dummyLogger implements a dummy logger for testing only. 26 | type dummyLogger struct{} 27 | 28 | func (l dummyLogger) Trace(msg string, fields commontypes.LogFields) {} 29 | func (l dummyLogger) Debug(msg string, fields commontypes.LogFields) {} 30 | func (l dummyLogger) Info(msg string, fields commontypes.LogFields) {} 31 | func (l dummyLogger) Warn(msg string, fields commontypes.LogFields) {} 32 | func (l dummyLogger) Error(msg string, fields commontypes.LogFields) {} 33 | func (l dummyLogger) Critical(msg string, fields commontypes.LogFields) {} 34 | 35 | // queue implements the DecryptionQueuingService interface. 36 | type queue struct { 37 | q []DecryptionRequest 38 | res [][]byte 39 | } 40 | 41 | func (q *queue) GetRequests(requestCountLimit int, totalBytesLimit int) []DecryptionRequest { 42 | stop := 0 43 | for i, tot := 0, 0; i < len(q.q) && i < requestCountLimit; i++ { 44 | tot += len(q.q[i].Ciphertext) 45 | if tot > totalBytesLimit { 46 | break 47 | } 48 | stop++ 49 | } 50 | out := q.q[:stop] 51 | q.q = q.q[stop:] 52 | return out 53 | } 54 | 55 | func (q *queue) GetCiphertext(ciphertextId CiphertextId) ([]byte, error) { 56 | if bytes.Equal([]byte("please fail"), ciphertextId) { 57 | return nil, fmt.Errorf("some error") 58 | } 59 | for _, e := range q.q { 60 | if bytes.Equal(ciphertextId, e.CiphertextId) { 61 | return e.Ciphertext, nil 62 | } 63 | } 64 | return nil, ErrNotFound 65 | } 66 | 67 | func (q *queue) SetResult(ciphertextId CiphertextId, plaintext []byte, err error) { 68 | q.res = append(q.res, ciphertextId) 69 | q.res = append(q.res, plaintext) 70 | } 71 | 72 | func makeConfig(t *testing.T, c *config.ReportingPluginConfig) types.ReportingPluginConfig { 73 | t.Helper() 74 | conf, err := config.EncodeReportingPluginConfig(&config.ReportingPluginConfigWrapper{ 75 | Config: c, 76 | }) 77 | if err != nil { 78 | t.Fatalf("EncodeReportingPluginConfig: %v", err) 79 | } 80 | return types.ReportingPluginConfig{OffchainConfig: conf} 81 | 82 | } 83 | 84 | func TestNewReportingPlugin(t *testing.T) { 85 | _, pk, sh, err := tdh2easy.GenerateKeys(1, 1) 86 | if err != nil { 87 | t.Fatalf("GenerateKeys: %v", err) 88 | } 89 | for _, tc := range []struct { 90 | name string 91 | conf types.ReportingPluginConfig 92 | err error 93 | }{ 94 | { 95 | name: "ok", 96 | conf: makeConfig(t, &config.ReportingPluginConfig{ 97 | MaxQueryLengthBytes: 1, 98 | MaxObservationLengthBytes: 2, 99 | MaxReportLengthBytes: 3, 100 | K: 1, 101 | }), 102 | }, 103 | { 104 | name: "ok minimal", 105 | conf: makeConfig(t, &config.ReportingPluginConfig{ 106 | K: 1, 107 | }), 108 | }, 109 | { 110 | name: "broken conf", 111 | conf: types.ReportingPluginConfig{ 112 | OffchainConfig: []byte("broken"), 113 | }, 114 | err: cmpopts.AnyError, 115 | }, 116 | { 117 | name: "invalid threshold", 118 | conf: makeConfig(t, &config.ReportingPluginConfig{ 119 | K: 0, 120 | }), 121 | err: cmpopts.AnyError, 122 | }, 123 | } { 124 | t.Run(tc.name, func(t *testing.T) { 125 | ctx, cancel := context.WithCancel(context.Background()) 126 | t.Cleanup(cancel) 127 | factory := DecryptionReportingPluginFactory{ 128 | Logger: dummyLogger{}, 129 | PublicKey: pk, 130 | PrivKeyShare: sh[0], 131 | ConfigParser: &config.DefaultConfigParser{}, 132 | } 133 | plugin, info, err := factory.NewReportingPlugin(ctx, tc.conf) 134 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 135 | t.Fatalf("err=%v, want=%v", err, tc.err) 136 | } else if err != nil { 137 | return 138 | } 139 | conf, err := config.DecodeReportingPluginConfig(tc.conf.OffchainConfig) 140 | if err != nil { 141 | t.Fatalf("DecodeReportingPluginConfig: %v", err) 142 | } 143 | if a, b := info.Limits.MaxQueryLength, int(conf.Config.MaxQueryLengthBytes); a != b { 144 | t.Errorf("info.Limits.MaxQueryLength=%v, want=%v", a, b) 145 | 146 | } 147 | if a, b := info.Limits.MaxObservationLength, int(conf.Config.MaxObservationLengthBytes); a != b { 148 | t.Errorf("info.Limits.MaxObservationLength=%v, want=%v", a, b) 149 | } 150 | if a, b := info.Limits.MaxReportLength, int(conf.Config.MaxReportLengthBytes); a != b { 151 | t.Errorf("info.Limits.MaxReportLength=%v, want=%v", a, b) 152 | } 153 | p := plugin.(*decryptionPlugin) 154 | if !reflect.DeepEqual(p.publicKey, pk) { 155 | t.Errorf("got pubkey %v, want %v", p.publicKey, pk) 156 | } 157 | if !reflect.DeepEqual(p.privKeyShare, sh[0]) { 158 | t.Errorf("got privkey %v, want %v", p.privKeyShare, sh[0]) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func TestGetValidDecryptionShare(t *testing.T) { 165 | _, pk, sh, err := tdh2easy.GenerateKeys(1, 2) 166 | if err != nil { 167 | t.Fatalf("GenerateKeys: %v", err) 168 | } 169 | c, err := tdh2easy.Encrypt(pk, []byte("msg")) 170 | if err != nil { 171 | t.Fatalf("Encrypt: %v", err) 172 | } 173 | ds, err := tdh2easy.Decrypt(c, sh[1]) 174 | if err != nil { 175 | t.Fatalf("Decrypt: %v", err) 176 | } 177 | dsRaw, err := ds.Marshal() 178 | if err != nil { 179 | t.Fatalf("Marshal: %v", err) 180 | } 181 | c2, err := tdh2easy.Encrypt(pk, []byte("msg2")) 182 | if err != nil { 183 | t.Fatalf("Encrypt: %v", err) 184 | } 185 | dp := &decryptionPlugin{ 186 | oracleToKeyShare: map[commontypes.OracleID]int{ 187 | 10: 0, 188 | 123: 1, 189 | }, 190 | publicKey: pk, 191 | } 192 | for _, tc := range []struct { 193 | name string 194 | id commontypes.OracleID 195 | c *tdh2easy.Ciphertext 196 | share []byte 197 | err error 198 | }{ 199 | { 200 | name: "ok", 201 | id: 123, 202 | c: c, 203 | share: dsRaw, 204 | }, 205 | { 206 | name: "no oracle", 207 | id: 1, 208 | c: c, 209 | share: dsRaw, 210 | err: cmpopts.AnyError, 211 | }, 212 | { 213 | name: "wrong index", 214 | id: 10, 215 | c: c, 216 | share: dsRaw, 217 | err: cmpopts.AnyError, 218 | }, 219 | { 220 | name: "wrong share", 221 | id: 123, 222 | c: c2, 223 | share: dsRaw, 224 | err: cmpopts.AnyError, 225 | }, 226 | { 227 | name: "broken ds", 228 | id: 123, 229 | c: c, 230 | share: []byte("broken"), 231 | err: cmpopts.AnyError, 232 | }, 233 | } { 234 | t.Run(tc.name, func(t *testing.T) { 235 | got, err := dp.getValidDecryptionShare(tc.id, tc.c, tc.share) 236 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 237 | t.Fatalf("err=%v, want=%v", err, tc.err) 238 | } else if err != nil { 239 | return 240 | } 241 | if !reflect.DeepEqual(got, ds) { 242 | t.Errorf("got ds=%v, want=%v", got, ds) 243 | } 244 | }) 245 | } 246 | 247 | } 248 | 249 | func TestQuery(t *testing.T) { 250 | _, pk, _, err := tdh2easy.GenerateKeys(1, 2) 251 | if err != nil { 252 | t.Fatalf("GenerateKeys: %v", err) 253 | } 254 | ctxts := []*CiphertextWithID{} 255 | for i := 0; i < 10; i++ { 256 | id := []byte(fmt.Sprintf("%d", i)) 257 | c, err := tdh2easy.Encrypt(pk, id) 258 | if err != nil { 259 | t.Fatalf("Encrypt: %v", err) 260 | } 261 | raw, err := c.Marshal() 262 | if err != nil { 263 | t.Fatalf("Marshal: %v", err) 264 | } 265 | ctxts = append(ctxts, &CiphertextWithID{ 266 | CiphertextId: id, 267 | Ciphertext: raw, 268 | }) 269 | } 270 | for _, tc := range []struct { 271 | name string 272 | in []*CiphertextWithID 273 | want []*CiphertextWithID 274 | }{ 275 | { 276 | name: "empty", 277 | }, 278 | { 279 | name: "one", 280 | in: ctxts[:1], 281 | want: ctxts[:1], 282 | }, 283 | { 284 | name: "all", 285 | in: ctxts, 286 | want: ctxts, 287 | }, 288 | { 289 | name: "one wrong", 290 | in: append(ctxts, &CiphertextWithID{ 291 | CiphertextId: []byte("1"), 292 | Ciphertext: []byte("broken"), 293 | }), 294 | want: ctxts, 295 | }, 296 | { 297 | name: "duplicate request", 298 | in: append(ctxts, ctxts[1]), 299 | want: ctxts, 300 | }, 301 | } { 302 | t.Run(tc.name, func(t *testing.T) { 303 | q := &queue{} 304 | for _, e := range tc.in { 305 | q.q = append(q.q, DecryptionRequest{ 306 | CiphertextId: e.CiphertextId, 307 | Ciphertext: e.Ciphertext, 308 | }) 309 | } 310 | dp := &decryptionPlugin{ 311 | logger: dummyLogger{}, 312 | publicKey: pk, 313 | specificConfig: &config.ReportingPluginConfigWrapper{ 314 | Config: &config.ReportingPluginConfig{ 315 | RequestCountLimit: 999, 316 | RequestTotalBytesLimit: 999999, 317 | }, 318 | }, 319 | decryptionQueue: q, 320 | } 321 | b, err := dp.Query(context.Background(), types.ReportTimestamp{}) 322 | if err != nil { 323 | t.Fatalf("Query: %v", err) 324 | } 325 | got := Query{} 326 | if err := proto.Unmarshal(b, &got); err != nil { 327 | t.Fatalf("Unmarshal: %v", err) 328 | } 329 | if d := cmp.Diff(got.DecryptionRequests, tc.want, cmpopts.IgnoreUnexported(CiphertextWithID{})); d != "" { 330 | t.Errorf("got/want diff=%v", d) 331 | } 332 | }) 333 | } 334 | } 335 | 336 | func TestShouldAcceptFinalizedReport(t *testing.T) { 337 | r := &Report{ 338 | ProcessedDecryptedRequests: []*ProcessedDecryptionRequest{ 339 | { 340 | CiphertextId: []byte("id1"), 341 | Plaintext: []byte("p1"), 342 | }, 343 | { 344 | CiphertextId: []byte("id2"), 345 | Plaintext: []byte("p2"), 346 | }, 347 | { 348 | CiphertextId: []byte("id3"), 349 | Plaintext: []byte("p3"), 350 | }, 351 | }, 352 | } 353 | b, err := proto.Marshal(r) 354 | if err != nil { 355 | t.Fatalf("Marshal: %v", err) 356 | } 357 | for _, tc := range []struct { 358 | name string 359 | in []byte 360 | want [][]byte 361 | err error 362 | }{ 363 | { 364 | name: "empty", 365 | }, 366 | { 367 | name: "broken", 368 | in: []byte("broken"), 369 | err: cmpopts.AnyError, 370 | }, 371 | { 372 | name: "ok", 373 | in: b, 374 | want: [][]byte{[]byte("id1"), []byte("p1"), []byte("id2"), []byte("p2"), []byte("id3"), []byte("p3")}, 375 | }, 376 | } { 377 | t.Run(tc.name, func(t *testing.T) { 378 | dp := &decryptionPlugin{ 379 | logger: dummyLogger{}, 380 | decryptionQueue: &queue{}, 381 | } 382 | transmit, err := dp.ShouldAcceptFinalizedReport(context.Background(), types.ReportTimestamp{}, tc.in) 383 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 384 | t.Fatalf("err=%v, want=%v", err, tc.err) 385 | } else if err != nil { 386 | return 387 | } 388 | if transmit { 389 | t.Errorf("ShouldAcceptFinalizedReport returned true") 390 | } 391 | q := dp.decryptionQueue.(*queue) 392 | if d := cmp.Diff(q.res, tc.want); d != "" { 393 | t.Errorf("got/want diff=%v", d) 394 | } 395 | }) 396 | } 397 | } 398 | 399 | func makeQuery(t *testing.T, c []*CiphertextWithID) []byte { 400 | t.Helper() 401 | b, err := proto.Marshal(&Query{ 402 | DecryptionRequests: c, 403 | }) 404 | if err != nil { 405 | t.Fatalf("Marshal: %v", err) 406 | } 407 | return b 408 | } 409 | 410 | type ctxtWithId struct { 411 | id []byte 412 | c *tdh2easy.Ciphertext 413 | } 414 | 415 | func TestObservation(t *testing.T) { 416 | _, pk, sh, err := tdh2easy.GenerateKeys(1, 2) 417 | if err != nil { 418 | t.Fatalf("GenerateKeys: %v", err) 419 | } 420 | q := &queue{} 421 | ctxts := []*ctxtWithId{} 422 | ctxtsRaw := []*CiphertextWithID{} 423 | for i := 0; i < 10; i++ { 424 | id := []byte(fmt.Sprintf("%d", i)) 425 | c, err := tdh2easy.Encrypt(pk, id) 426 | if err != nil { 427 | t.Fatalf("Encrypt: %v", err) 428 | } 429 | raw, err := c.Marshal() 430 | if err != nil { 431 | t.Fatalf("Marshal: %v", err) 432 | } 433 | ctxtsRaw = append(ctxtsRaw, &CiphertextWithID{ 434 | CiphertextId: id, 435 | Ciphertext: raw, 436 | }) 437 | // add only 5 to the queue 438 | if i < 5 { 439 | q.q = append(q.q, DecryptionRequest{ 440 | CiphertextId: id, 441 | Ciphertext: raw, 442 | }) 443 | } 444 | ctxts = append(ctxts, &ctxtWithId{ 445 | id: id, 446 | c: c, 447 | }) 448 | } 449 | for _, tc := range []struct { 450 | name string 451 | query []byte 452 | local bool 453 | queue DecryptionQueuingService 454 | err error 455 | want []*ctxtWithId 456 | }{ 457 | { 458 | name: "broken", 459 | query: []byte("broken"), 460 | err: cmpopts.AnyError, 461 | }, 462 | { 463 | name: "empty", 464 | query: makeQuery(t, nil), 465 | }, 466 | { 467 | name: "one", 468 | query: makeQuery(t, ctxtsRaw[:1]), 469 | want: ctxts[:1], 470 | }, 471 | { 472 | name: "many", 473 | query: makeQuery(t, ctxtsRaw), 474 | want: ctxts, 475 | }, 476 | { 477 | name: "many locally queued", 478 | query: makeQuery(t, ctxtsRaw[:5]), 479 | local: true, 480 | queue: q, 481 | want: ctxts[:5], 482 | }, 483 | { 484 | name: "some locally queued, some not found", 485 | query: makeQuery(t, ctxtsRaw), 486 | local: true, 487 | queue: q, 488 | want: ctxts[:5], 489 | }, 490 | { 491 | name: "queue failing", 492 | query: makeQuery(t, append(ctxtsRaw[:5], &CiphertextWithID{ 493 | CiphertextId: []byte("please fail"), 494 | Ciphertext: ctxtsRaw[5].Ciphertext, 495 | })), 496 | local: true, 497 | queue: q, 498 | want: ctxts[:5], 499 | }, 500 | { 501 | name: "queued ciphertext mismatch", 502 | query: makeQuery(t, append(ctxtsRaw[:4], &CiphertextWithID{ 503 | CiphertextId: ctxtsRaw[4].CiphertextId, 504 | Ciphertext: ctxtsRaw[5].Ciphertext, 505 | })), 506 | local: true, 507 | queue: q, 508 | want: ctxts[:4], 509 | }, 510 | { 511 | name: "broken ciphertext", 512 | query: makeQuery(t, append(ctxtsRaw[:3], &CiphertextWithID{ 513 | CiphertextId: []byte("id"), 514 | Ciphertext: []byte("broken"), 515 | })), 516 | err: cmpopts.AnyError, 517 | }, 518 | { 519 | name: "duplicate query", 520 | query: makeQuery(t, append(ctxtsRaw[:3], ctxtsRaw[1])), 521 | err: cmpopts.AnyError, 522 | }, 523 | } { 524 | t.Run(tc.name, func(t *testing.T) { 525 | dp := &decryptionPlugin{ 526 | logger: dummyLogger{}, 527 | publicKey: pk, 528 | privKeyShare: sh[1], 529 | specificConfig: &config.ReportingPluginConfigWrapper{ 530 | Config: &config.ReportingPluginConfig{ 531 | RequireLocalRequestCheck: tc.local, 532 | }, 533 | }, 534 | decryptionQueue: tc.queue, 535 | } 536 | b, err := dp.Observation(context.Background(), types.ReportTimestamp{}, tc.query) 537 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 538 | t.Fatalf("err=%v, want=%v", err, tc.err) 539 | } else if err != nil { 540 | return 541 | } 542 | var got Observation 543 | if err := proto.Unmarshal(b, &got); err != nil { 544 | t.Fatalf("Unmarshal: %v", err) 545 | } 546 | if a, b := len(got.DecryptionShares), len(tc.want); a != b { 547 | t.Errorf("got %v dec shares, want %v", a, b) 548 | } 549 | for i := 0; i < len(got.DecryptionShares) && i < len(tc.want); i++ { 550 | if a, b := got.DecryptionShares[i].CiphertextId, tc.want[i].id; !bytes.Equal(a, b) { 551 | t.Errorf("got id=%v, want=%v", a, b) 552 | } 553 | var ds tdh2easy.DecryptionShare 554 | if err := ds.Unmarshal(got.DecryptionShares[i].DecryptionShare); err != nil { 555 | t.Errorf("Unmarshal: %v", err) 556 | continue 557 | } 558 | if ds.Index() != 1 { 559 | t.Errorf("got index=%v, want=1", ds.Index()) 560 | } 561 | if err := tdh2easy.VerifyShare(tc.want[i].c, pk, &ds); err != nil { 562 | t.Errorf("VerifyShare id=%v err=%v", tc.want[i].id, err) 563 | } 564 | } 565 | }) 566 | } 567 | } 568 | 569 | func makeObservations(t *testing.T, oracle2ids map[int][]string, id2shares map[string][][]byte) []types.AttributedObservation { 570 | t.Helper() 571 | var out []types.AttributedObservation 572 | for oracle, ids := range oracle2ids { 573 | decShares := []*DecryptionShareWithID{} 574 | for _, id := range ids { 575 | decShares = append(decShares, &DecryptionShareWithID{ 576 | CiphertextId: []byte(id), 577 | DecryptionShare: id2shares[id][oracle], 578 | }) 579 | } 580 | ob, err := proto.Marshal(&Observation{ 581 | DecryptionShares: decShares, 582 | }) 583 | if err != nil { 584 | t.Fatalf("Marshal: %v", err) 585 | } 586 | out = append(out, types.AttributedObservation{ 587 | Observer: commontypes.OracleID(oracle), 588 | Observation: ob, 589 | }) 590 | } 591 | return out 592 | } 593 | 594 | func TestReport(t *testing.T) { 595 | k := uint32(3) 596 | _, pk, sh, err := tdh2easy.GenerateKeys(int(k), 5) 597 | if err != nil { 598 | t.Fatalf("GenerateKeys: %v", err) 599 | } 600 | want := []*ProcessedDecryptionRequest{} 601 | ctxts := []*CiphertextWithID{} 602 | shares := map[string][][]byte{} 603 | // generate id-plaintext pairs, "id0"->"0", "id1"->"1", "id2"->"2" 604 | for i := 0; i < 3; i++ { 605 | id := []byte(fmt.Sprintf("id%d", i)) 606 | plain := []byte(fmt.Sprintf("%d", i)) 607 | c, err := tdh2easy.Encrypt(pk, plain) 608 | if err != nil { 609 | t.Fatalf("Encrypt: %v", err) 610 | } 611 | raw, err := c.Marshal() 612 | if err != nil { 613 | t.Fatalf("Marshal: %v", err) 614 | } 615 | ctxts = append(ctxts, &CiphertextWithID{ 616 | CiphertextId: id, 617 | Ciphertext: raw, 618 | }) 619 | want = append(want, &ProcessedDecryptionRequest{ 620 | CiphertextId: id, 621 | Plaintext: plain, 622 | }) 623 | for _, s := range sh { 624 | ds, err := tdh2easy.Decrypt(c, s) 625 | if err != nil { 626 | t.Fatalf("Decrypt: %v", err) 627 | } 628 | b, err := ds.Marshal() 629 | if err != nil { 630 | t.Fatalf("Marshal: %v", err) 631 | } 632 | shares[string(id)] = append(shares[string(id)], b) 633 | } 634 | } 635 | for _, tc := range []struct { 636 | name string 637 | query []byte 638 | obs []types.AttributedObservation 639 | err error 640 | wantProcessed bool 641 | want []*ProcessedDecryptionRequest 642 | }{ 643 | { 644 | name: "empty", 645 | query: makeQuery(t, nil), 646 | }, 647 | { 648 | name: "broken query", 649 | query: []byte("broken"), 650 | err: cmpopts.AnyError, 651 | }, 652 | { 653 | name: "broken ciphertext", 654 | query: makeQuery(t, append(ctxts, &CiphertextWithID{ 655 | CiphertextId: []byte("id"), 656 | Ciphertext: []byte("broken"), 657 | })), 658 | err: cmpopts.AnyError, 659 | }, 660 | { 661 | name: "nothing processed (no shares)", 662 | query: makeQuery(t, ctxts), 663 | }, 664 | { 665 | name: "nothing processed (no enough shares)", 666 | query: makeQuery(t, ctxts), 667 | obs: makeObservations(t, map[int][]string{ 668 | 0: {"id0", "id1", "id2"}, 669 | 1: {"id0", "id1", "id2"}, 670 | }, shares), 671 | }, 672 | { 673 | name: "one processed", 674 | query: makeQuery(t, ctxts[:1]), 675 | obs: makeObservations(t, map[int][]string{ 676 | 0: {"id0", "id1", "id2"}, 677 | 1: {"id0", "id1", "id2"}, 678 | 2: {"id0", "id1", "id2"}, 679 | }, shares), 680 | wantProcessed: true, 681 | want: want[:1], 682 | }, 683 | { 684 | name: "two processed", 685 | query: makeQuery(t, ctxts), 686 | obs: makeObservations(t, map[int][]string{ 687 | 0: {"id0", "id1", "id2"}, 688 | 1: {"id0", "id1", "id2"}, 689 | 2: {"id0", "id1"}, 690 | }, shares), 691 | wantProcessed: true, 692 | want: want[:2], 693 | }, 694 | { 695 | name: "all processed", 696 | query: makeQuery(t, ctxts), 697 | obs: makeObservations(t, map[int][]string{ 698 | 0: {"id0", "id1", "id2"}, 699 | 1: {"id0", "id1", "id2"}, 700 | 2: {"id0", "id1", "id2"}, 701 | }, shares), 702 | wantProcessed: true, 703 | want: want, 704 | }, 705 | { 706 | name: "all processed, more shares than needed", 707 | query: makeQuery(t, ctxts), 708 | obs: makeObservations(t, map[int][]string{ 709 | 0: {"id0", "id1", "id2"}, 710 | 1: {"id0", "id1", "id2"}, 711 | 2: {"id0", "id1", "id2"}, 712 | 3: {"id0", "id1", "id2"}, 713 | }, shares), 714 | wantProcessed: true, 715 | want: want, 716 | }, 717 | { 718 | name: "nothing processed (wrong oracle-index mapping)", 719 | query: makeQuery(t, ctxts), 720 | obs: makeObservations(t, map[int][]string{ 721 | 0: {"id0", "id1", "id2"}, 722 | 1: {"id0", "id1", "id2"}, 723 | 4: {"id0", "id1", "id2"}, 724 | }, shares), 725 | }, 726 | { 727 | name: "all processed, one broken obs", 728 | query: makeQuery(t, ctxts), 729 | obs: append(makeObservations(t, map[int][]string{ 730 | 0: {"id0", "id1", "id2"}, 731 | 1: {"id0", "id1", "id2"}, 732 | 2: {"id0", "id1", "id2"}, 733 | }, shares), types.AttributedObservation{ 734 | Observer: 4, 735 | Observation: []byte("broken"), 736 | }), 737 | wantProcessed: true, 738 | want: want, 739 | }, 740 | { 741 | name: "all processed, duplicate decryption shares in a single observation", 742 | query: makeQuery(t, ctxts), 743 | obs: makeObservations(t, map[int][]string{ 744 | 0: {"id0", "id0", "id2"}, 745 | 1: {"id0", "id1", "id2"}, 746 | 2: {"id0", "id1", "id2"}, 747 | 3: {"id0", "id1", "id2"}, 748 | }, shares), 749 | wantProcessed: true, 750 | want: want, 751 | }, 752 | } { 753 | t.Run(tc.name, func(t *testing.T) { 754 | conf, err := config.DecodeReportingPluginConfig(makeConfig(t, &config.ReportingPluginConfig{ 755 | K: k, 756 | }).OffchainConfig) 757 | if err != nil { 758 | t.Fatalf("DecodeReportingPluginConfig: %v", err) 759 | } 760 | dp := &decryptionPlugin{ 761 | logger: dummyLogger{}, 762 | decryptionQueue: &queue{}, 763 | publicKey: pk, 764 | genericConfig: &types.ReportingPluginConfig{ 765 | F: 2, 766 | }, 767 | specificConfig: conf, 768 | oracleToKeyShare: map[commontypes.OracleID]int{ 769 | 0: 0, 770 | 1: 1, 771 | 2: 2, 772 | 3: 3, 773 | 4: 5, // wrong mapping 774 | }, 775 | } 776 | processed, reportBytes, err := dp.Report(context.Background(), types.ReportTimestamp{}, tc.query, tc.obs) 777 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 778 | t.Fatalf("err=%v, want=%v", err, tc.err) 779 | } else if err != nil { 780 | return 781 | } 782 | if processed != tc.wantProcessed { 783 | t.Errorf("got processed=%v, want=%v", processed, tc.wantProcessed) 784 | } 785 | // make sure Report() output is deterministic 786 | _, secondReportBytes, _ := dp.Report(context.Background(), types.ReportTimestamp{}, tc.query, tc.obs) 787 | require.Equal(t, reportBytes, secondReportBytes) 788 | var report Report 789 | if err := proto.Unmarshal(reportBytes, &report); err != nil { 790 | t.Errorf("Unmarshal: %v", err) 791 | } 792 | // make sure processed requests are sorted before comparison 793 | got := report.ProcessedDecryptedRequests 794 | sort.Slice(got, func(i, j int) bool { 795 | return string(got[i].CiphertextId) < string(got[j].CiphertextId) 796 | }) 797 | if d := cmp.Diff(got, tc.want, cmpopts.IgnoreUnexported(ProcessedDecryptionRequest{})); d != "" { 798 | t.Errorf("got/want diff=%v", d) 799 | } 800 | }) 801 | } 802 | } 803 | 804 | func TestNewReportingPlugin_CustomConfigParser(t *testing.T) { 805 | ctx, cancel := context.WithCancel(context.Background()) 806 | t.Cleanup(cancel) 807 | customParser := mocks.NewConfigParser(t) 808 | factory := DecryptionReportingPluginFactory{ 809 | ConfigParser: customParser, 810 | Logger: loggers.MakeLogrusLogger(), 811 | } 812 | 813 | customParser.On("ParseConfig", mock.Anything).Return(&config.ReportingPluginConfigWrapper{ 814 | Config: &config.ReportingPluginConfig{ 815 | K: 1, 816 | }, 817 | }, nil).Once() 818 | _, _, err := factory.NewReportingPlugin(ctx, types.ReportingPluginConfig{}) 819 | require.NoError(t, err) 820 | 821 | customParser.On("ParseConfig", mock.Anything).Return(nil, errors.New("error")).Once() 822 | _, _, err = factory.NewReportingPlugin(ctx, types.ReportingPluginConfig{}) 823 | require.Error(t, err) 824 | } 825 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.13 6 | 7 | require ( 8 | github.com/google/go-cmp v0.6.0 9 | github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 10 | github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166 11 | github.com/stretchr/testify v1.9.0 12 | google.golang.org/protobuf v1.34.2 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 17 | github.com/mr-tron/base58 v1.2.0 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 20 | github.com/rogpeppe/go-internal v1.12.0 // indirect 21 | github.com/sirupsen/logrus v1.9.3 // indirect 22 | github.com/stretchr/objx v0.5.2 // indirect 23 | golang.org/x/crypto v0.27.0 // indirect 24 | golang.org/x/sys v0.25.0 // indirect 25 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 26 | gopkg.in/yaml.v3 v3.0.1 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 8 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 9 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 13 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 14 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 15 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 16 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 17 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 20 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 22 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 23 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 24 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 25 | github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360= 26 | github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= 27 | github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166 h1:cNH0nQjRfmWj173L8exDkQratcFVQ8AAj8RZ8a+suaI= 28 | github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230616062456-5c32c95fc166/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= 29 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 30 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 31 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 32 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 33 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 34 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 35 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= 36 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 37 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 39 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 40 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 41 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 44 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 47 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 48 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/go_generate.go: -------------------------------------------------------------------------------- 1 | //go:generate protoc -I. --go_out=. types.proto 2 | //go:generate protoc -I. --go_out=./config ./config/config_types.proto 3 | 4 | package decryptionplugin 5 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/queue.go: -------------------------------------------------------------------------------- 1 | package decryptionplugin 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | ErrNotFound = fmt.Errorf("not found") 10 | ErrUnmarshalling = fmt.Errorf("cannot unmarshal the ciphertext in the query plugin function") 11 | ErrDecryption = fmt.Errorf("cannot decrypt the ciphertext with the private key share in observation plugin function") 12 | ErrAggregation = fmt.Errorf("cannot aggregate valid decryption shares in report plugn function") 13 | ) 14 | 15 | type CiphertextId []byte 16 | 17 | func (c CiphertextId) String() string { 18 | return "0x" + hex.EncodeToString(c) 19 | } 20 | 21 | type DecryptionRequest struct { 22 | CiphertextId CiphertextId 23 | Ciphertext []byte 24 | } 25 | 26 | type DecryptionQueuingService interface { 27 | // GetRequests returns up to requestCountLimit oldest pending unique requests 28 | // with total size up to totalBytesLimit bytes size. 29 | GetRequests(requestCountLimit int, totalBytesLimit int) []DecryptionRequest 30 | 31 | // GetCiphertext returns the ciphertext matching ciphertextId 32 | // if it exists in the queue. 33 | // If the ciphertext does not exist it returns ErrNotFound. 34 | GetCiphertext(ciphertextId CiphertextId) ([]byte, error) 35 | 36 | // SetResult sets the plaintext (decrypted ciphertext) which corresponds to ciphertextId 37 | // or returns an error if the decrypted ciphertext is invalid. 38 | SetResult(ciphertextId CiphertextId, plaintext []byte, err error) 39 | } 40 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/types.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.20.0 5 | // source: types.proto 6 | 7 | package decryptionplugin 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type CiphertextWithID struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | CiphertextId []byte `protobuf:"bytes,1,opt,name=ciphertext_id,json=ciphertextId,proto3" json:"ciphertext_id,omitempty"` 29 | Ciphertext []byte `protobuf:"bytes,2,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` 30 | } 31 | 32 | func (x *CiphertextWithID) Reset() { 33 | *x = CiphertextWithID{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_types_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *CiphertextWithID) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*CiphertextWithID) ProtoMessage() {} 46 | 47 | func (x *CiphertextWithID) ProtoReflect() protoreflect.Message { 48 | mi := &file_types_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use CiphertextWithID.ProtoReflect.Descriptor instead. 60 | func (*CiphertextWithID) Descriptor() ([]byte, []int) { 61 | return file_types_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *CiphertextWithID) GetCiphertextId() []byte { 65 | if x != nil { 66 | return x.CiphertextId 67 | } 68 | return nil 69 | } 70 | 71 | func (x *CiphertextWithID) GetCiphertext() []byte { 72 | if x != nil { 73 | return x.Ciphertext 74 | } 75 | return nil 76 | } 77 | 78 | type Query struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | DecryptionRequests []*CiphertextWithID `protobuf:"bytes,1,rep,name=decryption_requests,json=decryptionRequests,proto3" json:"decryption_requests,omitempty"` 84 | } 85 | 86 | func (x *Query) Reset() { 87 | *x = Query{} 88 | if protoimpl.UnsafeEnabled { 89 | mi := &file_types_proto_msgTypes[1] 90 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 91 | ms.StoreMessageInfo(mi) 92 | } 93 | } 94 | 95 | func (x *Query) String() string { 96 | return protoimpl.X.MessageStringOf(x) 97 | } 98 | 99 | func (*Query) ProtoMessage() {} 100 | 101 | func (x *Query) ProtoReflect() protoreflect.Message { 102 | mi := &file_types_proto_msgTypes[1] 103 | if protoimpl.UnsafeEnabled && x != nil { 104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 105 | if ms.LoadMessageInfo() == nil { 106 | ms.StoreMessageInfo(mi) 107 | } 108 | return ms 109 | } 110 | return mi.MessageOf(x) 111 | } 112 | 113 | // Deprecated: Use Query.ProtoReflect.Descriptor instead. 114 | func (*Query) Descriptor() ([]byte, []int) { 115 | return file_types_proto_rawDescGZIP(), []int{1} 116 | } 117 | 118 | func (x *Query) GetDecryptionRequests() []*CiphertextWithID { 119 | if x != nil { 120 | return x.DecryptionRequests 121 | } 122 | return nil 123 | } 124 | 125 | type DecryptionShareWithID struct { 126 | state protoimpl.MessageState 127 | sizeCache protoimpl.SizeCache 128 | unknownFields protoimpl.UnknownFields 129 | 130 | CiphertextId []byte `protobuf:"bytes,1,opt,name=ciphertext_id,json=ciphertextId,proto3" json:"ciphertext_id,omitempty"` 131 | DecryptionShare []byte `protobuf:"bytes,2,opt,name=decryption_share,json=decryptionShare,proto3" json:"decryption_share,omitempty"` 132 | } 133 | 134 | func (x *DecryptionShareWithID) Reset() { 135 | *x = DecryptionShareWithID{} 136 | if protoimpl.UnsafeEnabled { 137 | mi := &file_types_proto_msgTypes[2] 138 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 139 | ms.StoreMessageInfo(mi) 140 | } 141 | } 142 | 143 | func (x *DecryptionShareWithID) String() string { 144 | return protoimpl.X.MessageStringOf(x) 145 | } 146 | 147 | func (*DecryptionShareWithID) ProtoMessage() {} 148 | 149 | func (x *DecryptionShareWithID) ProtoReflect() protoreflect.Message { 150 | mi := &file_types_proto_msgTypes[2] 151 | if protoimpl.UnsafeEnabled && x != nil { 152 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 153 | if ms.LoadMessageInfo() == nil { 154 | ms.StoreMessageInfo(mi) 155 | } 156 | return ms 157 | } 158 | return mi.MessageOf(x) 159 | } 160 | 161 | // Deprecated: Use DecryptionShareWithID.ProtoReflect.Descriptor instead. 162 | func (*DecryptionShareWithID) Descriptor() ([]byte, []int) { 163 | return file_types_proto_rawDescGZIP(), []int{2} 164 | } 165 | 166 | func (x *DecryptionShareWithID) GetCiphertextId() []byte { 167 | if x != nil { 168 | return x.CiphertextId 169 | } 170 | return nil 171 | } 172 | 173 | func (x *DecryptionShareWithID) GetDecryptionShare() []byte { 174 | if x != nil { 175 | return x.DecryptionShare 176 | } 177 | return nil 178 | } 179 | 180 | type Observation struct { 181 | state protoimpl.MessageState 182 | sizeCache protoimpl.SizeCache 183 | unknownFields protoimpl.UnknownFields 184 | 185 | DecryptionShares []*DecryptionShareWithID `protobuf:"bytes,1,rep,name=decryption_shares,json=decryptionShares,proto3" json:"decryption_shares,omitempty"` 186 | } 187 | 188 | func (x *Observation) Reset() { 189 | *x = Observation{} 190 | if protoimpl.UnsafeEnabled { 191 | mi := &file_types_proto_msgTypes[3] 192 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 193 | ms.StoreMessageInfo(mi) 194 | } 195 | } 196 | 197 | func (x *Observation) String() string { 198 | return protoimpl.X.MessageStringOf(x) 199 | } 200 | 201 | func (*Observation) ProtoMessage() {} 202 | 203 | func (x *Observation) ProtoReflect() protoreflect.Message { 204 | mi := &file_types_proto_msgTypes[3] 205 | if protoimpl.UnsafeEnabled && x != nil { 206 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 207 | if ms.LoadMessageInfo() == nil { 208 | ms.StoreMessageInfo(mi) 209 | } 210 | return ms 211 | } 212 | return mi.MessageOf(x) 213 | } 214 | 215 | // Deprecated: Use Observation.ProtoReflect.Descriptor instead. 216 | func (*Observation) Descriptor() ([]byte, []int) { 217 | return file_types_proto_rawDescGZIP(), []int{3} 218 | } 219 | 220 | func (x *Observation) GetDecryptionShares() []*DecryptionShareWithID { 221 | if x != nil { 222 | return x.DecryptionShares 223 | } 224 | return nil 225 | } 226 | 227 | type ProcessedDecryptionRequest struct { 228 | state protoimpl.MessageState 229 | sizeCache protoimpl.SizeCache 230 | unknownFields protoimpl.UnknownFields 231 | 232 | CiphertextId []byte `protobuf:"bytes,1,opt,name=ciphertext_id,json=ciphertextId,proto3" json:"ciphertext_id,omitempty"` 233 | Plaintext []byte `protobuf:"bytes,2,opt,name=plaintext,proto3" json:"plaintext,omitempty"` 234 | } 235 | 236 | func (x *ProcessedDecryptionRequest) Reset() { 237 | *x = ProcessedDecryptionRequest{} 238 | if protoimpl.UnsafeEnabled { 239 | mi := &file_types_proto_msgTypes[4] 240 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 241 | ms.StoreMessageInfo(mi) 242 | } 243 | } 244 | 245 | func (x *ProcessedDecryptionRequest) String() string { 246 | return protoimpl.X.MessageStringOf(x) 247 | } 248 | 249 | func (*ProcessedDecryptionRequest) ProtoMessage() {} 250 | 251 | func (x *ProcessedDecryptionRequest) ProtoReflect() protoreflect.Message { 252 | mi := &file_types_proto_msgTypes[4] 253 | if protoimpl.UnsafeEnabled && x != nil { 254 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 255 | if ms.LoadMessageInfo() == nil { 256 | ms.StoreMessageInfo(mi) 257 | } 258 | return ms 259 | } 260 | return mi.MessageOf(x) 261 | } 262 | 263 | // Deprecated: Use ProcessedDecryptionRequest.ProtoReflect.Descriptor instead. 264 | func (*ProcessedDecryptionRequest) Descriptor() ([]byte, []int) { 265 | return file_types_proto_rawDescGZIP(), []int{4} 266 | } 267 | 268 | func (x *ProcessedDecryptionRequest) GetCiphertextId() []byte { 269 | if x != nil { 270 | return x.CiphertextId 271 | } 272 | return nil 273 | } 274 | 275 | func (x *ProcessedDecryptionRequest) GetPlaintext() []byte { 276 | if x != nil { 277 | return x.Plaintext 278 | } 279 | return nil 280 | } 281 | 282 | type Report struct { 283 | state protoimpl.MessageState 284 | sizeCache protoimpl.SizeCache 285 | unknownFields protoimpl.UnknownFields 286 | 287 | ProcessedDecryptedRequests []*ProcessedDecryptionRequest `protobuf:"bytes,1,rep,name=processedDecryptedRequests,proto3" json:"processedDecryptedRequests,omitempty"` 288 | } 289 | 290 | func (x *Report) Reset() { 291 | *x = Report{} 292 | if protoimpl.UnsafeEnabled { 293 | mi := &file_types_proto_msgTypes[5] 294 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 295 | ms.StoreMessageInfo(mi) 296 | } 297 | } 298 | 299 | func (x *Report) String() string { 300 | return protoimpl.X.MessageStringOf(x) 301 | } 302 | 303 | func (*Report) ProtoMessage() {} 304 | 305 | func (x *Report) ProtoReflect() protoreflect.Message { 306 | mi := &file_types_proto_msgTypes[5] 307 | if protoimpl.UnsafeEnabled && x != nil { 308 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 309 | if ms.LoadMessageInfo() == nil { 310 | ms.StoreMessageInfo(mi) 311 | } 312 | return ms 313 | } 314 | return mi.MessageOf(x) 315 | } 316 | 317 | // Deprecated: Use Report.ProtoReflect.Descriptor instead. 318 | func (*Report) Descriptor() ([]byte, []int) { 319 | return file_types_proto_rawDescGZIP(), []int{5} 320 | } 321 | 322 | func (x *Report) GetProcessedDecryptedRequests() []*ProcessedDecryptionRequest { 323 | if x != nil { 324 | return x.ProcessedDecryptedRequests 325 | } 326 | return nil 327 | } 328 | 329 | var File_types_proto protoreflect.FileDescriptor 330 | 331 | var file_types_proto_rawDesc = []byte{ 332 | 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 333 | 0x79, 0x70, 0x65, 0x73, 0x22, 0x57, 0x0a, 0x10, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 334 | 0x78, 0x74, 0x57, 0x69, 0x74, 0x68, 0x49, 0x44, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x69, 0x70, 0x68, 335 | 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 336 | 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 337 | 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 338 | 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x51, 0x0a, 339 | 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x48, 0x0a, 0x13, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 340 | 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 341 | 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 342 | 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x57, 0x69, 0x74, 0x68, 0x49, 0x44, 0x52, 0x12, 0x64, 0x65, 343 | 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 344 | 0x22, 0x67, 0x0a, 0x15, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 345 | 0x61, 0x72, 0x65, 0x57, 0x69, 0x74, 0x68, 0x49, 0x44, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x69, 0x70, 346 | 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 347 | 0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x29, 348 | 0x0a, 0x10, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 349 | 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 350 | 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x22, 0x58, 0x0a, 0x0b, 0x4f, 0x62, 0x73, 351 | 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x63, 0x72, 352 | 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 353 | 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x72, 354 | 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x57, 0x69, 0x74, 0x68, 0x49, 355 | 0x44, 0x52, 0x10, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 356 | 0x72, 0x65, 0x73, 0x22, 0x5f, 0x0a, 0x1a, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 357 | 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 358 | 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x5f, 359 | 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 360 | 0x74, 0x65, 0x78, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 361 | 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 362 | 0x74, 0x65, 0x78, 0x74, 0x22, 0x6b, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x61, 363 | 0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x44, 0x65, 0x63, 0x72, 0x79, 364 | 0x70, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 365 | 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 366 | 0x73, 0x73, 0x65, 0x64, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 367 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x1a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 368 | 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 369 | 0x73, 0x42, 0x15, 0x5a, 0x13, 0x2e, 0x2f, 0x3b, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 370 | 0x6f, 0x6e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 371 | } 372 | 373 | var ( 374 | file_types_proto_rawDescOnce sync.Once 375 | file_types_proto_rawDescData = file_types_proto_rawDesc 376 | ) 377 | 378 | func file_types_proto_rawDescGZIP() []byte { 379 | file_types_proto_rawDescOnce.Do(func() { 380 | file_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_proto_rawDescData) 381 | }) 382 | return file_types_proto_rawDescData 383 | } 384 | 385 | var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 386 | var file_types_proto_goTypes = []interface{}{ 387 | (*CiphertextWithID)(nil), // 0: types.CiphertextWithID 388 | (*Query)(nil), // 1: types.Query 389 | (*DecryptionShareWithID)(nil), // 2: types.DecryptionShareWithID 390 | (*Observation)(nil), // 3: types.Observation 391 | (*ProcessedDecryptionRequest)(nil), // 4: types.ProcessedDecryptionRequest 392 | (*Report)(nil), // 5: types.Report 393 | } 394 | var file_types_proto_depIdxs = []int32{ 395 | 0, // 0: types.Query.decryption_requests:type_name -> types.CiphertextWithID 396 | 2, // 1: types.Observation.decryption_shares:type_name -> types.DecryptionShareWithID 397 | 4, // 2: types.Report.processedDecryptedRequests:type_name -> types.ProcessedDecryptionRequest 398 | 3, // [3:3] is the sub-list for method output_type 399 | 3, // [3:3] is the sub-list for method input_type 400 | 3, // [3:3] is the sub-list for extension type_name 401 | 3, // [3:3] is the sub-list for extension extendee 402 | 0, // [0:3] is the sub-list for field type_name 403 | } 404 | 405 | func init() { file_types_proto_init() } 406 | func file_types_proto_init() { 407 | if File_types_proto != nil { 408 | return 409 | } 410 | if !protoimpl.UnsafeEnabled { 411 | file_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 412 | switch v := v.(*CiphertextWithID); i { 413 | case 0: 414 | return &v.state 415 | case 1: 416 | return &v.sizeCache 417 | case 2: 418 | return &v.unknownFields 419 | default: 420 | return nil 421 | } 422 | } 423 | file_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 424 | switch v := v.(*Query); i { 425 | case 0: 426 | return &v.state 427 | case 1: 428 | return &v.sizeCache 429 | case 2: 430 | return &v.unknownFields 431 | default: 432 | return nil 433 | } 434 | } 435 | file_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 436 | switch v := v.(*DecryptionShareWithID); i { 437 | case 0: 438 | return &v.state 439 | case 1: 440 | return &v.sizeCache 441 | case 2: 442 | return &v.unknownFields 443 | default: 444 | return nil 445 | } 446 | } 447 | file_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 448 | switch v := v.(*Observation); i { 449 | case 0: 450 | return &v.state 451 | case 1: 452 | return &v.sizeCache 453 | case 2: 454 | return &v.unknownFields 455 | default: 456 | return nil 457 | } 458 | } 459 | file_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 460 | switch v := v.(*ProcessedDecryptionRequest); i { 461 | case 0: 462 | return &v.state 463 | case 1: 464 | return &v.sizeCache 465 | case 2: 466 | return &v.unknownFields 467 | default: 468 | return nil 469 | } 470 | } 471 | file_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 472 | switch v := v.(*Report); i { 473 | case 0: 474 | return &v.state 475 | case 1: 476 | return &v.sizeCache 477 | case 2: 478 | return &v.unknownFields 479 | default: 480 | return nil 481 | } 482 | } 483 | } 484 | type x struct{} 485 | out := protoimpl.TypeBuilder{ 486 | File: protoimpl.DescBuilder{ 487 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 488 | RawDescriptor: file_types_proto_rawDesc, 489 | NumEnums: 0, 490 | NumMessages: 6, 491 | NumExtensions: 0, 492 | NumServices: 0, 493 | }, 494 | GoTypes: file_types_proto_goTypes, 495 | DependencyIndexes: file_types_proto_depIdxs, 496 | MessageInfos: file_types_proto_msgTypes, 497 | }.Build() 498 | File_types_proto = out.File 499 | file_types_proto_rawDesc = nil 500 | file_types_proto_goTypes = nil 501 | file_types_proto_depIdxs = nil 502 | } 503 | -------------------------------------------------------------------------------- /go/ocr2/decryptionplugin/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./;decryptionplugin"; 4 | 5 | package types; 6 | 7 | message CiphertextWithID { 8 | bytes ciphertext_id = 1; 9 | bytes ciphertext = 2; 10 | } 11 | message Query { 12 | repeated CiphertextWithID decryption_requests = 1; 13 | } 14 | 15 | message DecryptionShareWithID { 16 | bytes ciphertext_id = 1; 17 | bytes decryption_share = 2; 18 | } 19 | 20 | message Observation { 21 | repeated DecryptionShareWithID decryption_shares = 1; 22 | } 23 | 24 | message ProcessedDecryptionRequest { 25 | bytes ciphertext_id = 1; 26 | bytes plaintext = 2; 27 | } 28 | 29 | message Report { 30 | repeated ProcessedDecryptionRequest processedDecryptedRequests = 1; 31 | } -------------------------------------------------------------------------------- /go/tdh2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smartcontractkit/tdh2/go/tdh2 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/google/go-cmp v0.6.0 7 | github.com/stretchr/testify v1.9.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 12 | github.com/kr/pretty v0.3.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 14 | github.com/rogpeppe/go-internal v1.12.0 // indirect 15 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go/tdh2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 5 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 6 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 7 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 8 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 11 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 12 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 13 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 14 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 15 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 17 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 18 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 19 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 20 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 22 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 23 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 24 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 25 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 26 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/LICENSE: -------------------------------------------------------------------------------- 1 | This code is derived from https://github.com/dedis/kyber (v3.1.0) and its original license is below. 2 | 3 | This code is (c) by DEDIS/EPFL 2017 under the MPL v2 or later version. 4 | 5 | Mozilla Public License Version 2.0 6 | ================================== 7 | 8 | 1. Definitions 9 | -------------- 10 | 11 | 1.1. "Contributor" 12 | means each individual or legal entity that creates, contributes to 13 | the creation of, or owns Covered Software. 14 | 15 | 1.2. "Contributor Version" 16 | means the combination of the Contributions of others (if any) used 17 | by a Contributor and that particular Contributor's Contribution. 18 | 19 | 1.3. "Contribution" 20 | means Covered Software of a particular Contributor. 21 | 22 | 1.4. "Covered Software" 23 | means Source Code Form to which the initial Contributor has attached 24 | the notice in Exhibit A, the Executable Form of such Source Code 25 | Form, and Modifications of such Source Code Form, in each case 26 | including portions thereof. 27 | 28 | 1.5. "Incompatible With Secondary Licenses" 29 | means 30 | 31 | (a) that the initial Contributor has attached the notice described 32 | in Exhibit B to the Covered Software; or 33 | 34 | (b) that the Covered Software was made available under the terms of 35 | version 1.1 or earlier of the License, but not also under the 36 | terms of a Secondary License. 37 | 38 | 1.6. "Executable Form" 39 | means any form of the work other than Source Code Form. 40 | 41 | 1.7. "Larger Work" 42 | means a work that combines Covered Software with other material, in 43 | a separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | means this document. 47 | 48 | 1.9. "Licensable" 49 | means having the right to grant, to the maximum extent possible, 50 | whether at the time of the initial grant or subsequently, any and 51 | all of the rights conveyed by this License. 52 | 53 | 1.10. "Modifications" 54 | means any of the following: 55 | 56 | (a) any file in Source Code Form that results from an addition to, 57 | deletion from, or modification of the contents of Covered 58 | Software; or 59 | 60 | (b) any new file in Source Code Form that contains any Covered 61 | Software. 62 | 63 | 1.11. "Patent Claims" of a Contributor 64 | means any patent claim(s), including without limitation, method, 65 | process, and apparatus claims, in any patent Licensable by such 66 | Contributor that would be infringed, but for the grant of the 67 | License, by the making, using, selling, offering for sale, having 68 | made, import, or transfer of either its Contributions or its 69 | Contributor Version. 70 | 71 | 1.12. "Secondary License" 72 | means either the GNU General Public License, Version 2.0, the GNU 73 | Lesser General Public License, Version 2.1, the GNU Affero General 74 | Public License, Version 3.0, or any later versions of those 75 | licenses. 76 | 77 | 1.13. "Source Code Form" 78 | means the form of the work preferred for making modifications. 79 | 80 | 1.14. "You" (or "Your") 81 | means an individual or a legal entity exercising rights under this 82 | License. For legal entities, "You" includes any entity that 83 | controls, is controlled by, or is under common control with You. For 84 | purposes of this definition, "control" means (a) the power, direct 85 | or indirect, to cause the direction or management of such entity, 86 | whether by contract or otherwise, or (b) ownership of more than 87 | fifty percent (50%) of the outstanding shares or beneficial 88 | ownership of such entity. 89 | 90 | 2. License Grants and Conditions 91 | -------------------------------- 92 | 93 | 2.1. Grants 94 | 95 | Each Contributor hereby grants You a world-wide, royalty-free, 96 | non-exclusive license: 97 | 98 | (a) under intellectual property rights (other than patent or trademark) 99 | Licensable by such Contributor to use, reproduce, make available, 100 | modify, display, perform, distribute, and otherwise exploit its 101 | Contributions, either on an unmodified basis, with Modifications, or 102 | as part of a Larger Work; and 103 | 104 | (b) under Patent Claims of such Contributor to make, use, sell, offer 105 | for sale, have made, import, and otherwise transfer either its 106 | Contributions or its Contributor Version. 107 | 108 | 2.2. Effective Date 109 | 110 | The licenses granted in Section 2.1 with respect to any Contribution 111 | become effective for each Contribution on the date the Contributor first 112 | distributes such Contribution. 113 | 114 | 2.3. Limitations on Grant Scope 115 | 116 | The licenses granted in this Section 2 are the only rights granted under 117 | this License. No additional rights or licenses will be implied from the 118 | distribution or licensing of Covered Software under this License. 119 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 120 | Contributor: 121 | 122 | (a) for any code that a Contributor has removed from Covered Software; 123 | or 124 | 125 | (b) for infringements caused by: (i) Your and any other third party's 126 | modifications of Covered Software, or (ii) the combination of its 127 | Contributions with other software (except as part of its Contributor 128 | Version); or 129 | 130 | (c) under Patent Claims infringed by Covered Software in the absence of 131 | its Contributions. 132 | 133 | This License does not grant any rights in the trademarks, service marks, 134 | or logos of any Contributor (except as may be necessary to comply with 135 | the notice requirements in Section 3.4). 136 | 137 | 2.4. Subsequent Licenses 138 | 139 | No Contributor makes additional grants as a result of Your choice to 140 | distribute the Covered Software under a subsequent version of this 141 | License (see Section 10.2) or under the terms of a Secondary License (if 142 | permitted under the terms of Section 3.3). 143 | 144 | 2.5. Representation 145 | 146 | Each Contributor represents that the Contributor believes its 147 | Contributions are its original creation(s) or it has sufficient rights 148 | to grant the rights to its Contributions conveyed by this License. 149 | 150 | 2.6. Fair Use 151 | 152 | This License is not intended to limit any rights You have under 153 | applicable copyright doctrines of fair use, fair dealing, or other 154 | equivalents. 155 | 156 | 2.7. Conditions 157 | 158 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 159 | in Section 2.1. 160 | 161 | 3. Responsibilities 162 | ------------------- 163 | 164 | 3.1. Distribution of Source Form 165 | 166 | All distribution of Covered Software in Source Code Form, including any 167 | Modifications that You create or to which You contribute, must be under 168 | the terms of this License. You must inform recipients that the Source 169 | Code Form of the Covered Software is governed by the terms of this 170 | License, and how they can obtain a copy of this License. You may not 171 | attempt to alter or restrict the recipients' rights in the Source Code 172 | Form. 173 | 174 | 3.2. Distribution of Executable Form 175 | 176 | If You distribute Covered Software in Executable Form then: 177 | 178 | (a) such Covered Software must also be made available in Source Code 179 | Form, as described in Section 3.1, and You must inform recipients of 180 | the Executable Form how they can obtain a copy of such Source Code 181 | Form by reasonable means in a timely manner, at a charge no more 182 | than the cost of distribution to the recipient; and 183 | 184 | (b) You may distribute such Executable Form under the terms of this 185 | License, or sublicense it under different terms, provided that the 186 | license for the Executable Form does not attempt to limit or alter 187 | the recipients' rights in the Source Code Form under this License. 188 | 189 | 3.3. Distribution of a Larger Work 190 | 191 | You may create and distribute a Larger Work under terms of Your choice, 192 | provided that You also comply with the requirements of this License for 193 | the Covered Software. If the Larger Work is a combination of Covered 194 | Software with a work governed by one or more Secondary Licenses, and the 195 | Covered Software is not Incompatible With Secondary Licenses, this 196 | License permits You to additionally distribute such Covered Software 197 | under the terms of such Secondary License(s), so that the recipient of 198 | the Larger Work may, at their option, further distribute the Covered 199 | Software under the terms of either this License or such Secondary 200 | License(s). 201 | 202 | 3.4. Notices 203 | 204 | You may not remove or alter the substance of any license notices 205 | (including copyright notices, patent notices, disclaimers of warranty, 206 | or limitations of liability) contained within the Source Code Form of 207 | the Covered Software, except that You may alter any license notices to 208 | the extent required to remedy known factual inaccuracies. 209 | 210 | 3.5. Application of Additional Terms 211 | 212 | You may choose to offer, and to charge a fee for, warranty, support, 213 | indemnity or liability obligations to one or more recipients of Covered 214 | Software. However, You may do so only on Your own behalf, and not on 215 | behalf of any Contributor. You must make it absolutely clear that any 216 | such warranty, support, indemnity, or liability obligation is offered by 217 | You alone, and You hereby agree to indemnify every Contributor for any 218 | liability incurred by such Contributor as a result of warranty, support, 219 | indemnity or liability terms You offer. You may include additional 220 | disclaimers of warranty and limitations of liability specific to any 221 | jurisdiction. 222 | 223 | 4. Inability to Comply Due to Statute or Regulation 224 | --------------------------------------------------- 225 | 226 | If it is impossible for You to comply with any of the terms of this 227 | License with respect to some or all of the Covered Software due to 228 | statute, judicial order, or regulation then You must: (a) comply with 229 | the terms of this License to the maximum extent possible; and (b) 230 | describe the limitations and the code they affect. Such description must 231 | be placed in a text file included with all distributions of the Covered 232 | Software under this License. Except to the extent prohibited by statute 233 | or regulation, such description must be sufficiently detailed for a 234 | recipient of ordinary skill to be able to understand it. 235 | 236 | 5. Termination 237 | -------------- 238 | 239 | 5.1. The rights granted under this License will terminate automatically 240 | if You fail to comply with any of its terms. However, if You become 241 | compliant, then the rights granted under this License from a particular 242 | Contributor are reinstated (a) provisionally, unless and until such 243 | Contributor explicitly and finally terminates Your grants, and (b) on an 244 | ongoing basis, if such Contributor fails to notify You of the 245 | non-compliance by some reasonable means prior to 60 days after You have 246 | come back into compliance. Moreover, Your grants from a particular 247 | Contributor are reinstated on an ongoing basis if such Contributor 248 | notifies You of the non-compliance by some reasonable means, this is the 249 | first time You have received notice of non-compliance with this License 250 | from such Contributor, and You become compliant prior to 30 days after 251 | Your receipt of the notice. 252 | 253 | 5.2. If You initiate litigation against any entity by asserting a patent 254 | infringement claim (excluding declaratory judgment actions, 255 | counter-claims, and cross-claims) alleging that a Contributor Version 256 | directly or indirectly infringes any patent, then the rights granted to 257 | You by any and all Contributors for the Covered Software under Section 258 | 2.1 of this License shall terminate. 259 | 260 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 261 | end user license agreements (excluding distributors and resellers) which 262 | have been validly granted by You or Your distributors under this License 263 | prior to termination shall survive termination. 264 | 265 | ************************************************************************ 266 | * * 267 | * 6. Disclaimer of Warranty * 268 | * ------------------------- * 269 | * * 270 | * Covered Software is provided under this License on an "as is" * 271 | * basis, without warranty of any kind, either expressed, implied, or * 272 | * statutory, including, without limitation, warranties that the * 273 | * Covered Software is free of defects, merchantable, fit for a * 274 | * particular purpose or non-infringing. The entire risk as to the * 275 | * quality and performance of the Covered Software is with You. * 276 | * Should any Covered Software prove defective in any respect, You * 277 | * (not any Contributor) assume the cost of any necessary servicing, * 278 | * repair, or correction. This disclaimer of warranty constitutes an * 279 | * essential part of this License. No use of any Covered Software is * 280 | * authorized under this License except under this disclaimer. * 281 | * * 282 | ************************************************************************ 283 | 284 | ************************************************************************ 285 | * * 286 | * 7. Limitation of Liability * 287 | * -------------------------- * 288 | * * 289 | * Under no circumstances and under no legal theory, whether tort * 290 | * (including negligence), contract, or otherwise, shall any * 291 | * Contributor, or anyone who distributes Covered Software as * 292 | * permitted above, be liable to You for any direct, indirect, * 293 | * special, incidental, or consequential damages of any character * 294 | * including, without limitation, damages for lost profits, loss of * 295 | * goodwill, work stoppage, computer failure or malfunction, or any * 296 | * and all other commercial damages or losses, even if such party * 297 | * shall have been informed of the possibility of such damages. This * 298 | * limitation of liability shall not apply to liability for death or * 299 | * personal injury resulting from such party's negligence to the * 300 | * extent applicable law prohibits such limitation. Some * 301 | * jurisdictions do not allow the exclusion or limitation of * 302 | * incidental or consequential damages, so this exclusion and * 303 | * limitation may not apply to You. * 304 | * * 305 | ************************************************************************ 306 | 307 | 8. Litigation 308 | ------------- 309 | 310 | Any litigation relating to this License may be brought only in the 311 | courts of a jurisdiction where the defendant maintains its principal 312 | place of business and such litigation shall be governed by laws of that 313 | jurisdiction, without reference to its conflict-of-law provisions. 314 | Nothing in this Section shall prevent a party's ability to bring 315 | cross-claims or counter-claims. 316 | 317 | 9. Miscellaneous 318 | ---------------- 319 | 320 | This License represents the complete agreement concerning the subject 321 | matter hereof. If any provision of this License is held to be 322 | unenforceable, such provision shall be reformed only to the extent 323 | necessary to make it enforceable. Any law or regulation which provides 324 | that the language of a contract shall be construed against the drafter 325 | shall not be used to construe this License against a Contributor. 326 | 327 | 10. Versions of the License 328 | --------------------------- 329 | 330 | 10.1. New Versions 331 | 332 | Mozilla Foundation is the license steward. Except as provided in Section 333 | 10.3, no one other than the license steward has the right to modify or 334 | publish new versions of this License. Each version will be given a 335 | distinguishing version number. 336 | 337 | 10.2. Effect of New Versions 338 | 339 | You may distribute the Covered Software under the terms of the version 340 | of the License under which You originally received the Covered Software, 341 | or under the terms of any subsequent version published by the license 342 | steward. 343 | 344 | 10.3. Modified Versions 345 | 346 | If you create software not governed by this License, and you want to 347 | create a new license for such software, you may create and use a 348 | modified version of this License if you rename the license and remove 349 | any references to the name of the license steward (except to note that 350 | such modified license differs from this License). 351 | 352 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 353 | Licenses 354 | 355 | If You choose to distribute Source Code Form that is Incompatible With 356 | Secondary Licenses under the terms of this version of the License, the 357 | notice described in Exhibit B of this License must be attached. 358 | 359 | Exhibit A - Source Code Form License Notice 360 | ------------------------------------------- 361 | 362 | This Source Code Form is subject to the terms of the Mozilla Public 363 | License, v. 2.0. If a copy of the MPL was not distributed with this 364 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 365 | 366 | If it is not possible or desirable to put the notice in a particular 367 | file, then You may include the notice in a location (such as a LICENSE 368 | file in a relevant directory) where a recipient would be likely to look 369 | for such a notice. 370 | 371 | You may add additional accurate notices of copyright ownership. 372 | 373 | Exhibit B - "Incompatible With Secondary Licenses" Notice 374 | --------------------------------------------------------- 375 | 376 | This Source Code Form is "Incompatible With Secondary Licenses", as 377 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /go/tdh2/lib/group/group.go: -------------------------------------------------------------------------------- 1 | // package group provides interfaces for group-related objects. 2 | package group 3 | 4 | import ( 5 | "crypto/cipher" 6 | "encoding" 7 | ) 8 | 9 | /* 10 | Marshaling is a basic interface representing fixed-length (or known-length) 11 | cryptographic objects or structures having a built-in binary encoding. 12 | Implementors must ensure that calls to these methods do not modify 13 | the underlying object so that other users of the object can access 14 | it concurrently. 15 | */ 16 | type Marshaling interface { 17 | encoding.BinaryMarshaler 18 | encoding.BinaryUnmarshaler 19 | 20 | // String returns the human readable string representation of the object. 21 | String() string 22 | 23 | // Encoded length of this object in bytes. 24 | MarshalSize() int 25 | } 26 | 27 | // Scalar represents a scalar value by which 28 | // a Point (group element) may be encrypted to produce another Point. 29 | // This is an exponent in DSA-style groups, 30 | // in which security is based on the Discrete Logarithm assumption, 31 | // and a scalar multiplier in elliptic curve groups. 32 | type Scalar interface { 33 | Marshaling 34 | 35 | // Equality test for two Scalars derived from the same Group. 36 | Equal(s2 Scalar) bool 37 | 38 | // Clone creates a new Scalar with the same value. 39 | Clone() Scalar 40 | 41 | // SetInt64 sets the receiver to a small integer value. 42 | SetInt64(v int64) Scalar 43 | 44 | // Set to the additive identity (0). 45 | Zero() Scalar 46 | 47 | // Set to the modular sum of scalars a and b. 48 | Add(a, b Scalar) Scalar 49 | 50 | // Set to the modular difference a - b. 51 | Sub(a, b Scalar) Scalar 52 | 53 | // Set to the modular negation of scalar a. 54 | Neg(a Scalar) Scalar 55 | 56 | // Set to the multiplicative identity (1). 57 | One() Scalar 58 | 59 | // Set to the modular product of scalars a and b. 60 | Mul(a, b Scalar) Scalar 61 | 62 | // Set to the modular division of scalar a by scalar b. 63 | Div(a, b Scalar) Scalar 64 | 65 | // Set to the modular inverse of scalar a. 66 | Inv(a Scalar) Scalar 67 | 68 | // Set to a fresh random or pseudo-random scalar. 69 | Pick(rand cipher.Stream) Scalar 70 | 71 | // SetBytes sets the scalar from a byte-slice, 72 | // reducing if necessary to the appropriate modulus. 73 | // The endianess of the byte-slice is determined by the 74 | // implementation. 75 | SetBytes([]byte) Scalar 76 | } 77 | 78 | // Point represents an element of a public-key cryptographic Group. 79 | // For example, 80 | // this is a number modulo the prime P in a DSA-style Schnorr group, 81 | // or an (x, y) point on an elliptic curve. 82 | // A Point can contain a Diffie-Hellman public key, an ElGamal ciphertext, etc. 83 | type Point interface { 84 | Marshaling 85 | 86 | // Equality test for two Points derived from the same Group. 87 | Equal(s2 Point) bool 88 | 89 | // Null sets the receiver to the neutral identity element. 90 | Null() Point 91 | 92 | // Base sets the receiver to this group's standard base point. 93 | Base() Point 94 | 95 | // Pick sets the receiver to a fresh random or pseudo-random Point. 96 | Pick(rand cipher.Stream) Point 97 | 98 | // Set sets the receiver equal to another Point p. 99 | Set(p Point) Point 100 | 101 | // Clone clones the underlying point. 102 | Clone() Point 103 | 104 | // Add points so that their scalars add homomorphically. 105 | Add(a, b Point) Point 106 | 107 | // Subtract points so that their scalars subtract homomorphically. 108 | Sub(a, b Point) Point 109 | 110 | // Set to the negation of point a. 111 | Neg(a Point) Point 112 | 113 | // Multiply point p by the scalar s. 114 | // If p == nil, multiply with the standard base point Base(). 115 | Mul(s Scalar, p Point) Point 116 | } 117 | 118 | // Group interface represents a mathematical group 119 | // usable for Diffie-Hellman key exchange, ElGamal encryption, 120 | // and the related body of public-key cryptographic algorithms 121 | // and zero-knowledge proof methods. 122 | // The Group interface is designed in particular to be a generic front-end 123 | // to both traditional DSA-style modular arithmetic groups 124 | // and ECDSA-style elliptic curves: 125 | // the caller of this interface's methods 126 | // need not know or care which specific mathematical construction 127 | // underlies the interface. 128 | // 129 | // The Group interface is essentially just a "constructor" interface 130 | // enabling the caller to generate the two particular types of objects 131 | // relevant to DSA-style public-key cryptography; 132 | // we call these objects Points and Scalars. 133 | // The caller must explicitly initialize or set a new Point or Scalar object 134 | // to some value before using it as an input to some other operation 135 | // involving Point and/or Scalar objects. 136 | // For example, to compare a point P against the neutral (identity) element, 137 | // you might use P.Equal(suite.Point().Null()), 138 | // but not just P.Equal(suite.Point()). 139 | // 140 | // It is expected that any implementation of this interface 141 | // should satisfy suitable hardness assumptions for the applicable group: 142 | // e.g., that it is cryptographically hard for an adversary to 143 | // take an encrypted Point and the known generator it was based on, 144 | // and derive the Scalar with which the Point was encrypted. 145 | // Any implementation is also expected to satisfy 146 | // the standard homomorphism properties that Diffie-Hellman 147 | // and the associated body of public-key cryptography are based on. 148 | type Group interface { 149 | String() string 150 | 151 | ScalarLen() int // Max length of scalars in bytes 152 | Scalar() Scalar // Create new scalar 153 | 154 | PointLen() int // Max length of point in bytes 155 | Point() Point // Create new point 156 | } 157 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/mod/int.go: -------------------------------------------------------------------------------- 1 | // Package mod contains a generic implementation of finite field arithmetic 2 | // on integer fields with a constant modulus. 3 | package mod 4 | 5 | import ( 6 | "crypto/cipher" 7 | "encoding/hex" 8 | "errors" 9 | "math/big" 10 | 11 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 12 | ) 13 | 14 | // Int is a generic implementation of finite field arithmetic 15 | // on integer finite fields with a given constant modulus, 16 | // built using Go's built-in big.Int package. 17 | // Int satisfies the group.Scalar interface, 18 | // and hence serves as a basic implementation of group.Scalar, 19 | // e.g., representing discrete-log exponents of Schnorr groups 20 | // or scalar multipliers for elliptic curves. 21 | // 22 | // Int offers an API similar to and compatible with big.Int, 23 | // but "carries around" a pointer to the relevant modulus 24 | // and automatically normalizes the value to that modulus 25 | // after all arithmetic operations, simplifying modular arithmetic. 26 | // Binary operations assume that the source(s) 27 | // have the same modulus, but do not check this assumption. 28 | // Unary and binary arithmetic operations may be performed on uninitialized 29 | // target objects, and receive the modulus of the first operand. 30 | // For efficiency the modulus field M is a pointer, 31 | // whose target is assumed never to change. 32 | type Int struct { 33 | V big.Int // Integer value from 0 through M-1 34 | M *big.Int // Modulus for finite field arithmetic 35 | } 36 | 37 | // NewInt64 creates a new Int with a given int64 value and big.Int modulus. 38 | func NewInt64(v int64, M *big.Int) *Int { 39 | i := &Int{M: M} 40 | i.V.SetInt64(v).Mod(&i.V, M) 41 | return i 42 | } 43 | 44 | // Return the Int's integer value in hexadecimal string representation. 45 | func (i *Int) String() string { 46 | return hex.EncodeToString(i.V.Bytes()) 47 | } 48 | 49 | // Equal returns true if the two Ints are equal 50 | func (i *Int) Equal(s2 group.Scalar) bool { 51 | return i.V.Cmp(&s2.(*Int).V) == 0 52 | } 53 | 54 | // Clone returns a separate duplicate of this Int. 55 | func (i *Int) Clone() group.Scalar { 56 | ni := &Int{M: i.M} 57 | ni.V.Set(&i.V).Mod(&i.V, i.M) 58 | return ni 59 | } 60 | 61 | // Zero set the Int to the value 0. The modulus must already be initialized. 62 | func (i *Int) Zero() group.Scalar { 63 | i.V.SetInt64(0) 64 | return i 65 | } 66 | 67 | // One sets the Int to the value 1. The modulus must already be initialized. 68 | func (i *Int) One() group.Scalar { 69 | i.V.SetInt64(1) 70 | return i 71 | } 72 | 73 | // SetInt64 sets the Int to an arbitrary 64-bit "small integer" value. 74 | // The modulus must already be initialized. 75 | func (i *Int) SetInt64(v int64) group.Scalar { 76 | i.V.SetInt64(v).Mod(&i.V, i.M) 77 | return i 78 | } 79 | 80 | // Add sets the target to a + b mod M, where M is a's modulus.. 81 | func (i *Int) Add(a, b group.Scalar) group.Scalar { 82 | ai := a.(*Int) 83 | bi := b.(*Int) 84 | i.M = ai.M 85 | i.V.Add(&ai.V, &bi.V).Mod(&i.V, i.M) 86 | return i 87 | } 88 | 89 | // Sub sets the target to a - b mod M. 90 | // Target receives a's modulus. 91 | func (i *Int) Sub(a, b group.Scalar) group.Scalar { 92 | ai := a.(*Int) 93 | bi := b.(*Int) 94 | i.M = ai.M 95 | i.V.Sub(&ai.V, &bi.V).Mod(&i.V, i.M) 96 | return i 97 | } 98 | 99 | // Neg sets the target to -a mod M. 100 | func (i *Int) Neg(a group.Scalar) group.Scalar { 101 | ai := a.(*Int) 102 | i.M = ai.M 103 | if ai.V.Sign() > 0 { 104 | i.V.Sub(i.M, &ai.V) 105 | } else { 106 | i.V.SetUint64(0) 107 | } 108 | return i 109 | } 110 | 111 | // Mul sets the target to a * b mod M. 112 | // Target receives a's modulus. 113 | func (i *Int) Mul(a, b group.Scalar) group.Scalar { 114 | ai := a.(*Int) 115 | bi := b.(*Int) 116 | i.M = ai.M 117 | i.V.Mul(&ai.V, &bi.V).Mod(&i.V, i.M) 118 | return i 119 | } 120 | 121 | // Div sets the target to a * b^-1 mod M, where b^-1 is the modular inverse of b. 122 | func (i *Int) Div(a, b group.Scalar) group.Scalar { 123 | ai := a.(*Int) 124 | bi := b.(*Int) 125 | var t big.Int 126 | i.M = ai.M 127 | i.V.Mul(&ai.V, t.ModInverse(&bi.V, i.M)) 128 | i.V.Mod(&i.V, i.M) 129 | return i 130 | } 131 | 132 | // Inv sets the target to the modular inverse of a with respect to modulus M. 133 | func (i *Int) Inv(a group.Scalar) group.Scalar { 134 | ai := a.(*Int) 135 | i.M = ai.M 136 | i.V.ModInverse(&a.(*Int).V, i.M) 137 | return i 138 | } 139 | 140 | // Pick a [pseudo-]random integer modulo M 141 | // using bits from the given stream cipher. 142 | // This code is adopted from Go's elliptic.GenerateKey() 143 | // and the rejection sampling can lead to up to a two-fold 144 | // slowdown, if M is not close to 2**bitSize. 145 | func (i *Int) Pick(rand cipher.Stream) group.Scalar { 146 | var n *big.Int 147 | // This is just a bitmask with the number of ones starting at 8 then 148 | // incrementing by index. To account for fields with bitsizes that are not a whole 149 | // number of bytes, we mask off the unnecessary bits. h/t agl 150 | mask := []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f} 151 | bitSize := i.M.BitLen() 152 | byteLen := (bitSize + 7) / 8 153 | b := make([]byte, byteLen) 154 | 155 | for { 156 | rand.XORKeyStream(b, b) 157 | // We have to mask off any excess bits in the case that the size of the 158 | // underlying field is not a whole number of bytes. 159 | b[0] &= mask[bitSize%8] 160 | // This is because, in tests, rand will return all zeros and we don't 161 | // want to get the point at infinity and loop forever. 162 | b[1] ^= 0x42 163 | 164 | n = new(big.Int).SetBytes(b) 165 | // If the scalar is out of range, sample another random number. 166 | if n.Cmp(i.M) < 0 { 167 | break 168 | } 169 | } 170 | 171 | i.V.Set(n) 172 | return i 173 | } 174 | 175 | // MarshalSize returns the length in bytes of encoded integers with modulus M. 176 | // The length of encoded Ints depends only on the size of the modulus, 177 | // and not on the the value of the encoded integer, 178 | // making the encoding is fixed-length for simplicity and security. 179 | func (i *Int) MarshalSize() int { 180 | return (i.M.BitLen() + 7) / 8 181 | } 182 | 183 | // MarshalBinary encodes the value of this Int into a byte-slice exactly Len() bytes long. 184 | // It uses big endian. 185 | func (i *Int) MarshalBinary() ([]byte, error) { 186 | l := i.MarshalSize() 187 | b := i.V.Bytes() // may be shorter than l 188 | offset := l - len(b) 189 | 190 | if offset != 0 { 191 | nb := make([]byte, l) 192 | copy(nb[offset:], b) 193 | b = nb 194 | } 195 | return b, nil 196 | } 197 | 198 | // UnmarshalBinary tries to decode a Int from a byte-slice buffer. 199 | // Returns an error if the buffer is not exactly Len() bytes long 200 | // or if the contents of the buffer represents an out-of-range integer. 201 | func (i *Int) UnmarshalBinary(buf []byte) error { 202 | if len(buf) != i.MarshalSize() { 203 | return errors.New("UnmarshalBinary: wrong size buffer") 204 | } 205 | 206 | i.V.SetBytes(buf) 207 | if i.V.Cmp(i.M) >= 0 { 208 | return errors.New("UnmarshalBinary: value out of range") 209 | } 210 | return nil 211 | } 212 | 213 | // SetBytes set the value value to a number represented 214 | // by a byte string. 215 | func (i *Int) SetBytes(a []byte) group.Scalar { 216 | var buff = a 217 | i.V.SetBytes(buff).Mod(&i.V, i.M) 218 | return i 219 | } 220 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/mod/int_test.go: -------------------------------------------------------------------------------- 1 | package mod 2 | 3 | import ( 4 | "bytes" 5 | "crypto/elliptic" 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func FuzzIntMarshal(f *testing.F) { 11 | mods := []*big.Int{elliptic.P256().Params().N, elliptic.P384().Params().N, elliptic.P521().Params().N} 12 | for idx, m := range mods { 13 | i := NewInt64(0, m) 14 | b, err := i.MarshalBinary() 15 | if err != nil { 16 | f.Fatalf("MarshalBinary: %v", err) 17 | } 18 | f.Add(idx, b) 19 | } 20 | f.Fuzz(func(t *testing.T, idx int, data []byte) { 21 | if idx < 0 || idx >= len(mods) { 22 | t.Skip() 23 | } 24 | i1 := NewInt64(0, mods[idx]) 25 | i2 := NewInt64(0, mods[idx]) 26 | if err := i1.UnmarshalBinary(data); err != nil { 27 | t.Skip() 28 | } 29 | data1, err := i1.MarshalBinary() 30 | if err != nil { 31 | t.Fatalf("Cannot marshal: data=%v err=%v", data, err) 32 | } 33 | if err := i2.UnmarshalBinary(data1); err != nil { 34 | t.Fatalf("Cannot unmarshal: data=%v err=%v", data1, err) 35 | } 36 | data2, err := i2.MarshalBinary() 37 | if err != nil { 38 | t.Fatalf("Cannot marshal: data=%v err=%v", data2, err) 39 | } 40 | if !bytes.Equal(data1, data2) { 41 | t.Errorf("data1=%v data2=%v", data1, data2) 42 | } 43 | if !i1.Equal(i2) { 44 | t.Errorf("ps1=%v data1=%v ps2=%v data2=%v", i1, data1, i2, data2) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/nist/curve.go: -------------------------------------------------------------------------------- 1 | // Package nist implements cryptographic groups and ciphersuites 2 | // based on the NIST standards, using Go's built-in crypto library. 3 | package nist 4 | 5 | import ( 6 | "crypto/cipher" 7 | "crypto/elliptic" 8 | "errors" 9 | "fmt" 10 | "math/big" 11 | 12 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 13 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/mod" 14 | ) 15 | 16 | // streamReader implements io.Reader from cipher.Stream 17 | type streamReader struct { 18 | stream cipher.Stream 19 | } 20 | 21 | func (s *streamReader) Read(p []byte) (int, error) { 22 | s.stream.XORKeyStream(p, p) 23 | return len(p), nil 24 | } 25 | 26 | type curvePoint struct { 27 | x, y *big.Int 28 | c *curve 29 | } 30 | 31 | func (p *curvePoint) String() string { 32 | return "(" + p.x.String() + "," + p.y.String() + ")" 33 | } 34 | 35 | func (p *curvePoint) Equal(p2 group.Point) bool { 36 | cp2 := p2.(*curvePoint) 37 | 38 | // Make sure both coordinates are normalized. 39 | // Apparently Go's elliptic curve code doesn't always ensure this. 40 | M := p.c.p.P 41 | p.x.Mod(p.x, M) 42 | p.y.Mod(p.y, M) 43 | cp2.x.Mod(cp2.x, M) 44 | cp2.y.Mod(cp2.y, M) 45 | 46 | return p.x.Cmp(cp2.x) == 0 && p.y.Cmp(cp2.y) == 0 47 | } 48 | 49 | func (p *curvePoint) Null() group.Point { 50 | p.x = new(big.Int).SetInt64(0) 51 | p.y = new(big.Int).SetInt64(0) 52 | return p 53 | } 54 | 55 | func (p *curvePoint) Base() group.Point { 56 | p.x = p.c.p.Gx 57 | p.y = p.c.p.Gy 58 | return p 59 | } 60 | 61 | func (p *curvePoint) Valid() bool { 62 | // The IsOnCurve function in Go's elliptic curve package 63 | // doesn't consider the point-at-infinity to be "on the curve" 64 | return p.c.IsOnCurve(p.x, p.y) || 65 | (p.x.Sign() == 0 && p.y.Sign() == 0) 66 | } 67 | 68 | func (p *curvePoint) Pick(rand cipher.Stream) group.Point { 69 | var err error 70 | _, p.x, p.y, err = elliptic.GenerateKey(p.c, &streamReader{rand}) 71 | if err != nil { 72 | // It cannot panic since GenerateKey returns errors only on reading 73 | // from the randomness source which is deterministic in our case. 74 | panic(fmt.Sprintf("cannot generate point: %v", err)) 75 | } 76 | return p 77 | } 78 | 79 | func (p *curvePoint) Add(a, b group.Point) group.Point { 80 | ca := a.(*curvePoint) 81 | cb := b.(*curvePoint) 82 | p.x, p.y = p.c.Add(ca.x, ca.y, cb.x, cb.y) 83 | return p 84 | } 85 | 86 | func (p *curvePoint) Sub(a, b group.Point) group.Point { 87 | ca := a.(*curvePoint) 88 | cb := b.(*curvePoint) 89 | 90 | cbn := p.c.Point().Neg(cb).(*curvePoint) 91 | p.x, p.y = p.c.Add(ca.x, ca.y, cbn.x, cbn.y) 92 | return p 93 | } 94 | 95 | func (p *curvePoint) Neg(a group.Point) group.Point { 96 | s := p.c.Scalar().One() 97 | s.Neg(s) 98 | return p.Mul(s, a).(*curvePoint) 99 | } 100 | 101 | func (p *curvePoint) Mul(s group.Scalar, b group.Point) group.Point { 102 | cs := s.(*mod.Int) 103 | if b != nil { 104 | cb := b.(*curvePoint) 105 | p.x, p.y = p.c.ScalarMult(cb.x, cb.y, cs.V.Bytes()) 106 | } else { 107 | p.x, p.y = p.c.ScalarBaseMult(cs.V.Bytes()) 108 | } 109 | return p 110 | } 111 | 112 | func (p *curvePoint) MarshalSize() int { 113 | coordlen := (p.c.Params().BitSize + 7) >> 3 114 | return 1 + 2*coordlen // uncompressed ANSI X9.62 representation 115 | } 116 | 117 | func (p *curvePoint) MarshalBinary() ([]byte, error) { 118 | return elliptic.Marshal(p.c, p.x, p.y), nil 119 | } 120 | 121 | func (p *curvePoint) UnmarshalBinary(buf []byte) error { 122 | if len(buf) != p.MarshalSize() { 123 | return errors.New("wrong buffer size") 124 | } 125 | // Check whether all bytes after first one are 0, so we 126 | // just return the initial point. Read everything to 127 | // prevent timing-leakage. 128 | var c byte 129 | for _, b := range buf[1:] { 130 | c |= b 131 | } 132 | if c != 0 { 133 | p.x, p.y = elliptic.Unmarshal(p.c, buf) 134 | if p.x == nil || !p.Valid() { 135 | return errors.New("invalid elliptic curve point") 136 | } 137 | } else { 138 | // All bytes are 0, so we initialize x and y 139 | p.x = big.NewInt(0) 140 | p.y = big.NewInt(0) 141 | } 142 | return nil 143 | } 144 | 145 | // Curve is an implementation of the group.Group interface 146 | // for NIST elliptic curves, built on Go's native elliptic curve library. 147 | type curve struct { 148 | elliptic.Curve 149 | p *elliptic.CurveParams 150 | } 151 | 152 | // Return the number of bytes in the encoding of a Scalar for this curve. 153 | func (c *curve) ScalarLen() int { return (c.p.N.BitLen() + 7) / 8 } 154 | 155 | // Create a Scalar associated with this curve. The scalars created by 156 | // this package implement group.Scalar's SetBytes method, interpreting 157 | // the bytes as a big-endian integer, so as to be compatible with the 158 | // Go standard library's big.Int type. 159 | func (c *curve) Scalar() group.Scalar { 160 | return mod.NewInt64(0, c.p.N) 161 | } 162 | 163 | // Number of bytes required to store one coordinate on this curve 164 | func (c *curve) coordLen() int { 165 | return (c.p.BitSize + 7) / 8 166 | } 167 | 168 | // Return the number of bytes in the encoding of a Point for this curve. 169 | // Currently uses uncompressed ANSI X9.62 format with both X and Y coordinates; 170 | // this could change. 171 | func (c *curve) PointLen() int { 172 | return 1 + 2*c.coordLen() // ANSI X9.62: 1 header byte plus 2 coords 173 | } 174 | 175 | // Create a Point associated with this curve. 176 | func (c *curve) Point() group.Point { 177 | p := new(curvePoint) 178 | p.c = c 179 | return p 180 | } 181 | 182 | func (p *curvePoint) Set(P group.Point) group.Point { 183 | p.x = P.(*curvePoint).x 184 | p.y = P.(*curvePoint).y 185 | return p 186 | } 187 | 188 | func (p *curvePoint) Clone() group.Point { 189 | return &curvePoint{x: p.x, y: p.y, c: p.c} 190 | } 191 | 192 | // Return the order of this curve: the prime N in the curve parameters. 193 | func (c *curve) Order() *big.Int { 194 | return c.p.N 195 | } 196 | 197 | // P256 implements the group.Group interface for the NIST P-256 elliptic curve. 198 | type P256 struct { 199 | curve 200 | } 201 | 202 | func (curve *P256) String() string { 203 | return "P256" 204 | } 205 | 206 | // NewP256 returns a new instance of P256. 207 | func NewP256() *P256 { 208 | var g P256 209 | g.curve.Curve = elliptic.P256() 210 | g.p = g.Params() 211 | return &g 212 | } 213 | 214 | // P384 implements the group.Group interface for the NIST P-384 elliptic curve. 215 | type P384 struct { 216 | curve 217 | } 218 | 219 | func (curve *P384) String() string { 220 | return "P384" 221 | } 222 | 223 | // NewP384 returns a new instance of P384. 224 | func NewP384() *P384 { 225 | var g P384 226 | g.curve.Curve = elliptic.P384() 227 | g.p = g.Params() 228 | return &g 229 | } 230 | 231 | // P521 implements the group.Group interface for the NIST P-521 elliptic curve. 232 | type P521 struct { 233 | curve 234 | } 235 | 236 | func (curve *P521) String() string { 237 | return "P521" 238 | } 239 | 240 | // NewP521 returns a new instance of P521. 241 | func NewP521() *P521 { 242 | var g P521 243 | g.curve.Curve = elliptic.P521() 244 | g.p = g.Params() 245 | return &g 246 | } 247 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/nist/group_test.go: -------------------------------------------------------------------------------- 1 | package nist 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 8 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/test" 9 | ) 10 | 11 | var benchmarks = []*test.GroupBench{ 12 | test.NewGroupBench(NewP256()), 13 | test.NewGroupBench(NewP384()), 14 | test.NewGroupBench(NewP521()), 15 | } 16 | 17 | func TestSetBytesBE(t *testing.T) { 18 | for _, b := range benchmarks { 19 | t.Run(b.String(), func(t *testing.T) { 20 | s := b.G.Scalar() 21 | s.SetBytes([]byte{0, 1, 2, 3}) 22 | // 010203 because initial 0 is trimmed in String(), and 03 (last byte of BE) ends up 23 | // in the LSB of the bigint. 24 | if s.String() != "010203" { 25 | t.Fatal("unexpected result from String():", s.String()) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func TestGroup(t *testing.T) { 32 | for _, bench := range benchmarks { 33 | t.Run(bench.String(), func(t *testing.T) { 34 | test.GroupTest(t, bench.G) 35 | }) 36 | } 37 | } 38 | 39 | func BenchmarkScalarAdd(b *testing.B) { 40 | for _, bench := range benchmarks { 41 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarAdd(b.N) }) 42 | } 43 | } 44 | 45 | func BenchmarkScalarSub(b *testing.B) { 46 | for _, bench := range benchmarks { 47 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarSub(b.N) }) 48 | } 49 | } 50 | 51 | func BenchmarkScalarNeg(b *testing.B) { 52 | for _, bench := range benchmarks { 53 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarNeg(b.N) }) 54 | } 55 | } 56 | 57 | func BenchmarkScalarMul(b *testing.B) { 58 | for _, bench := range benchmarks { 59 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarMul(b.N) }) 60 | } 61 | } 62 | 63 | func BenchmarkScalarDiv(b *testing.B) { 64 | for _, bench := range benchmarks { 65 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarDiv(b.N) }) 66 | } 67 | } 68 | 69 | func BenchmarkScalarInv(b *testing.B) { 70 | for _, bench := range benchmarks { 71 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarInv(b.N) }) 72 | } 73 | } 74 | 75 | func BenchmarkScalarPick(b *testing.B) { 76 | for _, bench := range benchmarks { 77 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarPick(b.N) }) 78 | } 79 | } 80 | 81 | func BenchmarkScalarEncode(b *testing.B) { 82 | for _, bench := range benchmarks { 83 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarEncode(b.N) }) 84 | } 85 | } 86 | 87 | func BenchmarkScalarDecode(b *testing.B) { 88 | for _, bench := range benchmarks { 89 | b.Run(bench.String(), func(b *testing.B) { bench.ScalarDecode(b.N) }) 90 | } 91 | } 92 | 93 | func BenchmarkPointAdd(b *testing.B) { 94 | for _, bench := range benchmarks { 95 | b.Run(bench.String(), func(b *testing.B) { bench.PointAdd(b.N) }) 96 | } 97 | } 98 | 99 | func BenchmarkPointSub(b *testing.B) { 100 | for _, bench := range benchmarks { 101 | b.Run(bench.String(), func(b *testing.B) { bench.PointSub(b.N) }) 102 | } 103 | } 104 | 105 | func BenchmarkPointNeg(b *testing.B) { 106 | for _, bench := range benchmarks { 107 | b.Run(bench.String(), func(b *testing.B) { bench.PointNeg(b.N) }) 108 | } 109 | } 110 | 111 | func BenchmarkPointMul(b *testing.B) { 112 | for _, bench := range benchmarks { 113 | b.Run(bench.String(), func(b *testing.B) { bench.PointMul(b.N) }) 114 | } 115 | } 116 | 117 | func BenchmarkPointBaseMul(b *testing.B) { 118 | for _, bench := range benchmarks { 119 | b.Run(bench.String(), func(b *testing.B) { bench.PointBaseMul(b.N) }) 120 | } 121 | } 122 | 123 | func BenchmarkPointPick(b *testing.B) { 124 | for _, bench := range benchmarks { 125 | b.Run(bench.String(), func(b *testing.B) { bench.PointPick(b.N) }) 126 | } 127 | } 128 | 129 | func BenchmarkPointEncode(b *testing.B) { 130 | for _, bench := range benchmarks { 131 | b.Run(bench.String(), func(b *testing.B) { bench.PointEncode(b.N) }) 132 | } 133 | } 134 | 135 | func BenchmarkPointDecode(b *testing.B) { 136 | for _, bench := range benchmarks { 137 | b.Run(bench.String(), func(b *testing.B) { bench.PointDecode(b.N) }) 138 | } 139 | } 140 | 141 | func FuzzCurvePointMarshal(f *testing.F) { 142 | groups := []group.Group{NewP256(), NewP384(), NewP521()} 143 | for idx, g := range groups { 144 | p := g.Point().Base() 145 | b, err := p.MarshalBinary() 146 | if err != nil { 147 | f.Fatalf("MarshalBinary: %v", err) 148 | } 149 | f.Add(idx, b) 150 | } 151 | f.Fuzz(func(t *testing.T, idx int, data []byte) { 152 | if idx < 0 || idx >= len(groups) { 153 | t.Skip() 154 | } 155 | p1 := groups[idx].Point() 156 | p2 := groups[idx].Point() 157 | if err := p1.UnmarshalBinary(data); err != nil { 158 | t.Skip() 159 | } 160 | data1, err := p1.MarshalBinary() 161 | if err != nil { 162 | t.Fatalf("Cannot marshal: data=%v err=%v", data, err) 163 | } 164 | if err := p2.UnmarshalBinary(data1); err != nil { 165 | t.Fatalf("Cannot unmarshal: data=%v err=%v", data1, err) 166 | } 167 | data2, err := p2.MarshalBinary() 168 | if err != nil { 169 | t.Fatalf("Cannot marshal: data=%v err=%v", data2, err) 170 | } 171 | if !bytes.Equal(data1, data2) { 172 | t.Errorf("data1=%v data2=%v", data1, data2) 173 | } 174 | if !p1.Equal(p2) { 175 | t.Errorf("ps1=%v data1=%v ps2=%v data2=%v", p1, data1, p2, data2) 176 | } 177 | }) 178 | } 179 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/share/poly.go: -------------------------------------------------------------------------------- 1 | // Package share implements Shamir secret sharing and polynomial commitments. 2 | // Shamir's scheme allows you to split a secret value into multiple parts, so called 3 | // shares, by evaluating a secret sharing polynomial at certain indices. The 4 | // shared secret can only be reconstructed (via Lagrange interpolation) if a 5 | // threshold of the participants provide their shares. A polynomial commitment 6 | // scheme allows a committer to commit to a secret sharing polynomial so that 7 | // a verifier can check the claimed evaluations of the committed polynomial. 8 | // Both schemes of this package are core building blocks for more advanced 9 | // secret sharing techniques. 10 | package share 11 | 12 | import ( 13 | "crypto/cipher" 14 | "errors" 15 | "fmt" 16 | "sort" 17 | "strings" 18 | 19 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 20 | ) 21 | 22 | // PriShare represents a private share. 23 | type PriShare struct { 24 | I int // Index of the private share 25 | V group.Scalar // Value of the private share 26 | } 27 | 28 | func (p *PriShare) String() string { 29 | return fmt.Sprintf("{%d:%s}", p.I, p.V) 30 | } 31 | 32 | // PriPoly represents a secret sharing polynomial. 33 | type PriPoly struct { 34 | g group.Group // Cryptographic group 35 | coeffs []group.Scalar // Coefficients of the polynomial 36 | } 37 | 38 | // NewPriPoly creates a new secret sharing polynomial using the provided 39 | // cryptographic group, the secret sharing threshold t, and the secret to be 40 | // shared s. If s is nil, a new s is chosen using the provided randomness 41 | // stream rand. 42 | func NewPriPoly(grp group.Group, t int, s group.Scalar, rand cipher.Stream) *PriPoly { 43 | coeffs := make([]group.Scalar, t) 44 | coeffs[0] = s 45 | if coeffs[0] == nil { 46 | coeffs[0] = grp.Scalar().Pick(rand) 47 | } 48 | for i := 1; i < t; i++ { 49 | coeffs[i] = grp.Scalar().Pick(rand) 50 | } 51 | return &PriPoly{g: grp, coeffs: coeffs} 52 | } 53 | 54 | // Secret returns the shared secret p(0), i.e., the constant term of the polynomial. 55 | func (p *PriPoly) Secret() group.Scalar { 56 | return p.coeffs[0] 57 | } 58 | 59 | // Eval computes the private share v = p(i). 60 | func (p *PriPoly) Eval(i int) *PriShare { 61 | xi := p.g.Scalar().SetInt64(1 + int64(i)) 62 | v := p.g.Scalar().Zero() 63 | for j := len(p.coeffs) - 1; j >= 0; j-- { 64 | v.Mul(v, xi) 65 | v.Add(v, p.coeffs[j]) 66 | } 67 | return &PriShare{i, v} 68 | } 69 | 70 | // Shares creates a list of n private shares p(1),...,p(n). 71 | func (p *PriPoly) Shares(n int) []*PriShare { 72 | shares := make([]*PriShare, n) 73 | for i := range shares { 74 | shares[i] = p.Eval(i) 75 | } 76 | return shares 77 | } 78 | 79 | func (p *PriPoly) String() string { 80 | var strs = make([]string, len(p.coeffs)) 81 | for i, c := range p.coeffs { 82 | strs[i] = c.String() 83 | } 84 | return "[ " + strings.Join(strs, ", ") + " ]" 85 | } 86 | 87 | // PubShare represents a public share. 88 | type PubShare struct { 89 | I int // Index of the public share 90 | V group.Point // Value of the public share 91 | } 92 | 93 | // xyCommits is the public version of xScalars. 94 | func xyCommit(g group.Group, shares []*PubShare, t, n int) (map[int]group.Scalar, map[int]group.Point) { 95 | // we are sorting first the shares since the shares may be unrelated for 96 | // some applications. In this case, all participants needs to interpolate on 97 | // the exact same order shares. 98 | sorted := make([]*PubShare, 0, n) 99 | for _, share := range shares { 100 | if share != nil { 101 | sorted = append(sorted, share) 102 | } 103 | } 104 | sort.Slice(sorted, func(i, j int) bool { return sorted[i].I < sorted[j].I }) 105 | 106 | x := make(map[int]group.Scalar) 107 | y := make(map[int]group.Point) 108 | 109 | for _, s := range sorted { 110 | if s == nil || s.V == nil || s.I < 0 { 111 | continue 112 | } 113 | idx := s.I 114 | x[idx] = g.Scalar().SetInt64(int64(idx + 1)) 115 | y[idx] = s.V 116 | if len(x) == t { 117 | break 118 | } 119 | } 120 | return x, y 121 | } 122 | 123 | // RecoverCommit reconstructs the secret commitment p(0) from a list of public 124 | // shares using Lagrange interpolation. 125 | func RecoverCommit(g group.Group, shares []*PubShare, t, n int) (group.Point, error) { 126 | x, y := xyCommit(g, shares, t, n) 127 | if len(x) < t { 128 | return nil, errors.New("share: not enough good public shares to reconstruct secret commitment") 129 | } 130 | 131 | num := g.Scalar() 132 | den := g.Scalar() 133 | tmp := g.Scalar() 134 | Acc := g.Point().Null() 135 | Tmp := g.Point() 136 | 137 | for i, xi := range x { 138 | num.One() 139 | den.One() 140 | for j, xj := range x { 141 | if i == j { 142 | continue 143 | } 144 | num.Mul(num, xj) 145 | den.Mul(den, tmp.Sub(xj, xi)) 146 | } 147 | Tmp.Mul(num.Div(num, den), y[i]) 148 | Acc.Add(Acc, Tmp) 149 | } 150 | 151 | return Acc, nil 152 | } 153 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/share/poly_test.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "testing" 8 | 9 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 10 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/nist" 11 | ) 12 | 13 | var groups = []group.Group{ 14 | nist.NewP256(), 15 | nist.NewP384(), 16 | nist.NewP521(), 17 | } 18 | 19 | func randStream(t *testing.T) cipher.Stream { 20 | block, err := aes.NewCipher(make([]byte, 16)) 21 | if err != nil { 22 | t.Fatalf("NewCipher: %v", err) 23 | } 24 | iv := make([]byte, aes.BlockSize) 25 | if _, err := rand.Read(iv); err != nil { 26 | t.Fatalf("Read: %v", err) 27 | } 28 | return cipher.NewCTR(block, iv) 29 | } 30 | 31 | func pubShares(g group.Group, shares []*PriShare) []*PubShare { 32 | out := []*PubShare{} 33 | for _, s := range shares { 34 | out = append(out, &PubShare{ 35 | I: s.I, 36 | V: g.Point().Mul(s.V, nil), 37 | }) 38 | } 39 | return out 40 | } 41 | 42 | func TestRecoveryWithoutSecret(test *testing.T) { 43 | for _, g := range groups { 44 | test.Run(g.String(), func(test *testing.T) { 45 | n := 10 46 | t := n/2 + 1 47 | 48 | priPoly := NewPriPoly(g, t, nil, randStream(test)) 49 | shares := priPoly.Shares(n) 50 | pubShares := pubShares(g, shares) 51 | 52 | recovered, err := RecoverCommit(g, pubShares, t, n) 53 | if err != nil { 54 | test.Fatal(err) 55 | } 56 | 57 | if !recovered.Equal(g.Point().Mul(priPoly.Secret(), nil)) { 58 | test.Fatal("recovered commit does not match initial value") 59 | } 60 | 61 | }) 62 | } 63 | } 64 | 65 | func TestRecoveryWithSecret(test *testing.T) { 66 | for _, g := range groups { 67 | test.Run(g.String(), func(test *testing.T) { 68 | n := 10 69 | t := n/2 + 1 70 | s := g.Scalar().Pick(randStream(test)) 71 | 72 | priPoly := NewPriPoly(g, t, s, randStream(test)) 73 | if !s.Equal(priPoly.Secret()) { 74 | test.Fatalf("secrets not equal") 75 | } 76 | shares := priPoly.Shares(n) 77 | pubShares := pubShares(g, shares) 78 | 79 | recovered, err := RecoverCommit(g, pubShares, t, n) 80 | if err != nil { 81 | test.Fatal(err) 82 | } 83 | 84 | if !recovered.Equal(g.Point().Mul(s, nil)) { 85 | test.Fatal("recovered commit does not match initial value") 86 | } 87 | }) 88 | } 89 | } 90 | 91 | func TestPublicRecoveryOutIndex(test *testing.T) { 92 | for _, g := range groups { 93 | test.Run(g.String(), func(test *testing.T) { 94 | n := 10 95 | t := n/2 + 1 96 | 97 | priPoly := NewPriPoly(g, t, nil, randStream(test)) 98 | pubShares := pubShares(g, priPoly.Shares(n)) 99 | comm := g.Point().Mul(priPoly.Secret(), nil) 100 | 101 | selected := pubShares[n-t:] 102 | if len(selected) != t { 103 | test.Fatalf("len(selected) != t") 104 | } 105 | newN := t + 1 106 | 107 | recovered, err := RecoverCommit(g, selected, t, newN) 108 | if err != nil { 109 | test.Fatal(err) 110 | } 111 | 112 | if !recovered.Equal(comm) { 113 | test.Fatal("recovered commit does not match initial value") 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func TestPublicRecoveryDelete(test *testing.T) { 120 | for _, g := range groups { 121 | test.Run(g.String(), func(test *testing.T) { 122 | n := 10 123 | t := n/2 + 1 124 | 125 | priPoly := NewPriPoly(g, t, nil, randStream(test)) 126 | shares := pubShares(g, priPoly.Shares(n)) 127 | comm := g.Point().Mul(priPoly.Secret(), nil) 128 | 129 | // Corrupt a few shares 130 | shares[2] = nil 131 | shares[5] = nil 132 | shares[7] = nil 133 | shares[8] = nil 134 | 135 | recovered, err := RecoverCommit(g, shares, t, n) 136 | if err != nil { 137 | test.Fatal(err) 138 | } 139 | 140 | if !recovered.Equal(comm) { 141 | test.Fatal("recovered commit does not match initial value") 142 | } 143 | }) 144 | } 145 | } 146 | 147 | func TestPublicRecoveryDeleteFail(test *testing.T) { 148 | for _, g := range groups { 149 | test.Run(g.String(), func(test *testing.T) { 150 | n := 10 151 | t := n/2 + 1 152 | 153 | priPoly := NewPriPoly(g, t, nil, randStream(test)) 154 | shares := pubShares(g, priPoly.Shares(n)) 155 | 156 | // Corrupt one more share than acceptable 157 | shares[1] = nil 158 | shares[2] = nil 159 | shares[5] = nil 160 | shares[7] = nil 161 | shares[8] = nil 162 | 163 | _, err := RecoverCommit(g, shares, t, n) 164 | if err == nil { 165 | test.Fatal("recovered commit unexpectably") 166 | } 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/test/group.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 5 | ) 6 | 7 | // GroupBench is a generic benchmark suite for group.groups. 8 | type GroupBench struct { 9 | G group.Group 10 | 11 | // Random secrets and points for testing 12 | x, y group.Scalar 13 | X, Y group.Point 14 | xe []byte // encoded Scalar 15 | Xe []byte // encoded Point 16 | } 17 | 18 | // NewGroupBench returns a new GroupBench. 19 | func NewGroupBench(g group.Group) *GroupBench { 20 | var gb GroupBench 21 | rng := randomNew() 22 | gb.G = g 23 | gb.x = g.Scalar().Pick(rng) 24 | gb.y = g.Scalar().Pick(rng) 25 | gb.xe, _ = gb.x.MarshalBinary() 26 | gb.X = g.Point().Pick(rng) 27 | gb.Y = g.Point().Pick(rng) 28 | gb.Xe, _ = gb.X.MarshalBinary() 29 | return &gb 30 | } 31 | 32 | func (gb GroupBench) String() string { 33 | return gb.G.String() 34 | } 35 | 36 | // ScalarAdd benchmarks the addition operation for scalars 37 | func (gb GroupBench) ScalarAdd(iters int) { 38 | for i := 1; i < iters; i++ { 39 | gb.x.Add(gb.x, gb.y) 40 | } 41 | } 42 | 43 | // ScalarSub benchmarks the substraction operation for scalars 44 | func (gb GroupBench) ScalarSub(iters int) { 45 | for i := 1; i < iters; i++ { 46 | gb.x.Sub(gb.x, gb.y) 47 | } 48 | } 49 | 50 | // ScalarNeg benchmarks the negation operation for scalars 51 | func (gb GroupBench) ScalarNeg(iters int) { 52 | for i := 1; i < iters; i++ { 53 | gb.x.Neg(gb.x) 54 | } 55 | } 56 | 57 | // ScalarMul benchmarks the multiplication operation for scalars 58 | func (gb GroupBench) ScalarMul(iters int) { 59 | for i := 1; i < iters; i++ { 60 | gb.x.Mul(gb.x, gb.y) 61 | } 62 | } 63 | 64 | // ScalarDiv benchmarks the division operation for scalars 65 | func (gb GroupBench) ScalarDiv(iters int) { 66 | for i := 1; i < iters; i++ { 67 | gb.x.Div(gb.x, gb.y) 68 | } 69 | } 70 | 71 | // ScalarInv benchmarks the inverse operation for scalars 72 | func (gb GroupBench) ScalarInv(iters int) { 73 | for i := 1; i < iters; i++ { 74 | gb.x.Inv(gb.x) 75 | } 76 | } 77 | 78 | // ScalarPick benchmarks the Pick operation for scalars 79 | func (gb GroupBench) ScalarPick(iters int) { 80 | for i := 1; i < iters; i++ { 81 | gb.x.Pick(randomNew()) 82 | } 83 | } 84 | 85 | // ScalarEncode benchmarks the marshalling operation for scalars 86 | func (gb GroupBench) ScalarEncode(iters int) { 87 | for i := 1; i < iters; i++ { 88 | _, _ = gb.x.MarshalBinary() 89 | } 90 | } 91 | 92 | // ScalarDecode benchmarks the unmarshalling operation for scalars 93 | func (gb GroupBench) ScalarDecode(iters int) { 94 | for i := 1; i < iters; i++ { 95 | _ = gb.x.UnmarshalBinary(gb.xe) 96 | } 97 | } 98 | 99 | // PointAdd benchmarks the addition operation for points 100 | func (gb GroupBench) PointAdd(iters int) { 101 | for i := 1; i < iters; i++ { 102 | gb.X.Add(gb.X, gb.Y) 103 | } 104 | } 105 | 106 | // PointSub benchmarks the substraction operation for points 107 | func (gb GroupBench) PointSub(iters int) { 108 | for i := 1; i < iters; i++ { 109 | gb.X.Sub(gb.X, gb.Y) 110 | } 111 | } 112 | 113 | // PointNeg benchmarks the negation operation for points 114 | func (gb GroupBench) PointNeg(iters int) { 115 | for i := 1; i < iters; i++ { 116 | gb.X.Neg(gb.X) 117 | } 118 | } 119 | 120 | // PointMul benchmarks the multiplication operation for points 121 | func (gb GroupBench) PointMul(iters int) { 122 | for i := 1; i < iters; i++ { 123 | gb.X.Mul(gb.y, gb.X) 124 | } 125 | } 126 | 127 | // PointBaseMul benchmarks the base multiplication operation for points 128 | func (gb GroupBench) PointBaseMul(iters int) { 129 | for i := 1; i < iters; i++ { 130 | gb.X.Mul(gb.y, nil) 131 | } 132 | } 133 | 134 | // PointPick benchmarks the pick-ing operation for points 135 | func (gb GroupBench) PointPick(iters int) { 136 | for i := 1; i < iters; i++ { 137 | gb.X.Pick(randomNew()) 138 | } 139 | } 140 | 141 | // PointEncode benchmarks the encoding operation for points 142 | func (gb GroupBench) PointEncode(iters int) { 143 | for i := 1; i < iters; i++ { 144 | _, _ = gb.X.MarshalBinary() 145 | } 146 | } 147 | 148 | // PointDecode benchmarks the decoding operation for points 149 | func (gb GroupBench) PointDecode(iters int) { 150 | for i := 1; i < iters; i++ { 151 | _ = gb.X.UnmarshalBinary(gb.Xe) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /go/tdh2/lib/group/test/test.go: -------------------------------------------------------------------------------- 1 | // Package test contains generic testing and benchmarking infrastructure 2 | // for cryptographic groups and ciphersuites. 3 | package test 4 | 5 | import ( 6 | "bytes" 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "crypto/rand" 10 | "testing" 11 | 12 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 13 | ) 14 | 15 | func testPointSet(t *testing.T, g group.Group, rand cipher.Stream) { 16 | N := 1000 17 | null := g.Point().Null() 18 | for i := 0; i < N; i++ { 19 | P1 := g.Point().Pick(rand) 20 | P2 := g.Point() 21 | P2.Set(P1) 22 | if !P1.Equal(P2) { 23 | t.Errorf("Set() set to a different point: %v != %v", P1, P2) 24 | } 25 | if !P1.Equal(null) { 26 | P1.Add(P1, P1) 27 | if P1.Equal(P2) { 28 | t.Errorf("Modifying P1 shouldn't modify P2: %v == %v", P1, P2) 29 | } 30 | } 31 | } 32 | } 33 | 34 | func testPointClone(t *testing.T, g group.Group, rand cipher.Stream) { 35 | N := 1000 36 | null := g.Point().Null() 37 | for i := 0; i < N; i++ { 38 | P1 := g.Point().Pick(rand) 39 | P2 := P1.Clone() 40 | if !P1.Equal(P2) { 41 | t.Errorf("Clone didn't work for point: %v != %v", P1, P2) 42 | } 43 | if !P1.Equal(null) { 44 | P1.Add(P1, P1) 45 | if P1.Equal(P2) { 46 | t.Errorf("Modifying P1 shouldn't modify P2: %v == %v", P1, P2) 47 | } 48 | } 49 | } 50 | } 51 | 52 | func testScalarSet(t *testing.T, g group.Group, rand cipher.Stream) { 53 | N := 1000 54 | zero := g.Scalar().Zero() 55 | one := g.Scalar().One() 56 | for i := 0; i < N; i++ { 57 | s1 := g.Scalar().Pick(rand) 58 | s2 := s1.Clone() 59 | if !s1.Equal(s2) { 60 | t.Errorf("Set() set to a different scalar: %v != %v", s1, s2) 61 | } 62 | if !s1.Equal(zero) && !s1.Equal(one) { 63 | s1.Mul(s1, s1) 64 | if s1.Equal(s2) { 65 | t.Errorf("Modifying s1 shouldn't modify s2: %v == %v", s1, s2) 66 | } 67 | } 68 | } 69 | } 70 | 71 | func testScalarClone(t *testing.T, g group.Group, rand cipher.Stream) { 72 | N := 1000 73 | zero := g.Scalar().Zero() 74 | one := g.Scalar().One() 75 | for i := 0; i < N; i++ { 76 | s1 := g.Scalar().Pick(rand) 77 | s2 := s1.Clone() 78 | if !s1.Equal(s2) { 79 | t.Errorf("Clone didn't work for scalar: %v != %v", s1, s2) 80 | } 81 | if !s1.Equal(zero) && !s1.Equal(one) { 82 | s1.Mul(s1, s1) 83 | if s1.Equal(s2) { 84 | t.Errorf("Modifying s1 shouldn't modify s2: %v == %v", s1, s2) 85 | } 86 | } 87 | } 88 | } 89 | 90 | // Apply a generic set of validation tests to a cryptographic Group, 91 | // using a given source of [pseudo-]randomness. 92 | // 93 | // Returns a log of the pseudorandom Points produced in the test, 94 | // for comparison across alternative implementations 95 | // that are supposed to be equivalent. 96 | func testGroup(t *testing.T, g group.Group, rand cipher.Stream) []group.Point { 97 | t.Logf("\nTesting group '%s': %d-byte Point, %d-byte Scalar\n", 98 | g.String(), g.PointLen(), g.ScalarLen()) 99 | 100 | points := make([]group.Point, 0) 101 | ptmp := g.Point() 102 | stmp := g.Scalar() 103 | pzero := g.Point().Null() 104 | szero := g.Scalar().Zero() 105 | sone := g.Scalar().One() 106 | 107 | // Do a simple Diffie-Hellman test 108 | s1 := g.Scalar().Pick(rand) 109 | s2 := g.Scalar().Pick(rand) 110 | if s1.Equal(szero) { 111 | t.Errorf("first secret is scalar zero %v", s1) 112 | } 113 | if s2.Equal(szero) { 114 | t.Errorf("second secret is scalar zero %v", s2) 115 | } 116 | if s1.Equal(s2) { 117 | t.Errorf("not getting unique secrets: picked %s twice", s1) 118 | } 119 | 120 | gen := g.Point().Base() 121 | points = append(points, gen) 122 | 123 | // Sanity-check relationship between addition and multiplication 124 | p1 := g.Point().Add(gen, gen) 125 | p2 := g.Point().Mul(stmp.SetInt64(2), nil) 126 | if !p1.Equal(p2) { 127 | t.Errorf("multiply by two doesn't work: %v == %v (+) %[2]v != %[2]v (x) 2 == %v", p1, gen, p2) 128 | } 129 | p1.Add(p1, p1) 130 | p2.Mul(stmp.SetInt64(4), nil) 131 | if !p1.Equal(p2) { 132 | t.Errorf("multiply by four doesn't work: %v (+) %[1]v != %v (x) 4 == %v", 133 | g.Point().Add(gen, gen), gen, p2) 134 | } 135 | points = append(points, p1) 136 | 137 | // Find out if this curve has a prime order: 138 | // if the curve does not offer a method IsPrimeOrder, 139 | // then assume that it is. 140 | type canCheckPrimeOrder interface { 141 | IsPrimeOrder() bool 142 | } 143 | primeOrder := true 144 | if gpo, ok := g.(canCheckPrimeOrder); ok { 145 | primeOrder = gpo.IsPrimeOrder() 146 | } 147 | 148 | // Verify additive and multiplicative identities of the generator. 149 | ptmp.Mul(stmp.SetInt64(-1), nil).Add(ptmp, gen) 150 | if !ptmp.Equal(pzero) { 151 | t.Errorf("generator additive identity doesn't work: %v (x) -1 (+) %v != %v the group point identity", 152 | ptmp.Mul(stmp.SetInt64(-1), nil), gen, pzero) 153 | } 154 | // secret.Inv works only in prime-order groups 155 | if primeOrder { 156 | ptmp.Mul(stmp.SetInt64(2), nil).Mul(stmp.Inv(stmp), ptmp) 157 | if !ptmp.Equal(gen) { 158 | t.Errorf("generator multiplicative identity doesn't work:\n%v (x) %v = %v\n%[3]v (x) %v = %v", 159 | ptmp.Base().String(), stmp.SetInt64(2).String(), 160 | ptmp.Mul(stmp.SetInt64(2), nil).String(), 161 | stmp.Inv(stmp).String(), 162 | ptmp.Mul(stmp.SetInt64(2), nil).Mul(stmp.Inv(stmp), ptmp).String()) 163 | } 164 | } 165 | 166 | p1.Mul(s1, gen) 167 | p2.Mul(s2, gen) 168 | if p1.Equal(p2) { 169 | t.Errorf("encryption isn't producing unique points: %v (x) %v == %v (x) %[2]v == %[4]v", s1, gen, s2, p1) 170 | } 171 | points = append(points, p1) 172 | 173 | dh1 := g.Point().Mul(s2, p1) 174 | dh2 := g.Point().Mul(s1, p2) 175 | if !dh1.Equal(dh2) { 176 | t.Errorf("Diffie-Hellman didn't work: %v == %v (x) %v != %v (x) %v == %v", dh1, s2, p1, s1, p2, dh2) 177 | } 178 | points = append(points, dh1) 179 | t.Logf("shared secret = %v", dh1) 180 | 181 | // Test secret inverse to get from dh1 back to p1 182 | if primeOrder { 183 | ptmp.Mul(g.Scalar().Inv(s2), dh1) 184 | if !ptmp.Equal(p1) { 185 | t.Errorf("Scalar inverse didn't work: %v != (-)%v (x) %v == %v", p1, s2, dh1, ptmp) 186 | } 187 | } 188 | 189 | // Zero and One identity secrets 190 | //println("dh1^0 = ",ptmp.Mul(dh1, szero).String()) 191 | if !ptmp.Mul(szero, dh1).Equal(pzero) { 192 | t.Errorf("Encryption with secret=0 didn't work: %v (x) %v == %v != %v", szero, dh1, ptmp, pzero) 193 | } 194 | if !ptmp.Mul(sone, dh1).Equal(dh1) { 195 | t.Errorf("Encryption with secret=1 didn't work: %v (x) %v == %v != %[2]v", sone, dh1, ptmp) 196 | } 197 | 198 | // Additive homomorphic identities 199 | ptmp.Add(p1, p2) 200 | stmp.Add(s1, s2) 201 | pt2 := g.Point().Mul(stmp, gen) 202 | if !pt2.Equal(ptmp) { 203 | t.Errorf("Additive homomorphism doesn't work: %v + %v == %v, %[3]v (x) %v == %v != %v == %v (+) %v", 204 | s1, s2, stmp, gen, pt2, ptmp, p1, p2) 205 | } 206 | ptmp.Sub(p1, p2) 207 | stmp.Sub(s1, s2) 208 | pt2.Mul(stmp, gen) 209 | if !pt2.Equal(ptmp) { 210 | t.Errorf("Additive homomorphism doesn't work: %v - %v == %v, %[3]v (x) %v == %v != %v == %v (-) %v", 211 | s1, s2, stmp, gen, pt2, ptmp, p1, p2) 212 | } 213 | st2 := g.Scalar().Neg(s2) 214 | st2.Add(s1, st2) 215 | if !stmp.Equal(st2) { 216 | t.Errorf("Scalar.Neg doesn't work: -%v == %v, %[2]v + %v == %v != %v", 217 | s2, g.Scalar().Neg(s2), s1, st2, stmp) 218 | } 219 | pt2.Neg(p2).Add(pt2, p1) 220 | if !pt2.Equal(ptmp) { 221 | t.Errorf("Point.Neg doesn't work: (-)%v == %v, %[2]v (+) %v == %v != %v", 222 | p2, g.Point().Neg(p2), p1, pt2, ptmp) 223 | } 224 | 225 | // Multiplicative homomorphic identities 226 | stmp.Mul(s1, s2) 227 | if !ptmp.Mul(stmp, gen).Equal(dh1) { 228 | t.Errorf("Multiplicative homomorphism doesn't work: %v * %v == %v, %[2]v (x) %v == %v != %v", 229 | s1, s2, stmp, gen, ptmp, dh1) 230 | } 231 | if primeOrder { 232 | st2.Inv(s2) 233 | st2.Mul(st2, stmp) 234 | if !st2.Equal(s1) { 235 | t.Errorf("Scalar division doesn't work: %v^-1 * %v == %v * %[2]v == %[4]v != %v", 236 | s2, stmp, g.Scalar().Inv(s2), st2, s1) 237 | } 238 | st2.Div(stmp, s2) 239 | if !st2.Equal(s1) { 240 | t.Errorf("Scalar division doesn't work: %v / %v == %v != %v", 241 | stmp, s2, st2, s1) 242 | } 243 | } 244 | 245 | // Test randomly picked points 246 | last := gen 247 | for i := 0; i < 5; i++ { 248 | rgen := g.Point().Pick(rand) 249 | if rgen.Equal(last) { 250 | t.Errorf("Pick() not producing unique points: got %v twice", rgen) 251 | } 252 | last = rgen 253 | 254 | ptmp.Mul(stmp.SetInt64(-1), rgen).Add(ptmp, rgen) 255 | if !ptmp.Equal(pzero) { 256 | t.Errorf("random generator fails additive identity: %v (x) %v == %v, %v (+) %[3]v == %[5]v != %v", 257 | g.Scalar().SetInt64(-1), rgen, g.Point().Mul(g.Scalar().SetInt64(-1), rgen), 258 | rgen, g.Point().Mul(g.Scalar().SetInt64(-1), rgen), pzero) 259 | } 260 | if primeOrder { 261 | ptmp.Mul(stmp.SetInt64(2), rgen).Mul(stmp.Inv(stmp), ptmp) 262 | if !ptmp.Equal(rgen) { 263 | t.Errorf("random generator fails multiplicative identity: %v (x) (2 (x) %v) == %v != %[2]v", 264 | stmp, rgen, ptmp) 265 | } 266 | } 267 | points = append(points, rgen) 268 | } 269 | 270 | // Test verifiable secret sharing 271 | 272 | // Test encoding and decoding 273 | for i := 0; i < 5; i++ { 274 | s := g.Scalar().Pick(rand) 275 | b, err := s.MarshalBinary() 276 | if err != nil { 277 | t.Errorf("encoding of secret fails: " + err.Error()) 278 | } 279 | if err := stmp.UnmarshalBinary(b); err != nil { 280 | t.Errorf("decoding of secret fails: " + err.Error()) 281 | } 282 | if !stmp.Equal(s) { 283 | t.Errorf("decoding produces different secret than encoded") 284 | } 285 | 286 | p := g.Point().Pick(rand) 287 | b, err = p.MarshalBinary() 288 | if err != nil { 289 | t.Errorf("encoding of point fails: " + err.Error()) 290 | } 291 | if err := ptmp.UnmarshalBinary(b); err != nil { 292 | t.Errorf("decoding of point fails: " + err.Error()) 293 | } 294 | if !ptmp.Equal(p) { 295 | t.Errorf("decoding produces different point than encoded") 296 | } 297 | } 298 | 299 | // Test that we can marshal/ unmarshal null point 300 | pzero = g.Point().Null() 301 | b, _ := pzero.MarshalBinary() 302 | repzero := g.Point() 303 | err := repzero.UnmarshalBinary(b) 304 | if err != nil { 305 | t.Errorf("Could not unmarshall binary %v: %v", b, err) 306 | } 307 | 308 | testPointSet(t, g, rand) 309 | testPointClone(t, g, rand) 310 | testScalarSet(t, g, rand) 311 | testScalarClone(t, g, rand) 312 | 313 | return points 314 | } 315 | 316 | // GroupTest applies a generic set of validation tests to a cryptographic Group. 317 | func GroupTest(t *testing.T, g group.Group) { 318 | testGroup(t, g, randomNew()) 319 | } 320 | 321 | // CompareGroups tests two group implementations that are supposed to be equivalent, 322 | // and compare their results. 323 | func CompareGroups(t *testing.T, fn func(key []byte) cipher.Stream, g1, g2 group.Group) { 324 | // Produce test results from the same pseudorandom seed 325 | r1 := testGroup(t, g1, fn(nil)) 326 | r2 := testGroup(t, g2, fn(nil)) 327 | 328 | // Compare resulting Points 329 | for i := range r1 { 330 | b1, _ := r1[i].MarshalBinary() 331 | b2, _ := r2[i].MarshalBinary() 332 | if !bytes.Equal(b1, b2) { 333 | t.Errorf("unequal result-pair %v\n1: %v\n2: %v", 334 | i, r1[i], r2[i]) 335 | } 336 | } 337 | } 338 | 339 | func randomNew() cipher.Stream { 340 | block, err := aes.NewCipher(make([]byte, 16)) 341 | if err != nil { 342 | panic(err) 343 | } 344 | iv := make([]byte, aes.BlockSize) 345 | if _, err := rand.Read(iv); err != nil { 346 | panic(err) 347 | } 348 | return cipher.NewCTR(block, iv) 349 | } 350 | -------------------------------------------------------------------------------- /go/tdh2/tdh2/tdh2.go: -------------------------------------------------------------------------------- 1 | // Package tdh2 implements the TDH2 protocol (Shoup and Gennaro, 2001: https://www.shoup.net/papers/thresh1.pdf). 2 | package tdh2 3 | 4 | import ( 5 | "bytes" 6 | "crypto/cipher" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group" 13 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/nist" 14 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/share" 15 | ) 16 | 17 | var ( 18 | // defaultHash is the default hash function used. Note, its output size 19 | // determines the input size in TDH2. 20 | defaultHash = sha256.New 21 | // InputSize determines the size of messages and labels. 22 | InputSize = defaultHash().Size() 23 | ) 24 | 25 | func parseGroup(group string) (group.Group, error) { 26 | switch group { 27 | case nist.NewP256().String(): 28 | return nist.NewP256(), nil 29 | } 30 | return nil, fmt.Errorf("unsupported group: %q", group) 31 | } 32 | 33 | // PrivateShare is a node's private share. It extends group.s share.PriShare. 34 | type PrivateShare struct { 35 | group group.Group 36 | index int 37 | v group.Scalar 38 | } 39 | 40 | func (s PrivateShare) String() string { 41 | return fmt.Sprintf("grp:%s idx:%d", s.group.String(), s.index) 42 | } 43 | 44 | func (s PrivateShare) Index() int { 45 | return s.index 46 | } 47 | 48 | // mulPoint returns a new point with value v*p, where v is a private scalar. 49 | // If p==nil, the returned point has value v*BasePoint. 50 | func (s *PrivateShare) mulPoint(p group.Point) group.Point { 51 | return s.group.Point().Mul(s.v, p) 52 | } 53 | 54 | // mulScalar returns a new scalar with value v*a where v is a private scalar. 55 | func (s *PrivateShare) mulScalar(a group.Scalar) group.Scalar { 56 | return s.group.Scalar().Mul(s.v, a) 57 | } 58 | 59 | func (p *PrivateShare) Clear() { 60 | p.group = nil 61 | p.index = 0 62 | p.v.Zero() 63 | } 64 | 65 | // privateShareRaw is used for PrivateShare (un)marshaling. 66 | type privateShareRaw struct { 67 | Group string 68 | Index int 69 | V []byte 70 | } 71 | 72 | func (s PrivateShare) Marshal() ([]byte, error) { 73 | v, err := s.v.MarshalBinary() 74 | if err != nil { 75 | return nil, fmt.Errorf("cannot marshal V: %w", err) 76 | } 77 | return json.Marshal(&privateShareRaw{ 78 | Group: s.group.String(), 79 | Index: s.index, 80 | V: v, 81 | }) 82 | } 83 | 84 | func (s *PrivateShare) Unmarshal(data []byte) error { 85 | var raw privateShareRaw 86 | err := json.Unmarshal(data, &raw) 87 | if err != nil { 88 | return fmt.Errorf("cannot unmarshal data: %w", err) 89 | } 90 | 91 | s.group, err = parseGroup(raw.Group) 92 | if err != nil { 93 | return fmt.Errorf("cannot parse group: %w", err) 94 | } 95 | 96 | s.index = raw.Index 97 | s.v = s.group.Scalar() 98 | if err = s.v.UnmarshalBinary(raw.V); err != nil { 99 | return fmt.Errorf("cannot unmarshal: %w", err) 100 | } 101 | return nil 102 | } 103 | 104 | // PubliKey defines a public and verification key. 105 | type PublicKey struct { 106 | group group.Group 107 | g_bar group.Point 108 | h group.Point 109 | hArray []group.Point 110 | } 111 | 112 | func (a *PublicKey) Equal(b *PublicKey) bool { 113 | if a.group.String() != b.group.String() || !a.g_bar.Equal(b.g_bar) || !a.h.Equal(b.h) { 114 | return false 115 | } 116 | if len(a.hArray) != len(b.hArray) { 117 | return false 118 | } 119 | for i := range a.hArray { 120 | if !a.hArray[i].Equal(b.hArray[i]) { 121 | return false 122 | } 123 | } 124 | return true 125 | } 126 | 127 | // MasterSecret keeps the master secret of a TDH2 instance. 128 | type MasterSecret struct { 129 | group group.Group 130 | s group.Scalar 131 | } 132 | 133 | func (m *MasterSecret) String() string { 134 | return fmt.Sprintf("group:%s value:hidden", m.group) 135 | } 136 | 137 | func (m *MasterSecret) Clear() { 138 | m.group = nil 139 | m.s.Zero() 140 | } 141 | 142 | // masterSecretRaw is used for MasterSecret (un)marshaling. 143 | type masterSecretRaw struct { 144 | Group string 145 | S []byte 146 | } 147 | 148 | func (m *MasterSecret) Marshal() ([]byte, error) { 149 | s, err := m.s.MarshalBinary() 150 | if err != nil { 151 | return nil, fmt.Errorf("cannot marshal s: %w", err) 152 | } 153 | return json.Marshal(&masterSecretRaw{ 154 | Group: m.group.String(), 155 | S: s, 156 | }) 157 | } 158 | 159 | func (m *MasterSecret) Unmarshal(data []byte) error { 160 | var raw masterSecretRaw 161 | if err := json.Unmarshal(data, &raw); err != nil { 162 | return fmt.Errorf("cannot unmarshal data: %w", err) 163 | } 164 | var err error 165 | m.group, err = parseGroup(raw.Group) 166 | if err != nil { 167 | return fmt.Errorf("cannot parse group: %w", err) 168 | } 169 | m.s = m.group.Scalar() 170 | if err := m.s.UnmarshalBinary(raw.S); err != nil { 171 | return fmt.Errorf("cannot unmarshal s: %w", err) 172 | } 173 | return nil 174 | } 175 | 176 | // publicKeyRaw is used for PublicKey (un)marshaling. 177 | type publicKeyRaw struct { 178 | Group string 179 | G_bar []byte 180 | H []byte 181 | HArray [][]byte 182 | } 183 | 184 | func (p PublicKey) Marshal() ([]byte, error) { 185 | gbar, err := p.g_bar.MarshalBinary() 186 | if err != nil { 187 | return nil, fmt.Errorf("marshaling G_bar: %w", err) 188 | } 189 | 190 | h, err := p.h.MarshalBinary() 191 | if err != nil { 192 | return nil, fmt.Errorf("marshaling H: %w", err) 193 | } 194 | 195 | harray := [][]byte{} 196 | for _, h := range p.hArray { 197 | d, err := h.MarshalBinary() 198 | if err != nil { 199 | return nil, fmt.Errorf("cannot marshal H: %w", err) 200 | } 201 | harray = append(harray, d) 202 | } 203 | 204 | return json.Marshal(&publicKeyRaw{ 205 | Group: p.group.String(), 206 | G_bar: gbar, 207 | H: h, 208 | HArray: harray, 209 | }) 210 | } 211 | 212 | func (p *PublicKey) Unmarshal(data []byte) error { 213 | var raw publicKeyRaw 214 | err := json.Unmarshal(data, &raw) 215 | if err != nil { 216 | return fmt.Errorf("cannot unmarshal data: %w", err) 217 | } 218 | 219 | p.group, err = parseGroup(raw.Group) 220 | if err != nil { 221 | return fmt.Errorf("cannot parse group: %w", err) 222 | } 223 | 224 | p.g_bar = p.group.Point() 225 | if err = p.g_bar.UnmarshalBinary(raw.G_bar); err != nil { 226 | return fmt.Errorf("unmarshaling G_bar: %w", err) 227 | } 228 | 229 | p.h = p.group.Point() 230 | if err = p.h.UnmarshalBinary(raw.H); err != nil { 231 | return fmt.Errorf("unmarshaling H: %w", err) 232 | } 233 | 234 | p.hArray = []group.Point{} 235 | for _, h := range raw.HArray { 236 | new := p.group.Point() 237 | if err = new.UnmarshalBinary(h); err != nil { 238 | return fmt.Errorf("cannot unmarshal point: %w", err) 239 | } 240 | p.hArray = append(p.hArray, new) 241 | } 242 | 243 | return nil 244 | } 245 | 246 | // GenerateKeys generates a master secret, public key, and secret key shares according to TDH2 paper. 247 | // It takes cryptographic group to be used, master secret to be used (if nil, a new secret is generated), 248 | // the total number of nodes n, the number of shares sufficient for decryption k, and a randomness source. 249 | // It returns the master secret (either passed or generated), public key, and secret key shares. 250 | func GenerateKeys(grp group.Group, ms *MasterSecret, k, n int, rand cipher.Stream) (*MasterSecret, *PublicKey, []*PrivateShare, error) { 251 | if k > n { 252 | return nil, nil, nil, fmt.Errorf("threshold is higher than total number of nodes") 253 | } 254 | if k <= 0 { 255 | return nil, nil, nil, fmt.Errorf("threshold has to be positive") 256 | } 257 | if ms != nil && grp.String() != ms.group.String() { 258 | return nil, nil, nil, fmt.Errorf("inconsistent groups") 259 | } 260 | 261 | var s group.Scalar 262 | if ms != nil { 263 | s = ms.s 264 | } 265 | poly := share.NewPriPoly(grp, k, s, rand) 266 | x := poly.Secret() 267 | if ms != nil && !x.Equal(ms.s) { 268 | return nil, nil, nil, fmt.Errorf("generated wrong secret") 269 | } 270 | 271 | HArray := make([]group.Point, n) 272 | shares := poly.Shares(n) 273 | privShares := []*PrivateShare{} 274 | // IDs are assigned consecutively from 0. 275 | for i, s := range shares { 276 | if i != s.I { 277 | return nil, nil, nil, fmt.Errorf("share index=%d, expect=%d", s.I, i) 278 | } 279 | HArray[i] = grp.Point().Mul(s.V, nil) 280 | privShares = append(privShares, &PrivateShare{grp, s.I, s.V}) 281 | } 282 | 283 | return &MasterSecret{ 284 | group: grp, 285 | s: x}, 286 | &PublicKey{ 287 | group: grp, 288 | g_bar: grp.Point().Pick(rand), 289 | h: grp.Point().Mul(x, nil), 290 | hArray: HArray, 291 | }, privShares, nil 292 | } 293 | 294 | // Redeal re-deals private shares such that new quorums can decrypt old ciphertexts. It takes the 295 | // previous public key and master secret as well as the number of nodes sufficient for decrypt k, 296 | // the total number of nodes n, and a randomness source. It returns a new public key and private shares. 297 | // The master secret passed corresponds to the public key returned. The old public key can still be used 298 | // for encryption but it cannot be used for share verification (the new key has to be used instead). 299 | func Redeal(pk *PublicKey, ms *MasterSecret, k, n int, rand cipher.Stream) (*PublicKey, []*PrivateShare, error) { 300 | if ms == nil { 301 | return nil, nil, fmt.Errorf("nil secret") 302 | } 303 | _, new, shares, err := GenerateKeys(pk.group, ms, k, n, rand) 304 | if err != nil { 305 | return nil, nil, fmt.Errorf("cannot generate keys: %w", err) 306 | } 307 | return &PublicKey{ 308 | group: pk.group, 309 | g_bar: pk.g_bar, 310 | h: pk.h, 311 | hArray: new.hArray, 312 | }, shares, nil 313 | } 314 | 315 | // Encrypt a message with a label (see p15 of the paper). 316 | func Encrypt(pk *PublicKey, msg []byte, label []byte, rand cipher.Stream) (*Ciphertext, error) { 317 | r := pk.group.Scalar().Pick(rand) 318 | s := pk.group.Scalar().Pick(rand) 319 | 320 | h, err := hash1(pk.group.String(), pk.group.Point().Mul(r, pk.h)) 321 | if err != nil { 322 | return nil, fmt.Errorf("cannot hash: %w", err) 323 | } 324 | c, err := xor(h, msg) 325 | if err != nil { 326 | return nil, fmt.Errorf("cannot xor: %w", err) 327 | } 328 | 329 | u := pk.group.Point().Mul(r, nil) 330 | w := pk.group.Point().Mul(s, nil) 331 | u_bar := pk.group.Point().Mul(r, pk.g_bar) 332 | w_bar := pk.group.Point().Mul(s, pk.g_bar) 333 | e, err := hash2(c, label, u, w, u_bar, w_bar, pk.group) 334 | if err != nil { 335 | return nil, fmt.Errorf("cannot generate e: %w", err) 336 | } 337 | f := pk.group.Scalar().Add(s, pk.group.Scalar().Mul(r, e.Clone())) 338 | 339 | return &Ciphertext{ 340 | group: pk.group, 341 | c: c, 342 | label: label, 343 | u: u, 344 | u_bar: u_bar, 345 | e: e, 346 | f: f, 347 | }, nil 348 | } 349 | 350 | // VerifyShare verifies the correctness of the decryption share obtained from node i. 351 | // The caller has to ensure that the ciphertext is validated. 352 | func VerifyShare(pk *PublicKey, ctxt *Ciphertext, share *DecryptionShare) error { 353 | if pk.group.String() != ctxt.group.String() { 354 | return fmt.Errorf("incorrect ciphertext group: %q", ctxt.group) 355 | } 356 | 357 | if pk.group.String() != share.group.String() { 358 | return fmt.Errorf("incorrect share group: %q", share.group) 359 | } 360 | 361 | if err := checkEi(pk, ctxt, share); err != nil { 362 | return fmt.Errorf("failed format validity check: %w", err) 363 | } 364 | 365 | return nil 366 | } 367 | 368 | // checkEi checks the validity of param E_i to ensure that it is a DH triple (formula 3 on p13). 369 | func checkEi(pk *PublicKey, ctxt *Ciphertext, share *DecryptionShare) error { 370 | g := pk.group 371 | ui_hat := g.Point().Sub(g.Point().Mul(share.f_i, ctxt.u), g.Point().Mul(share.e_i, share.u_i)) 372 | if share.index < 0 || share.index >= len(pk.hArray) { 373 | return fmt.Errorf("invalid share index") 374 | } 375 | hi_hat := g.Point().Sub(g.Point().Mul(share.f_i, nil), g.Point().Mul(share.e_i, pk.hArray[share.index])) 376 | ei, err := hash4(share.u_i, ui_hat, hi_hat, pk.group) 377 | if err != nil { 378 | return fmt.Errorf("cannot generate e_i: %w", err) 379 | } 380 | if !share.e_i.Equal(ei) { 381 | return fmt.Errorf("error during the verification of E_i") 382 | } 383 | return nil 384 | } 385 | 386 | // Ciphertext defines a ciphertext as output from the Encryption algorithm. 387 | type Ciphertext struct { 388 | group group.Group 389 | c []byte 390 | label []byte 391 | u group.Point 392 | u_bar group.Point 393 | e group.Scalar 394 | f group.Scalar 395 | } 396 | 397 | // Verify checks if the ciphertext matches the public key 398 | // (i.e., it checks the validity of param e -- see formula 4 on p15). 399 | func (c *Ciphertext) Verify(pk *PublicKey) error { 400 | if c.group.String() != pk.group.String() { 401 | return fmt.Errorf("group mismatch") 402 | } 403 | w := pk.group.Point().Sub(pk.group.Point().Mul(c.f, nil), pk.group.Point().Mul(c.e, c.u)) 404 | w_bar := pk.group.Point().Sub(pk.group.Point().Mul(c.f, pk.g_bar), pk.group.Point().Mul(c.e, c.u_bar)) 405 | e, err := hash2(c.c, c.label, c.u, w, c.u_bar, w_bar, pk.group) 406 | if err != nil { 407 | return fmt.Errorf("cannot compute e: %w", err) 408 | } 409 | if !c.e.Equal(e) { 410 | return fmt.Errorf("wrong e") 411 | } 412 | return nil 413 | } 414 | 415 | func (a *Ciphertext) Equal(b *Ciphertext) bool { 416 | if a.group.String() != b.group.String() || 417 | !bytes.Equal(a.c, b.c) || 418 | !bytes.Equal(a.label, b.label) || 419 | !a.u.Equal(b.u) || 420 | !a.u_bar.Equal(b.u_bar) || 421 | !a.e.Equal(b.e) || 422 | !a.f.Equal(b.f) { 423 | return false 424 | } 425 | return true 426 | 427 | } 428 | 429 | // Decrypt decrypts a ciphertext using a secret key share x_i according to TDH2 paper. 430 | // The caller has to ensure that the ciphertext is validated. 431 | func (ctxt *Ciphertext) Decrypt(group group.Group, x_i *PrivateShare, rand cipher.Stream) (*DecryptionShare, error) { 432 | if group.String() != ctxt.group.String() { 433 | return nil, fmt.Errorf("incorrect ciphertext group: %q", ctxt.group) 434 | } 435 | if group.String() != x_i.group.String() { 436 | return nil, fmt.Errorf("incorrect share group: %q", x_i.group) 437 | } 438 | 439 | s_i := group.Scalar().Pick(rand) 440 | u_i := x_i.mulPoint(ctxt.u) 441 | u_hat := group.Point().Mul(s_i, ctxt.u) 442 | h_hat := group.Point().Mul(s_i, nil) 443 | e_i, err := hash4(u_i, u_hat, h_hat, group) 444 | if err != nil { 445 | return nil, fmt.Errorf("cannot generate e_i: %w", err) 446 | } 447 | f_i := group.Scalar().Add(s_i, x_i.mulScalar(e_i.Clone())) 448 | return &DecryptionShare{ 449 | group: group, 450 | index: x_i.index, 451 | u_i: u_i, 452 | e_i: e_i, 453 | f_i: f_i, 454 | }, nil 455 | } 456 | 457 | // CombineShares combines a set of decryption shares and returns the decrypted message. 458 | // The caller has to ensure that the ciphertext is validated, the decryption shares are valid, 459 | // all the shares are distinct and the number of them is at least k. 460 | func (c *Ciphertext) CombineShares(group group.Group, shares []*DecryptionShare, k, n int) ([]byte, error) { 461 | if group.String() != c.group.String() { 462 | return nil, fmt.Errorf("incorrect ciphertext group: %q", c.group) 463 | } 464 | 465 | if len(shares) < k { 466 | return nil, fmt.Errorf("too few shares") 467 | } 468 | 469 | pubShares := []*share.PubShare{} 470 | for _, s := range shares { 471 | if group.String() != s.group.String() { 472 | return nil, fmt.Errorf("incorrect share group: %q", s.group) 473 | } 474 | pubShares = append(pubShares, &share.PubShare{ 475 | I: s.index, 476 | V: s.u_i, 477 | }) 478 | } 479 | 480 | arg, err := share.RecoverCommit(group, pubShares, k, n) 481 | if err != nil { 482 | return nil, fmt.Errorf("cannot recover secret: %w", err) 483 | } 484 | 485 | h, err := hash1(c.group.String(), arg) 486 | if err != nil { 487 | return nil, fmt.Errorf("failed to marshal %q: %w", arg, err) 488 | } 489 | 490 | return xor(h, c.c) 491 | } 492 | 493 | // ciphertextRaw is used for Ciphertext (un)marshaling. 494 | type ciphertextRaw struct { 495 | Group string 496 | C []byte 497 | Label []byte 498 | U []byte 499 | U_bar []byte 500 | E []byte 501 | F []byte 502 | } 503 | 504 | func (c Ciphertext) Marshal() ([]byte, error) { 505 | u, err := c.u.MarshalBinary() 506 | if err != nil { 507 | return nil, fmt.Errorf("cannot marshal U: %w", err) 508 | } 509 | ubar, err := c.u_bar.MarshalBinary() 510 | if err != nil { 511 | return nil, fmt.Errorf("cannot marshal U_bar: %w", err) 512 | } 513 | f, err := c.f.MarshalBinary() 514 | if err != nil { 515 | return nil, fmt.Errorf("cannot marshal F: %w", err) 516 | } 517 | e, err := c.e.MarshalBinary() 518 | if err != nil { 519 | return nil, fmt.Errorf("cannot marshal E: %w", err) 520 | } 521 | return json.Marshal(&ciphertextRaw{ 522 | Group: c.group.String(), 523 | C: c.c, 524 | Label: c.label, 525 | U: u, 526 | U_bar: ubar, 527 | E: e, 528 | F: f, 529 | }) 530 | } 531 | 532 | func (c *Ciphertext) Unmarshal(data []byte) error { 533 | var raw ciphertextRaw 534 | err := json.Unmarshal(data, &raw) 535 | if err != nil { 536 | return fmt.Errorf("cannot unmarshal data: %w", err) 537 | } 538 | c.c = raw.C 539 | c.label = raw.Label 540 | c.group, err = parseGroup(raw.Group) 541 | if err != nil { 542 | return fmt.Errorf("cannot parse group: %w", err) 543 | } 544 | c.e = c.group.Scalar() 545 | if err = c.e.UnmarshalBinary(raw.E); err != nil { 546 | return fmt.Errorf("cannot unmarshal E: %w", err) 547 | } 548 | c.u = c.group.Point() 549 | if err = c.u.UnmarshalBinary(raw.U); err != nil { 550 | return fmt.Errorf("cannot unmarshal U: %w", err) 551 | } 552 | c.u_bar = c.group.Point() 553 | if err = c.u_bar.UnmarshalBinary(raw.U_bar); err != nil { 554 | return fmt.Errorf("cannot unmarshal U_bar: %w", err) 555 | } 556 | c.f = c.group.Scalar() 557 | if err = c.f.UnmarshalBinary(raw.F); err != nil { 558 | return fmt.Errorf("cannot unmarshal F: %w", err) 559 | } 560 | return nil 561 | } 562 | 563 | // DecryptionShare defines a decryption share 564 | type DecryptionShare struct { 565 | group group.Group 566 | index int 567 | u_i group.Point 568 | e_i group.Scalar 569 | f_i group.Scalar 570 | } 571 | 572 | // TODO(pszal): test + fix tests which currently ignore share equality 573 | func (a *DecryptionShare) Equal(b *DecryptionShare) bool { 574 | if a.group.String() != b.group.String() || 575 | a.index != b.index || 576 | !a.u_i.Equal(b.u_i) || 577 | !a.e_i.Equal(b.e_i) || 578 | !a.f_i.Equal(b.f_i) { 579 | return false 580 | } 581 | return true 582 | } 583 | 584 | func (d DecryptionShare) Index() int { 585 | return d.index 586 | } 587 | 588 | // decryptionShareRaw is used for DecryptionShare (un)marshaling. 589 | type decryptionShareRaw struct { 590 | Group string 591 | Index int 592 | U_i []byte 593 | E_i []byte 594 | F_i []byte 595 | } 596 | 597 | func (d DecryptionShare) Marshal() ([]byte, error) { 598 | u, err := d.u_i.MarshalBinary() 599 | if err != nil { 600 | return nil, fmt.Errorf("cannot marshal U_i: %w", err) 601 | } 602 | f, err := d.f_i.MarshalBinary() 603 | if err != nil { 604 | return nil, fmt.Errorf("cannot marshal F_i: %w", err) 605 | } 606 | e, err := d.e_i.MarshalBinary() 607 | if err != nil { 608 | return nil, fmt.Errorf("cannot marshal E_i: %w", err) 609 | } 610 | return json.Marshal(&decryptionShareRaw{ 611 | Group: d.group.String(), 612 | Index: d.index, 613 | U_i: u, 614 | E_i: e, 615 | F_i: f, 616 | }) 617 | } 618 | 619 | func (d *DecryptionShare) Unmarshal(data []byte) error { 620 | var raw decryptionShareRaw 621 | err := json.Unmarshal(data, &raw) 622 | if err != nil { 623 | return fmt.Errorf("cannot unmarshal data: %w", err) 624 | } 625 | d.index = raw.Index 626 | d.group, err = parseGroup(raw.Group) 627 | if err != nil { 628 | return fmt.Errorf("cannot parse group: %w", err) 629 | } 630 | d.e_i = d.group.Scalar() 631 | if err = d.e_i.UnmarshalBinary(raw.E_i); err != nil { 632 | return fmt.Errorf("cannot unmarshal E_i: %w", err) 633 | } 634 | d.u_i = d.group.Point() 635 | if err = d.u_i.UnmarshalBinary(raw.U_i); err != nil { 636 | return fmt.Errorf("cannot unmarshal U_i: %w", err) 637 | } 638 | d.f_i = d.group.Scalar() 639 | if err = d.f_i.UnmarshalBinary(raw.F_i); err != nil { 640 | return fmt.Errorf("cannot unmarshal F_i: %w", err) 641 | } 642 | return nil 643 | } 644 | 645 | // hash is a generic hash function 646 | func hash(msg []byte) []byte { 647 | h := defaultHash() 648 | h.Write(msg) 649 | return h.Sum(nil) 650 | } 651 | 652 | // hash1 is an implementation of the H_1 hash function (see p15 of the paper). 653 | func hash1(group string, g group.Point) ([]byte, error) { 654 | point, err := concatenate(group, g) 655 | if err != nil { 656 | return nil, fmt.Errorf("cannot concatenate points: %w", err) 657 | } 658 | return hash(append([]byte("tdh2hash1"), point...)), nil 659 | } 660 | 661 | // hash2 is an implementation of the H_2 hash function (see p15 of the paper). 662 | func hash2(msg, label []byte, g1, g2, g3, g4 group.Point, group group.Group) (group.Scalar, error) { 663 | if len(msg) != len(label) || len(msg) != InputSize { 664 | return nil, fmt.Errorf("message and label must be %dB long", InputSize) 665 | } 666 | 667 | points, err := concatenate(group.String(), g1, g2, g3, g4) 668 | if err != nil { 669 | return nil, fmt.Errorf("cannot concatenate points: %w", err) 670 | } 671 | input := []byte("tdh2hash2") 672 | for _, arg := range [][]byte{msg, label, points} { 673 | input = append(input, arg...) 674 | } 675 | 676 | return group.Scalar().SetBytes(hash(input)), nil 677 | } 678 | 679 | // hash4 is an implementation of the H_4 hash function (see p15 of the paper). 680 | func hash4(g1, g2, g3 group.Point, group group.Group) (group.Scalar, error) { 681 | points, err := concatenate(group.String(), g1, g2, g3) 682 | if err != nil { 683 | return nil, fmt.Errorf("cannot concatenate points: %w", err) 684 | } 685 | h := hash(append([]byte("tdh2hash4"), points...)) 686 | 687 | return group.Scalar().SetBytes(h), nil 688 | } 689 | 690 | // concatenate marshals and concatenates points (elements of a group). It is 691 | // used in hash functions. 692 | func concatenate(group string, points ...group.Point) ([]byte, error) { 693 | final := group 694 | for _, point := range points { 695 | p, err := point.MarshalBinary() 696 | if err != nil { 697 | return nil, fmt.Errorf("cannot marshal point=%v err=%v", point, err) 698 | } 699 | final += "," + hex.EncodeToString(p) 700 | } 701 | return []byte(final), nil 702 | } 703 | 704 | // xor computes and returns XOR between two slices. 705 | func xor(a, b []byte) ([]byte, error) { 706 | if len(a) != len(b) { 707 | return nil, fmt.Errorf("length of byte slices is not equivalent: %d != %d", len(a), len(b)) 708 | } 709 | buf := make([]byte, len(a)) 710 | for i := range a { 711 | buf[i] = a[i] ^ b[i] 712 | } 713 | return buf, nil 714 | } 715 | -------------------------------------------------------------------------------- /go/tdh2/tdh2easy/js_test.go: -------------------------------------------------------------------------------- 1 | package tdh2easy 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | const jsTestPath = "../../../js/tdh2/test/test.js" 11 | 12 | func TestJS(t *testing.T) { 13 | _, pk, sh, err := GenerateKeys(2, 3) 14 | if err != nil { 15 | t.Fatalf("GenerateKeys: %v", err) 16 | } 17 | b, err := pk.Marshal() 18 | if err != nil { 19 | t.Fatalf("Marshal: %v", err) 20 | } 21 | 22 | cmdArgs := []string{jsTestPath, string(b)} 23 | cmd := exec.Command("node", cmdArgs...) 24 | output, err := cmd.CombinedOutput() 25 | if err != nil { 26 | t.Fatalf("Failed to run test.js: %s", err) 27 | } 28 | pairs := bytes.Split(output, []byte("\n")) 29 | // it contains the last empty newline 30 | if len(pairs) < 3 || len(pairs)%2 == 0 { 31 | t.Fatalf("Incorrect script output: %v", pairs) 32 | } 33 | pairs = pairs[:len(pairs)-1] 34 | for i := 0; i < len(pairs)/2; i++ { 35 | want, err := base64.StdEncoding.DecodeString(string(pairs[2*i])) 36 | if err != nil { 37 | t.Fatalf("b64Decode: %v", err) 38 | } 39 | var c Ciphertext 40 | if err := c.UnmarshalVerify(pairs[2*i+1], pk); err != nil { 41 | t.Fatalf("Unmarshal: %v", err) 42 | } 43 | dec := []*DecryptionShare{} 44 | for _, s := range sh { 45 | d, err := Decrypt(&c, s) 46 | if err != nil { 47 | t.Fatalf("Decrypt: %v", err) 48 | } 49 | dec = append(dec, d) 50 | } 51 | got, err := Aggregate(&c, dec, 3) 52 | if err != nil { 53 | t.Fatalf("Aggregate: %v", err) 54 | } 55 | if !bytes.Equal(got, want) { 56 | t.Errorf("got=%v; want=%v", got, want) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /go/tdh2/tdh2easy/sym.go: -------------------------------------------------------------------------------- 1 | package tdh2easy 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "fmt" 8 | ) 9 | 10 | // symKey generates a symmetric key. 11 | func symKey(keySize int) ([]byte, error) { 12 | key := make([]byte, keySize) 13 | if _, err := rand.Read(key); err != nil { 14 | return nil, fmt.Errorf("cannot generate key") 15 | } 16 | return key, nil 17 | } 18 | 19 | // symEncrypt encrypts the message using the AES-GCM cipher. 20 | func symEncrypt(msg, key []byte) ([]byte, []byte, error) { 21 | block, err := aes.NewCipher(key) 22 | if err != nil { 23 | return nil, nil, fmt.Errorf("cannot use AES: %v", err) 24 | } 25 | if uint64(len(msg)) > ((1<<32)-2)*uint64(block.BlockSize()) { 26 | return nil, nil, fmt.Errorf("message too long") 27 | } 28 | gcm, err := cipher.NewGCM(block) 29 | if err != nil { 30 | return nil, nil, fmt.Errorf("cannot use GCM mode: %v", err) 31 | } 32 | nonce := make([]byte, gcm.NonceSize()) 33 | if _, err := rand.Read(nonce); err != nil { 34 | return nil, nil, fmt.Errorf("cannot generate nonce") 35 | } 36 | 37 | return gcm.Seal(nil, nonce, msg, nil), nonce, nil 38 | } 39 | 40 | // symDecrypt decrypts the ciphertext using the AES-GCM cipher. 41 | func symDecrypt(nonce, ctxt, key []byte) ([]byte, error) { 42 | block, err := aes.NewCipher(key) 43 | if err != nil { 44 | return nil, fmt.Errorf("cannot use AES: %v", err) 45 | } 46 | gcm, err := cipher.NewGCM(block) 47 | if err != nil { 48 | return nil, fmt.Errorf("cannot use GCM mode: %v", err) 49 | } 50 | if len(nonce) != gcm.NonceSize() { 51 | return nil, fmt.Errorf("nonce must have %dB", gcm.NonceSize()) 52 | } 53 | 54 | return gcm.Open(nil, nonce, ctxt, nil) 55 | } 56 | -------------------------------------------------------------------------------- /go/tdh2/tdh2easy/sym_test.go: -------------------------------------------------------------------------------- 1 | package tdh2easy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/google/go-cmp/cmp/cmpopts" 8 | ) 9 | 10 | func TestSymmetric(t *testing.T) { 11 | key, err := symKey(16) 12 | if err != nil { 13 | t.Fatalf("symmetricKey: %v", err) 14 | } 15 | for _, tc := range []struct { 16 | name string 17 | msg []byte 18 | key []byte 19 | err error 20 | }{ 21 | { 22 | name: "OK", 23 | msg: []byte("msg"), 24 | key: key, 25 | }, 26 | { 27 | name: "OK (empty)", 28 | key: key, 29 | }, 30 | { 31 | name: "OK (long)", 32 | msg: make([]byte, 65536), 33 | key: key, 34 | }, 35 | { 36 | name: "wrong key length", 37 | msg: make([]byte, 65536), 38 | key: key[:4], 39 | err: cmpopts.AnyError, 40 | }, 41 | } { 42 | t.Run(tc.name, func(t *testing.T) { 43 | c, nonce, err := symEncrypt(tc.msg, tc.key) 44 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 45 | t.Errorf("err=%v, want=%v", err, tc.err) 46 | } else if err != nil { 47 | return 48 | } 49 | out, err := symDecrypt(nonce, c, key) 50 | if err != nil { 51 | t.Errorf("symmetricDecryption: %v", err) 52 | } 53 | if diff := cmp.Diff(tc.msg, out); diff != "" { 54 | t.Errorf("encrypted/decrypted message diff=%v", diff) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | func TestSymmetricDecryptionFail(t *testing.T) { 61 | msg := []byte("msg") 62 | key, err := symKey(16) 63 | if err != nil { 64 | t.Fatalf("symmetricKey: %v", err) 65 | } 66 | c, nonce, err := symEncrypt(msg, key) 67 | if err != nil { 68 | t.Fatalf("symmetricEncryption: %v", err) 69 | } 70 | for _, tc := range []struct { 71 | name string 72 | nonce []byte 73 | c []byte 74 | key []byte 75 | err error 76 | }{ 77 | { 78 | name: "OK", 79 | key: key, 80 | nonce: nonce, 81 | c: c, 82 | }, 83 | { 84 | name: "wrong key", 85 | key: []byte("key"), 86 | nonce: nonce, 87 | c: c, 88 | err: cmpopts.AnyError, 89 | }, 90 | { 91 | name: "wrong nonce", 92 | key: key, 93 | nonce: []byte("nonce"), 94 | c: c, 95 | err: cmpopts.AnyError, 96 | }, 97 | { 98 | name: "wrong c", 99 | key: key, 100 | nonce: nonce, 101 | c: []byte("ciphertext"), 102 | err: cmpopts.AnyError, 103 | }, 104 | } { 105 | t.Run(tc.name, func(t *testing.T) { 106 | out, err := symDecrypt(nonce, c, key) 107 | if err != nil { 108 | t.Errorf("symmetricDecryption: %v", err) 109 | } 110 | if diff := cmp.Diff(msg, out); diff != "" { 111 | t.Errorf("encrypted/decrypted message diff=%v", diff) 112 | } 113 | }) 114 | } 115 | } 116 | 117 | func FuzzSymEncryption(f *testing.F) { 118 | f.Add(16, []byte("sample message")) 119 | f.Add(24, []byte("another sample message")) 120 | f.Add(32, []byte("and another sample message")) 121 | f.Fuzz(func(t *testing.T, keySize int, msg []byte) { 122 | if keySize != 16 && keySize != 24 && keySize != 32 { 123 | t.Skip() 124 | } 125 | key, err := symKey(keySize) 126 | if err != nil { 127 | t.Fatalf("symKey(%v): %v", keySize, err) 128 | } 129 | c, n, err := symEncrypt(msg, key) 130 | if err != nil { 131 | t.Fatalf("symEncrypt(%v, %v): %v", msg, key, err) 132 | } 133 | p, err := symDecrypt(n, c, key) 134 | if err != nil { 135 | t.Fatalf("symDecryt(%v, %v, %v): %v", n, c, key, err) 136 | } 137 | if d := cmp.Diff(p, msg); d != "" { 138 | t.Fatalf("got/want diff=%v", d) 139 | } 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /go/tdh2/tdh2easy/tdh2easy.go: -------------------------------------------------------------------------------- 1 | // Package tdh2easy implements an easy interface of TDH2-based hybrid encryption. 2 | package tdh2easy 3 | 4 | import ( 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/json" 9 | "fmt" 10 | 11 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/nist" 12 | "github.com/smartcontractkit/tdh2/go/tdh2/tdh2" 13 | ) 14 | 15 | // key size used in symmetric encryption (AES). 256 bits is a higher securitylevel than provided 16 | // by the EC group deployed, but as tdh2.InputSize is 256 bits we decided to use the same value. 17 | const aes256KeySize = 32 18 | 19 | // defaultGroup is the default EC group used. 20 | var defaultGroup = nist.NewP256() 21 | 22 | // PrivateShare encodes TDH2 private share. 23 | type PrivateShare struct { 24 | p *tdh2.PrivateShare 25 | } 26 | 27 | // Index returns private share index. 28 | func (p *PrivateShare) Index() int { 29 | return p.p.Index() 30 | } 31 | 32 | func (p PrivateShare) Marshal() ([]byte, error) { 33 | return p.p.Marshal() 34 | } 35 | 36 | func (p *PrivateShare) MarshalJSON() ([]byte, error) { 37 | return p.Marshal() 38 | } 39 | 40 | func (p *PrivateShare) Unmarshal(data []byte) error { 41 | p.p = &tdh2.PrivateShare{} 42 | return p.p.Unmarshal(data) 43 | } 44 | 45 | func (p *PrivateShare) UnmarshalJSON(data []byte) error { 46 | return p.Unmarshal(data) 47 | } 48 | 49 | func (p *PrivateShare) Clear() { 50 | p.p.Clear() 51 | } 52 | 53 | // DecryptionShare encodes TDH2 decryption share. 54 | type DecryptionShare struct { 55 | d *tdh2.DecryptionShare 56 | } 57 | 58 | // Index returns private share index. 59 | func (d *DecryptionShare) Index() int { 60 | return d.d.Index() 61 | } 62 | 63 | func (d DecryptionShare) Marshal() ([]byte, error) { 64 | return d.d.Marshal() 65 | } 66 | 67 | func (d DecryptionShare) MarshalJSON() ([]byte, error) { 68 | return d.Marshal() 69 | } 70 | 71 | func (d *DecryptionShare) Unmarshal(data []byte) error { 72 | d.d = &tdh2.DecryptionShare{} 73 | return d.d.Unmarshal(data) 74 | } 75 | 76 | func (d *DecryptionShare) UnmarshalJSON(data []byte) error { 77 | return d.Unmarshal(data) 78 | } 79 | 80 | // PublicKey encodes TDH2 public key. 81 | type PublicKey struct { 82 | p *tdh2.PublicKey 83 | } 84 | 85 | func (p PublicKey) Marshal() ([]byte, error) { 86 | return p.p.Marshal() 87 | } 88 | 89 | func (p *PublicKey) MarshalJSON() ([]byte, error) { 90 | return p.Marshal() 91 | } 92 | 93 | func (p *PublicKey) Unmarshal(data []byte) error { 94 | p.p = &tdh2.PublicKey{} 95 | return p.p.Unmarshal(data) 96 | } 97 | 98 | func (p *PublicKey) UnmarshalJSON(data []byte) error { 99 | return p.Unmarshal(data) 100 | } 101 | 102 | // MasterSecret encodes TDH2 master key. 103 | type MasterSecret struct { 104 | m *tdh2.MasterSecret 105 | } 106 | 107 | func (m MasterSecret) Marshal() ([]byte, error) { 108 | return m.m.Marshal() 109 | } 110 | 111 | func (m MasterSecret) MarshalJSON() ([]byte, error) { 112 | return m.Marshal() 113 | } 114 | 115 | func (m *MasterSecret) Unmarshal(data []byte) error { 116 | m.m = &tdh2.MasterSecret{} 117 | return m.m.Unmarshal(data) 118 | } 119 | 120 | func (m MasterSecret) UnmarshalJSON(data []byte) error { 121 | return m.Unmarshal(data) 122 | } 123 | 124 | func (m *MasterSecret) Clear() { 125 | m.m.Clear() 126 | } 127 | 128 | // Ciphertext encodes hybrid ciphertext. 129 | type Ciphertext struct { 130 | tdh2Ctxt *tdh2.Ciphertext 131 | symCtxt []byte 132 | nonce []byte 133 | } 134 | 135 | // Decrypt returns a decryption share for the ciphertext. 136 | func Decrypt(c *Ciphertext, x_i *PrivateShare) (*DecryptionShare, error) { 137 | r, err := randStream() 138 | if err != nil { 139 | return nil, err 140 | } 141 | d, err := c.tdh2Ctxt.Decrypt(defaultGroup, x_i.p, r) 142 | if err != nil { 143 | return nil, err 144 | } 145 | return &DecryptionShare{d}, nil 146 | } 147 | 148 | // VerifyShare checks if the share matches the ciphertext and public key. 149 | func VerifyShare(c *Ciphertext, pk *PublicKey, share *DecryptionShare) error { 150 | return tdh2.VerifyShare(pk.p, c.tdh2Ctxt, share.d) 151 | } 152 | 153 | // Aggregate decrypts the TDH2-encrypted key and using it recovers the 154 | // symmetrically encrypted plaintext. It takes decryption shares and 155 | // the total number of participants as the arguments. 156 | // Ciphertext and shares MUST be verified before calling Aggregate, 157 | // all the shares have to be distinct and their number has to be 158 | // at least k (the scheme's threshold). 159 | func Aggregate(c *Ciphertext, shares []*DecryptionShare, n int) ([]byte, error) { 160 | sh := []*tdh2.DecryptionShare{} 161 | for _, s := range shares { 162 | sh = append(sh, s.d) 163 | } 164 | key, err := c.tdh2Ctxt.CombineShares(defaultGroup, sh, len(sh), n) 165 | if err != nil { 166 | return nil, fmt.Errorf("cannot combine shares: %w", err) 167 | } 168 | if aes256KeySize != len(key) { 169 | return nil, fmt.Errorf("incorrect key size") 170 | } 171 | return symDecrypt(c.nonce, c.symCtxt, key) 172 | } 173 | 174 | // randStream returns a stream cipher used for providing randomness. 175 | func randStream() (cipher.Stream, error) { 176 | key := make([]byte, aes256KeySize) 177 | if _, err := rand.Read(key); err != nil { 178 | return nil, fmt.Errorf("cannot generate key: %w", err) 179 | } 180 | iv := make([]byte, aes.BlockSize) 181 | if _, err := rand.Read(iv); err != nil { 182 | return nil, fmt.Errorf("cannot generate iv: %w", err) 183 | } 184 | block, err := aes.NewCipher(key) 185 | if err != nil { 186 | return nil, fmt.Errorf("cannot init aes: %w", err) 187 | } 188 | return cipher.NewCTR(block, iv), nil 189 | } 190 | 191 | type ciphertextRaw struct { 192 | TDH2Ctxt []byte 193 | SymCtxt []byte 194 | Nonce []byte 195 | } 196 | 197 | func (c *Ciphertext) Marshal() ([]byte, error) { 198 | ctxt, err := c.tdh2Ctxt.Marshal() 199 | if err != nil { 200 | return nil, fmt.Errorf("cannot marshal TDH2 ciphertext: %w", err) 201 | } 202 | return json.Marshal(&ciphertextRaw{ 203 | TDH2Ctxt: ctxt, 204 | SymCtxt: c.symCtxt, 205 | Nonce: c.nonce, 206 | }) 207 | } 208 | 209 | // UnmarshalVerify unmarshals ciphertext and verifies if it matches the public key. 210 | func (c *Ciphertext) UnmarshalVerify(data []byte, pk *PublicKey) error { 211 | var raw ciphertextRaw 212 | if err := json.Unmarshal(data, &raw); err != nil { 213 | return fmt.Errorf("cannot unmarshal data: %w", err) 214 | } 215 | c.symCtxt = raw.SymCtxt 216 | c.nonce = raw.Nonce 217 | c.tdh2Ctxt = &tdh2.Ciphertext{} 218 | if err := c.tdh2Ctxt.Unmarshal(raw.TDH2Ctxt); err != nil { 219 | return fmt.Errorf("cannot unmarshal TDH2 ciphertext: %w", err) 220 | } 221 | 222 | if err := c.tdh2Ctxt.Verify(pk.p); err != nil { 223 | return fmt.Errorf("tdh2 ciphertext verification: %w", err) 224 | } 225 | return nil 226 | } 227 | 228 | // GenerateKeys generates and returns, the master secret, public key, and private shares. It takes the 229 | // total number of nodes n and a threshold k (the number of shares sufficient for decryption). 230 | func GenerateKeys(k, n int) (*MasterSecret, *PublicKey, []*PrivateShare, error) { 231 | r, err := randStream() 232 | if err != nil { 233 | return nil, nil, nil, err 234 | } 235 | ms, pk, sh, err := tdh2.GenerateKeys(defaultGroup, nil, k, n, r) 236 | if err != nil { 237 | return nil, nil, nil, err 238 | } 239 | shares := []*PrivateShare{} 240 | for i := range sh { 241 | shares = append(shares, &PrivateShare{sh[i]}) 242 | } 243 | return &MasterSecret{ms}, &PublicKey{pk}, shares, nil 244 | } 245 | 246 | // Redeal re-deals private shares such that new quorums can decrypt old ciphertexts. 247 | // It takes the previous public key and master secret as well as the number of nodes 248 | // sufficient for decrypt k, and the total number of nodes n. It returns a new public 249 | // key and private shares. The master secret passed corresponds to the public key returned. 250 | // The old public key can still be used for encryption but it cannot be used for share 251 | // verification (the new key has to be used instead). 252 | func Redeal(pk *PublicKey, ms *MasterSecret, k, n int) (*PublicKey, []*PrivateShare, error) { 253 | r, err := randStream() 254 | if err != nil { 255 | return nil, nil, err 256 | } 257 | p, sh, err := tdh2.Redeal(pk.p, ms.m, k, n, r) 258 | if err != nil { 259 | return nil, nil, err 260 | } 261 | shares := []*PrivateShare{} 262 | for i := range sh { 263 | shares = append(shares, &PrivateShare{sh[i]}) 264 | } 265 | return &PublicKey{p}, shares, nil 266 | } 267 | 268 | // Encrypt generates a fresh symmetric key, encrypts and authenticates 269 | // the message with it, and encrypts the key using TDH2. It returns a 270 | // struct encoding the generated ciphertexts. 271 | func Encrypt(pk *PublicKey, msg []byte) (*Ciphertext, error) { 272 | if aes256KeySize != tdh2.InputSize { 273 | return nil, fmt.Errorf("incorrect key size") 274 | } 275 | // generate a fresh key and encrypt the message 276 | key, err := symKey(tdh2.InputSize) 277 | if err != nil { 278 | return nil, fmt.Errorf("cannot generate key: %w", err) 279 | } 280 | // for each encryption a fresh key and nonce are generated, 281 | // therefore the probability of nonce misuse is negligible 282 | symCtxt, nonce, err := symEncrypt(msg, key) 283 | if err != nil { 284 | return nil, fmt.Errorf("cannot encrypt message: %w", err) 285 | } 286 | 287 | r, err := randStream() 288 | if err != nil { 289 | return nil, err 290 | } 291 | // encrypt the key with TDH2 using empty label 292 | tdh2Ctxt, err := tdh2.Encrypt(pk.p, key, make([]byte, tdh2.InputSize), r) 293 | if err != nil { 294 | return nil, fmt.Errorf("cannot TDH2 encrypt: %w", err) 295 | } 296 | return &Ciphertext{ 297 | tdh2Ctxt: tdh2Ctxt, 298 | symCtxt: symCtxt, 299 | nonce: nonce, 300 | }, nil 301 | } 302 | -------------------------------------------------------------------------------- /go/tdh2/tdh2easy/tdh2easy_test.go: -------------------------------------------------------------------------------- 1 | package tdh2easy 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | "github.com/google/go-cmp/cmp/cmpopts" 11 | "github.com/smartcontractkit/tdh2/go/tdh2/lib/group/nist" 12 | "github.com/smartcontractkit/tdh2/go/tdh2/tdh2" 13 | ) 14 | 15 | func TestShareIndex(t *testing.T) { 16 | _, pk, sh, err := GenerateKeys(5, 10) 17 | if err != nil { 18 | t.Fatalf("GenerateKeys: %v", err) 19 | } 20 | for i := range sh { 21 | if sh[i].Index() != i { 22 | t.Errorf("index=%v, want=%v", sh[i].Index(), i) 23 | } 24 | } 25 | c, err := Encrypt(pk, []byte("msg")) 26 | if err != nil { 27 | t.Fatalf("Encrypt: %v", err) 28 | } 29 | for i, s := range sh { 30 | ds, err := Decrypt(c, s) 31 | if err != nil { 32 | t.Fatalf("Decrypt: %v", err) 33 | } 34 | if ds.Index() != i { 35 | t.Errorf("index=%v, want=%v", ds.Index(), i) 36 | } 37 | } 38 | } 39 | 40 | func TestPrivateShareMarshal(t *testing.T) { 41 | _, _, want, err := GenerateKeys(2, 3) 42 | if err != nil { 43 | t.Fatalf("GenerateKeys: %v", err) 44 | } 45 | b, err := want[0].Marshal() 46 | if err != nil { 47 | t.Fatalf("Marshal: %v", err) 48 | } 49 | var got PrivateShare 50 | if err := got.Unmarshal(b); err != nil { 51 | t.Fatalf("Unmarshal: %v", err) 52 | } 53 | if !reflect.DeepEqual(got.p, want[0].p) { 54 | t.Errorf("got=%v want=%v", got, want[0]) 55 | } 56 | if err := got.Unmarshal([]byte("broken")); err == nil { 57 | t.Errorf("Unmarshal did not fail") 58 | } 59 | } 60 | 61 | func TestDecryptionShareMarshal(t *testing.T) { 62 | _, pk, sh, err := GenerateKeys(2, 3) 63 | if err != nil { 64 | t.Fatalf("GenerateKeys: %v", err) 65 | } 66 | c, err := Encrypt(pk, []byte("msg")) 67 | if err != nil { 68 | t.Fatalf("Encrypt: %v", err) 69 | } 70 | want, err := Decrypt(c, sh[0]) 71 | if err != nil { 72 | t.Fatalf("Decrypt: %v", err) 73 | } 74 | b, err := want.Marshal() 75 | if err != nil { 76 | t.Fatalf("Marshal: %v", err) 77 | } 78 | var got DecryptionShare 79 | if err := got.Unmarshal(b); err != nil { 80 | t.Fatalf("Unmarshal: %v", err) 81 | } 82 | if !reflect.DeepEqual(got.d, want.d) { 83 | t.Errorf("got=%v want=%v", got, want) 84 | } 85 | if err := got.Unmarshal([]byte("broken")); err == nil { 86 | t.Errorf("Unmarshal did not fail") 87 | } 88 | } 89 | 90 | func TestPublicKeyMarshal(t *testing.T) { 91 | _, want, _, err := GenerateKeys(2, 3) 92 | if err != nil { 93 | t.Fatalf("GenerateKeys: %v", err) 94 | } 95 | b, err := want.Marshal() 96 | if err != nil { 97 | t.Fatalf("Marshal: %v", err) 98 | } 99 | var got PublicKey 100 | if err := got.Unmarshal(b); err != nil { 101 | t.Fatalf("Unmarshal: %v", err) 102 | } 103 | if !got.p.Equal(want.p) { 104 | t.Errorf("got=%v want=%v", got, want) 105 | } 106 | if err := got.Unmarshal([]byte("broken")); err == nil { 107 | t.Errorf("Unmarshal did not fail") 108 | } 109 | } 110 | 111 | func TestMasterSecretMarshal(t *testing.T) { 112 | want, _, _, err := GenerateKeys(2, 3) 113 | if err != nil { 114 | t.Fatalf("GenerateKeys: %v", err) 115 | } 116 | b, err := want.Marshal() 117 | if err != nil { 118 | t.Fatalf("Marshal: %v", err) 119 | } 120 | var got MasterSecret 121 | if err := got.Unmarshal(b); err != nil { 122 | t.Fatalf("Unmarshal: %v", err) 123 | } 124 | if !reflect.DeepEqual(got.m, want.m) { 125 | t.Errorf("got=%v want=%v", got, want) 126 | } 127 | if err := got.Unmarshal([]byte("broken")); err == nil { 128 | t.Errorf("Unmarshal did not fail") 129 | } 130 | } 131 | 132 | func TestCiphertextDecrypt(t *testing.T) { 133 | _, pk, share, err := GenerateKeys(1, 1) 134 | if err != nil { 135 | t.Fatalf("GenerateKeys: %v", err) 136 | } 137 | r, err := randStream() 138 | if err != nil { 139 | t.Fatalf("RandStream: %v", err) 140 | } 141 | _, _, wrong, err := tdh2.GenerateKeys(nist.NewP521(), nil, 1, 1, r) 142 | if err != nil { 143 | t.Fatalf("GenerateKeys: %v", err) 144 | } 145 | c, err := Encrypt(pk, []byte("msg")) 146 | if err != nil { 147 | t.Fatalf("Encrypt: %v", err) 148 | } 149 | if _, err := Decrypt(c, share[0]); err != nil { 150 | t.Errorf("Decrypt: %v", err) 151 | } 152 | if _, err := Decrypt(c, &PrivateShare{wrong[0]}); err == nil { 153 | t.Errorf("Decrypt did not fail") 154 | } 155 | } 156 | 157 | func TestCiphertextVerifyShare(t *testing.T) { 158 | _, pk, share, err := GenerateKeys(1, 1) 159 | if err != nil { 160 | t.Fatalf("GenerateKeys: %v", err) 161 | } 162 | _, _, wrongShare, err := GenerateKeys(1, 1) 163 | if err != nil { 164 | t.Fatalf("GenerateKeys: %v", err) 165 | } 166 | c, err := Encrypt(pk, []byte("msg")) 167 | if err != nil { 168 | t.Fatalf("Encrypt: %v", err) 169 | } 170 | ds, err := Decrypt(c, share[0]) 171 | if err != nil { 172 | t.Fatalf("Decrypt: %v", err) 173 | } 174 | wrongDs, err := Decrypt(c, wrongShare[0]) 175 | if err != nil { 176 | t.Fatalf("Decrypt: %v", err) 177 | } 178 | if err := VerifyShare(c, pk, ds); err != nil { 179 | t.Errorf("VerifyShare: %v", err) 180 | } 181 | if err := VerifyShare(c, pk, wrongDs); err == nil { 182 | t.Errorf("VerifyShare did not fail") 183 | } 184 | } 185 | 186 | func TestAggregate(t *testing.T) { 187 | k := 3 188 | n := 5 189 | _, pk, shares, err := GenerateKeys(k, n) 190 | if err != nil { 191 | t.Fatalf("GenerateKeys: %v", err) 192 | } 193 | msg := []byte("message") 194 | c, err := Encrypt(pk, msg) 195 | if err != nil { 196 | t.Fatalf("Encrypt: %v", err) 197 | } 198 | decShares := make([]*DecryptionShare, n) 199 | for i := range shares { 200 | ds, err := Decrypt(c, shares[i]) 201 | if err != nil { 202 | t.Fatalf("Decrypt: %v", err) 203 | } 204 | decShares[i] = ds 205 | } 206 | for _, tc := range []struct { 207 | name string 208 | ctxt *Ciphertext 209 | shares []*DecryptionShare 210 | err error 211 | }{ 212 | { 213 | name: "OK (all shares)", 214 | ctxt: c, 215 | shares: decShares, 216 | }, 217 | { 218 | name: "OK (min shares)", 219 | ctxt: c, 220 | shares: decShares[:k], 221 | }, 222 | { 223 | name: "not enough shares", 224 | ctxt: c, 225 | shares: decShares[:2], 226 | err: cmpopts.AnyError, 227 | }, 228 | { 229 | name: "wrong nonce", 230 | ctxt: &Ciphertext{ 231 | tdh2Ctxt: c.tdh2Ctxt, 232 | symCtxt: c.symCtxt, 233 | nonce: make([]byte, len(c.nonce)), 234 | }, 235 | shares: decShares, 236 | err: cmpopts.AnyError, 237 | }, 238 | { 239 | name: "wrong nonce size", 240 | ctxt: &Ciphertext{ 241 | tdh2Ctxt: c.tdh2Ctxt, 242 | symCtxt: c.symCtxt, 243 | nonce: []byte("nonce"), 244 | }, 245 | shares: decShares, 246 | err: cmpopts.AnyError, 247 | }, 248 | { 249 | name: "wrong symmetric ciphertext", 250 | ctxt: &Ciphertext{ 251 | tdh2Ctxt: c.tdh2Ctxt, 252 | symCtxt: []byte("ciphertext"), 253 | nonce: c.nonce, 254 | }, 255 | shares: decShares, 256 | err: cmpopts.AnyError, 257 | }, 258 | } { 259 | t.Run(tc.name, func(t *testing.T) { 260 | out, err := Aggregate(tc.ctxt, tc.shares, n) 261 | if !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 262 | t.Errorf("err=%v, want=%v", err, tc.err) 263 | } else if err != nil { 264 | return 265 | } 266 | if diff := cmp.Diff(msg, out); diff != "" { 267 | t.Errorf("encrypted decrypted message diff=%v", diff) 268 | } 269 | }) 270 | } 271 | } 272 | 273 | func TestCiphertextMarshal(t *testing.T) { 274 | _, pk, _, err := GenerateKeys(1, 1) 275 | if err != nil { 276 | t.Fatalf("GenerateKeys: %v", err) 277 | } 278 | want, err := Encrypt(pk, []byte("msg")) 279 | if err != nil { 280 | t.Fatalf("Encrypt: %v", err) 281 | } 282 | b, err := want.Marshal() 283 | if err != nil { 284 | t.Fatalf("Marshal: %v", err) 285 | } 286 | var got Ciphertext 287 | if err := got.UnmarshalVerify(b, pk); err != nil { 288 | t.Fatalf("Unmarshal: %v", err) 289 | } 290 | if d := cmp.Diff(got.symCtxt, want.symCtxt); d != "" { 291 | t.Errorf("got/want Ciphertext diff=%v", d) 292 | } 293 | if d := cmp.Diff(got.nonce, want.nonce); d != "" { 294 | t.Errorf("got/want Nonce diff=%v", d) 295 | } 296 | if !got.tdh2Ctxt.Equal(want.tdh2Ctxt) { 297 | t.Errorf("different ciphertexts") 298 | } 299 | } 300 | 301 | func TestCiphertextUnmarshal(t *testing.T) { 302 | _, pk, _, err := GenerateKeys(1, 1) 303 | if err != nil { 304 | t.Fatalf("GenerateKeys: %v", err) 305 | } 306 | _, wrong, _, err := GenerateKeys(1, 1) 307 | if err != nil { 308 | t.Fatalf("GenerateKeys: %v", err) 309 | } 310 | c, err := Encrypt(pk, []byte("msg")) 311 | if err != nil { 312 | t.Fatalf("Encrypt: %v", err) 313 | } 314 | cRaw, err := c.Marshal() 315 | if err != nil { 316 | t.Fatalf("Marshal: %v", err) 317 | } 318 | brokenTdh2, err := json.Marshal(&ciphertextRaw{ 319 | TDH2Ctxt: []byte("broken"), 320 | SymCtxt: []byte("ciphertext"), 321 | Nonce: []byte("nonce"), 322 | }) 323 | if err != nil { 324 | t.Fatalf("json.Marshal: %v", err) 325 | } 326 | for _, tc := range []struct { 327 | name string 328 | raw []byte 329 | pk *PublicKey 330 | err error 331 | }{ 332 | { 333 | name: "ok", 334 | raw: cRaw, 335 | pk: pk, 336 | }, 337 | { 338 | name: "wrong pk", 339 | raw: cRaw, 340 | pk: wrong, 341 | err: cmpopts.AnyError, 342 | }, 343 | { 344 | name: "broken", 345 | raw: []byte("broken"), 346 | pk: pk, 347 | err: cmpopts.AnyError, 348 | }, 349 | { 350 | name: "broken tdh2 ciphertext", 351 | raw: brokenTdh2, 352 | pk: pk, 353 | err: cmpopts.AnyError, 354 | }, 355 | } { 356 | t.Run(tc.name, func(t *testing.T) { 357 | var hc Ciphertext 358 | if err := hc.UnmarshalVerify(tc.raw, tc.pk); !cmp.Equal(err, tc.err, cmpopts.EquateErrors()) { 359 | t.Errorf("got err=%v, want=%v", err, tc.err) 360 | } 361 | }) 362 | } 363 | } 364 | 365 | func TestRedealEncryptNew(t *testing.T) { 366 | ms, pk, _, err := GenerateKeys(3, 5) 367 | if err != nil { 368 | t.Fatalf("GenerateKeys: %v", err) 369 | } 370 | want := []byte("msg") 371 | for _, tc := range []struct { 372 | name string 373 | k, n int 374 | }{ 375 | { 376 | name: "same n,k", 377 | k: 3, 378 | n: 5, 379 | }, 380 | { 381 | name: "smaller quorum", 382 | k: 2, 383 | n: 5, 384 | }, 385 | { 386 | name: "larger quorum", 387 | k: 4, 388 | n: 5, 389 | }, 390 | } { 391 | t.Run(tc.name, func(t *testing.T) { 392 | // generate new instance 393 | newPk, shares, err := Redeal(pk, ms, tc.k, tc.n) 394 | if err != nil { 395 | t.Fatalf("Redeal: %v", err) 396 | } 397 | // encrypt and decrypt using new keys 398 | c, err := Encrypt(newPk, want) 399 | if err != nil { 400 | t.Fatalf("Encrypt: %v", err) 401 | } 402 | ds := []*DecryptionShare{} 403 | for _, sh := range shares { 404 | d, err := Decrypt(c, sh) 405 | if err != nil { 406 | t.Fatalf("Decrypt: %v", err) 407 | } 408 | if err := VerifyShare(c, newPk, d); err != nil { 409 | t.Fatalf("VerifyShare: %v", err) 410 | } 411 | ds = append(ds, d) 412 | } 413 | if got, err := Aggregate(c, ds[:tc.k], tc.n); err != nil { 414 | t.Errorf("Aggregate: %v", err) 415 | } else if !cmp.Equal(got, want) { 416 | t.Errorf("got=%v, want=%v", got, want) 417 | } 418 | }) 419 | } 420 | } 421 | 422 | func TestRedealDecryptOld(t *testing.T) { 423 | ms, pk, _, err := GenerateKeys(3, 5) 424 | if err != nil { 425 | t.Fatalf("GenerateKeys: %v", err) 426 | } 427 | want := []byte("msg") 428 | c, err := Encrypt(pk, want) 429 | if err != nil { 430 | t.Fatalf("Encrypt: %v", err) 431 | } 432 | for _, tc := range []struct { 433 | name string 434 | k, n int 435 | }{ 436 | { 437 | name: "same n,k", 438 | k: 3, 439 | n: 5, 440 | }, 441 | { 442 | name: "smaller quorum", 443 | k: 2, 444 | n: 5, 445 | }, 446 | { 447 | name: "larger quorum", 448 | k: 4, 449 | n: 5, 450 | }, 451 | } { 452 | t.Run(tc.name, func(t *testing.T) { 453 | // generate new instance 454 | new, shares, err := Redeal(pk, ms, tc.k, tc.n) 455 | if err != nil { 456 | t.Fatalf("Redeal: %v", err) 457 | } 458 | // try to decrypt old ciphertext 459 | ds := []*DecryptionShare{} 460 | for _, sh := range shares { 461 | d, err := Decrypt(c, sh) 462 | if err != nil { 463 | t.Fatalf("Decrypt: %v", err) 464 | } 465 | if err := VerifyShare(c, new, d); err != nil { 466 | t.Fatalf("VerifyShare: %v", err) 467 | } 468 | ds = append(ds, d) 469 | } 470 | // should fail w/o enough shares 471 | if _, err := Aggregate(c, ds[:tc.k-1], tc.n); err == nil { 472 | t.Error("Aggregate did not fail") 473 | } 474 | // try with enough shares 475 | if got, err := Aggregate(c, ds[:tc.k], tc.n); err != nil { 476 | t.Errorf("Aggregate: %v", err) 477 | } else if !cmp.Equal(got, want) { 478 | t.Errorf("got=%v, want=%v", got, want) 479 | } 480 | }) 481 | } 482 | } 483 | 484 | func TestRedealReuseOldShares(t *testing.T) { 485 | ms, pk, shares, err := GenerateKeys(3, 5) 486 | if err != nil { 487 | t.Fatalf("GenerateKeys: %v", err) 488 | } 489 | newPk, _, err := Redeal(pk, ms, 3, 5) 490 | if err != nil { 491 | t.Fatalf("Redeal: %v", err) 492 | } 493 | c, err := Encrypt(newPk, []byte("msg")) 494 | if err != nil { 495 | t.Fatalf("Encrypt: %v", err) 496 | } 497 | // use old share for decryption 498 | ds, err := Decrypt(c, shares[0]) 499 | if err != nil { 500 | t.Fatalf("Decrypt: %v", err) 501 | } 502 | // make sure old shares cannot be used for new encryptions 503 | if err := VerifyShare(c, newPk, ds); err == nil { 504 | t.Error("VerifyShare did not fail") 505 | } 506 | } 507 | 508 | func FuzzCiphertextMarshal(f *testing.F) { 509 | _, pk, _, err := GenerateKeys(1, 1) 510 | if err != nil { 511 | f.Fatalf("Keys: %v", err) 512 | } 513 | r, err := randStream() 514 | if err != nil { 515 | f.Fatalf("randStream: %v", err) 516 | } 517 | tdh2Input := make([]byte, tdh2.InputSize) 518 | f.Add(tdh2Input, []byte("symcCtxt"), []byte("nonce")) 519 | f.Fuzz(func(t *testing.T, key, symCtxt, nonce []byte) { 520 | if len(key) != tdh2.InputSize { 521 | t.Skip() 522 | } 523 | tdh2Ctxt, err := tdh2.Encrypt(pk.p, key, tdh2Input, r) 524 | if err != nil { 525 | t.Fatalf("Encrypt(%v): %v", key, err) 526 | } 527 | want := Ciphertext{ 528 | tdh2Ctxt: tdh2Ctxt, 529 | symCtxt: symCtxt, 530 | nonce: nonce, 531 | } 532 | b, err := want.Marshal() 533 | if err != nil { 534 | t.Fatalf("Marshal(%v): %v", want, err) 535 | } 536 | var got Ciphertext 537 | if err := got.UnmarshalVerify(b, pk); err != nil { 538 | t.Fatalf("UnmarshalVerify(%v): %v", b, err) 539 | } 540 | }) 541 | } 542 | 543 | func FuzzCiphertextUnmarshal(f *testing.F) { 544 | _, pk, _, err := GenerateKeys(1, 1) 545 | if err != nil { 546 | f.Fatalf("Keys: %v", err) 547 | } 548 | r, err := randStream() 549 | if err != nil { 550 | f.Fatalf("ranStream: %v", err) 551 | } 552 | tdh2Ctxt, err := tdh2.Encrypt(pk.p, make([]byte, tdh2.InputSize), make([]byte, tdh2.InputSize), r) 553 | if err != nil { 554 | f.Fatalf("Encrypt: %v", err) 555 | } 556 | c := Ciphertext{ 557 | tdh2Ctxt: tdh2Ctxt, 558 | symCtxt: []byte("symCtxt"), 559 | nonce: []byte("nonce"), 560 | } 561 | b, err := c.Marshal() 562 | if err != nil { 563 | f.Fatalf("Marshal: %v", err) 564 | } 565 | f.Add(b) 566 | f.Fuzz(func(t *testing.T, data []byte) { 567 | var c1, c2 Ciphertext 568 | if err := c1.UnmarshalVerify(data, pk); err != nil { 569 | t.Skip() 570 | } 571 | data1, err := c1.Marshal() 572 | if err != nil { 573 | t.Fatalf("Cannot marshal: data=%v err=%v", data, err) 574 | } 575 | if err := c2.UnmarshalVerify(data1, pk); err != nil { 576 | t.Fatalf("Cannot unmarshal: data=%v err=%v", data1, err) 577 | } 578 | data2, err := c2.Marshal() 579 | if err != nil { 580 | t.Fatalf("Cannot marshal: data=%v err=%v", data2, err) 581 | } 582 | if !bytes.Equal(data1, data2) { 583 | t.Errorf("data1=%v data2=%v", data1, data2) 584 | } 585 | if !bytes.Equal(c1.symCtxt, c2.symCtxt) { 586 | t.Errorf("c1.symCtxt=%v data1=%v c2.symCtxt=%v data2=%v", c1.symCtxt, data1, c2.symCtxt, data2) 587 | 588 | } 589 | if !bytes.Equal(c1.nonce, c2.nonce) { 590 | t.Errorf("c1.nonce=%v data1=%v c2.nonce=%v data2=%v", c1.nonce, data1, c2.nonce, data2) 591 | 592 | } 593 | if !c1.tdh2Ctxt.Equal(c2.tdh2Ctxt) { 594 | t.Errorf("c1.tdh2Ctxt=%v data1=%v c2.tdh2Ctxt=%v data2=%v", c1.tdh2Ctxt, data1, c2.tdh2Ctxt, data2) 595 | } 596 | }) 597 | } 598 | -------------------------------------------------------------------------------- /js/tdh2/README.md: -------------------------------------------------------------------------------- 1 | See test/ for an example. 2 | 3 | A TS project (Uniswap v2 interface) required `echo "/* global BigInt */" >> node_modules/bcrypto/lib/js/bn.js` after `npm install` 4 | -------------------------------------------------------------------------------- /js/tdh2/decs.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tdh2" 2 | -------------------------------------------------------------------------------- /js/tdh2/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tdh2", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "bcrypto": "^5.4.0", 9 | "uniq": "^1.0.1" 10 | } 11 | }, 12 | "node_modules/bcrypto": { 13 | "version": "5.4.0", 14 | "resolved": "https://registry.npmjs.org/bcrypto/-/bcrypto-5.4.0.tgz", 15 | "integrity": "sha512-KDX2CR29o6ZoqpQndcCxFZAtYA1jDMnXU3jmCfzP44g++Cu7AHHtZN/JbrN/MXAg9SLvtQ8XISG+eVD9zH1+Jg==", 16 | "hasInstallScript": true, 17 | "dependencies": { 18 | "bufio": "~1.0.7", 19 | "loady": "~0.0.5" 20 | }, 21 | "engines": { 22 | "node": ">=8.0.0" 23 | } 24 | }, 25 | "node_modules/bufio": { 26 | "version": "1.0.7", 27 | "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz", 28 | "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==", 29 | "engines": { 30 | "node": ">=8.0.0" 31 | } 32 | }, 33 | "node_modules/loady": { 34 | "version": "0.0.5", 35 | "resolved": "https://registry.npmjs.org/loady/-/loady-0.0.5.tgz", 36 | "integrity": "sha512-uxKD2HIj042/HBx77NBcmEPsD+hxCgAtjEWlYNScuUjIsh/62Uyu39GOR68TBR68v+jqDL9zfftCWoUo4y03sQ==", 37 | "engines": { 38 | "node": ">=8.0.0" 39 | } 40 | }, 41 | "node_modules/uniq": { 42 | "version": "1.0.1", 43 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 44 | "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" 45 | } 46 | }, 47 | "dependencies": { 48 | "bcrypto": { 49 | "version": "5.4.0", 50 | "resolved": "https://registry.npmjs.org/bcrypto/-/bcrypto-5.4.0.tgz", 51 | "integrity": "sha512-KDX2CR29o6ZoqpQndcCxFZAtYA1jDMnXU3jmCfzP44g++Cu7AHHtZN/JbrN/MXAg9SLvtQ8XISG+eVD9zH1+Jg==", 52 | "requires": { 53 | "bufio": "~1.0.7", 54 | "loady": "~0.0.5" 55 | } 56 | }, 57 | "bufio": { 58 | "version": "1.0.7", 59 | "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz", 60 | "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==" 61 | }, 62 | "loady": { 63 | "version": "0.0.5", 64 | "resolved": "https://registry.npmjs.org/loady/-/loady-0.0.5.tgz", 65 | "integrity": "sha512-uxKD2HIj042/HBx77NBcmEPsD+hxCgAtjEWlYNScuUjIsh/62Uyu39GOR68TBR68v+jqDL9zfftCWoUo4y03sQ==" 66 | }, 67 | "uniq": { 68 | "version": "1.0.1", 69 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 70 | "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /js/tdh2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bcrypto": "^5.4.0", 4 | "uniq": "^1.0.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /js/tdh2/tdh2.js: -------------------------------------------------------------------------------- 1 | const rnd = require('bcrypto/lib/random'); 2 | const sha256 = require('bcrypto/lib/sha256'); 3 | const elliptic = require('bcrypto/lib/js/elliptic'); 4 | const cipher = require('bcrypto/lib/cipher'); 5 | 6 | const { 7 | ShortCurve, 8 | EdwardsCurve, 9 | curves 10 | } = elliptic; 11 | 12 | const { 13 | Cipher, 14 | Decipher, 15 | enc, 16 | dec 17 | } = cipher; 18 | 19 | 20 | const p256 = new curves.P256(); 21 | const groupName = "P256"; 22 | const tdh2InputSize = 32; 23 | 24 | function toHexString(byteArray) { 25 | return Array.from(byteArray, function(byte) { 26 | return ('0' + (byte & 0xFF).toString(16)).slice(-2); 27 | }).join('') 28 | } 29 | 30 | function tdh2Encrypt(pub, msg, label) { 31 | if (pub.Group != groupName) 32 | throw Error('invalid group') 33 | g_bar = p256.decodePoint(Buffer.from(pub.G_bar, 'base64')) 34 | h = p256.decodePoint(Buffer.from(pub.H, 'base64')) 35 | 36 | const r = p256.randomScalar(rnd); 37 | const s = p256.randomScalar(rnd); 38 | 39 | const c = xor(hash1(h.mul(r)), msg) 40 | 41 | const u = p256.g.mul(r) 42 | const w = p256.g.mul(s) 43 | const uBar = g_bar.mul(r) 44 | const wBar = g_bar.mul(s) 45 | 46 | const e = hash2(c, label, u, w, uBar, wBar) 47 | const f = s.add(r.mul(e).mod(p256.n)).mod(p256.n) 48 | 49 | return JSON.stringify({ 50 | Group: groupName, 51 | C: c.toString('base64'), 52 | Label: label.toString('base64'), 53 | U: p256.encodePoint(u, false).toString('base64'), 54 | U_bar: p256.encodePoint(uBar, false).toString('base64'), 55 | E: p256.encodeScalar(e).toString('base64'), 56 | F: p256.encodeScalar(f).toString('base64'), 57 | }) 58 | } 59 | 60 | function concatenate(points) { 61 | var out = groupName; 62 | for (let i = 0; i < points.length; i++) { 63 | out += "," + toHexString(p256.encodePoint(points[i], false)); 64 | } 65 | 66 | return Buffer.from(out); 67 | } 68 | 69 | function hash1(point) { 70 | return sha256.digest(Buffer.concat([ 71 | Buffer.from("tdh2hash1"), 72 | concatenate([point]) 73 | ])); 74 | } 75 | 76 | function hash2(msg, label, p1, p2, p3, p4) { 77 | if (msg.length != tdh2InputSize) 78 | throw new Error('message has incorrect length'); 79 | 80 | if (label.length != tdh2InputSize) 81 | throw new Error('label has incorrect length'); 82 | 83 | const h = sha256.digest(Buffer.concat([ 84 | Buffer.from("tdh2hash2"), 85 | msg, 86 | label, 87 | concatenate([p1,p2,p3,p4]) 88 | ])); 89 | 90 | return p256.decodeScalar(h) 91 | } 92 | 93 | function xor(a, b) { 94 | if (a.length != b.length) 95 | throw new Error('buffers with different lengths'); 96 | 97 | var out = Buffer.alloc(a.length) 98 | for (var i = 0; i < a.length; i++) { 99 | out[i] = a[i] ^ b[i]; 100 | } 101 | 102 | return out 103 | } 104 | 105 | function encrypt(pub, msg) { 106 | const ciph = new Cipher('AES-256-GCM'); 107 | const blockSize = 16; 108 | const key = rnd.randomBytes(tdh2InputSize); 109 | const nonce = rnd.randomBytes(12); 110 | 111 | ciph.init(key, nonce); 112 | if (msg.length > ((2**32)-2)*blockSize) 113 | throw new Error('message too long'); 114 | const ctxt = Buffer.concat([ 115 | ciph.update(msg), 116 | ciph.final(), 117 | ciph.getAuthTag() 118 | ]); 119 | 120 | const tdh2Ctxt = tdh2Encrypt(pub, key, Buffer.alloc(tdh2InputSize)); 121 | 122 | return JSON.stringify({ 123 | TDH2Ctxt: Buffer.from(tdh2Ctxt).toString('base64'), 124 | SymCtxt: ctxt.toString('base64'), 125 | Nonce: nonce.toString('base64'), 126 | }) 127 | } 128 | 129 | module.exports = { encrypt } 130 | -------------------------------------------------------------------------------- /js/tdh2/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /js/tdh2/test/test.js: -------------------------------------------------------------------------------- 1 | import { randomBytes, randomInt } from 'crypto' 2 | import pkg from '../tdh2.js'; 3 | const { encrypt } = pkg; 4 | 5 | const pub = JSON.parse(process.argv.slice(2)[0]); 6 | 7 | for (let i = 0; i < 100; i++) { 8 | const msg = randomBytes(randomInt(1, 5000)) 9 | console.log(msg.toString('base64')) 10 | console.log(encrypt(pub, msg)) 11 | } 12 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # projectKey is required (may be found under "Project Information" in Sonar or in project url) 2 | sonar.projectKey=smartcontractkit_tdh2 3 | sonar.sources=. 4 | 5 | # Full exclusions from the static analysis 6 | sonar.exclusions=**/mocks/**/*, **/testdata/**/*, **/script/**/*, **/generated/**/*, **/fixtures/**/*, **/docs/**/*, **/tools/**/*, **/*.pb.go, **/*report.xml, **/*.txt, **/*.abi, **/*.bin 7 | # Coverage exclusions 8 | sonar.coverage.exclusions=**/*_test.go, **/config/**/*, **/test/**/* 9 | 10 | # Tests' root folder, inclusions (tests to check and count) and exclusions 11 | sonar.tests=. 12 | sonar.test.inclusions=**/*_test.go --------------------------------------------------------------------------------