├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── brng ├── brng.go ├── brng_suite_test.go ├── brng_test.go ├── brngutil │ ├── machine.go │ ├── message.go │ └── type.go ├── err.go ├── mock │ └── consensus.go └── sharing.go ├── go.mod ├── go.sum ├── inv ├── inv.go ├── inv_suite_test.go ├── inv_test.go ├── invutil │ ├── machine.go │ ├── malicious_machine.go │ ├── message.go │ └── type.go ├── marshal.go └── marshal_test.go ├── mpcutil ├── debugger.go ├── machine.go └── network.go ├── mulopen ├── err.go ├── marshal.go ├── marshal_test.go ├── message.go ├── mulopen.go ├── mulopen_suite_test.go ├── mulopen_test.go ├── mulopenutil │ ├── machine.go │ └── message.go └── mulzkp │ ├── marshal_test.go │ ├── mulzkp.go │ ├── mulzkp_suite_test.go │ ├── mulzkp_test.go │ ├── proof.go │ └── zkp │ ├── marshal_test.go │ ├── message.go │ ├── response.go │ ├── witness.go │ ├── zkp.go │ ├── zkp_suite_test.go │ └── zkp_test.go ├── open ├── err.go ├── marshal.go ├── marshal_test.go ├── open.go ├── open_suite_test.go ├── open_test.go └── openutil │ ├── machine.go │ └── message.go ├── params └── params.go ├── rkpg ├── err.go ├── marshal.go ├── marshal_test.go ├── rkpg.go ├── rkpg_suite_test.go ├── rkpg_test.go ├── rkpgutil │ ├── machine.go │ ├── message.go │ └── testutil.go └── state.go └── rng ├── compute ├── compute.go ├── compute_suite_test.go └── compute_test.go ├── event.go ├── marshal.go ├── marshal_test.go ├── rng.go ├── rng_suite_test.go ├── rng_test.go ├── rngutil ├── machine.go ├── message.go └── testutil.go ├── rzg_test.go └── transition_test.go /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Description 4 | 5 | 6 | 7 | 8 | 9 | ### Reference 10 | 11 | 12 | - https://github.com/renproject/mpc/issues/? 13 | - https://github.com/renproject/mpc/issues/?? 14 | - https://github.com/renproject/mpc/issues/??? 15 | 16 | # Checklist 17 | 18 | ### Pull Request 19 | - [ ] I assigned to myself the corresponding issue for this PR 20 | - [ ] The PR title is prefixed with one of the follwing: 21 | - `[FEATURE]` 22 | - `[IMPROVEMENT]` 23 | - `[FIX]` 24 | - [ ] The PR title is suffixed with one of the following: 25 | - `(Resolves #issue-no)` 26 | - `(Needed for #issue-no)` 27 | - [ ] I have requested a review for this PR 28 | 29 | ### Code 30 | - [ ] My commits are squashed 31 | - [ ] My commits well describe the changes done in them 32 | - [ ] My code is vetted via `go vet` 33 | - [ ] My code is is happily linted with `golint` 34 | - [ ] My code passes all tests via `go test` 35 | - [ ] My changes are tested, well covered and inspires confidence 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-go@v2 9 | with: 10 | go-version: "^1.14.0" 11 | - uses: actions/cache@v1 12 | with: 13 | path: ~/go/pkg/mod 14 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 15 | restore-keys: | 16 | ${{ runner.os }}-go- 17 | - name: Configure git for private modules 18 | env: 19 | PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 20 | run: git config --global url."https://loongy:${PERSONAL_ACCESS_TOKEN}@github.com".insteadOf "https://github.com" 21 | - name: Run vetting 22 | run: | 23 | cd $GITHUB_WORKSPACE 24 | export PATH=$PATH:$(go env GOPATH)/bin 25 | cd $GITHUB_WORKSPACE 26 | go vet ./... 27 | - name: Run linting 28 | run: | 29 | cd $GITHUB_WORKSPACE 30 | export PATH=$PATH:$(go env GOPATH)/bin 31 | go get -u golang.org/x/lint/golint 32 | go vet ./... 33 | golint ./... 34 | - name: Run tests 35 | env: 36 | COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 37 | CI: true 38 | run: | 39 | cd $GITHUB_WORKSPACE 40 | export PATH=$PATH:$(go env GOPATH)/bin 41 | go get -u github.com/mattn/goveralls 42 | go test --race --cover --coverprofile mpc.coverprofile ./... 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # IDE files 12 | .idea 13 | .vscode 14 | 15 | # Output of the go coverage tool 16 | *.out 17 | *.coverprofile 18 | 19 | .direnv.* 20 | .envrc 21 | shell.nix 22 | 23 | # Binaries 24 | releases 25 | 26 | # Debuggers 27 | *.dump -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #
RenVM's Secure Multi-Party Computation Protocol
4 | 5 |  6 | [](https://www.gnu.org/licenses/gpl-3.0) 7 | 8 | -------------------------------------------------------------------------------- 9 | 10 | This is an implementation of a threshold ECDSA scheme, that is for use in RenVM. For a network of `n` parties, this scheme is robustly secure against `t` malicious adversaries, such that `n >= 3t + 1`. During both ECDSA key generation and signing, up to `t` parties can go offline at the beginning, middle, or end of a round, and the protocols will complete successfully without the need to go back repeat from a prior round. 11 | 12 | ## Overview 13 | **MPC Primitives** are the building blocks for threshold ECDSA, namely [Open](/open), [BRNG](brng/), [RNG/RZG](rng/) and [RKPG](rkpg/), are implemented in their own packages. We make use of Pedersen's [Commitment Scheme](https://link.springer.com/chapter/10.1007/3-540-46766-1_9) to augment Shamir's [Secret Sharing Scheme](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) to a Verifiable Secret Sharing Scheme, which is implemented as a [separate package](https://github.com/renproject/shamir). 14 | 15 | #### Finite State Machine 16 | MPC primitives are implemented as [finite-state machines](https://en.wikipedia.org/wiki/Finite-state_machine). A general state transitional behaviour is described below. 17 | 18 | A `Primitive` in some `State` receives messages of the form `Transition*` with one or more message arguments. On receiving such a message, the `Primitive` must: 19 | * Preliminary checks 20 | * Ensure that it is in an appropriate state to process the message 21 | * Ensure that the message arguments are valid 22 | * Process message 23 | * Do the necessary computations with the message arguments 24 | * Do the necessary state transition 25 | * Return an appropriate event that describes 26 | * If the machine has transitioned 27 | * How the machine has processed the message 28 | * Whether the message arguments were invalid 29 | 30 | For more information regarding various primitive protocols and their state transitions, refer [RenVM MPC's Wiki](https://github.com/renproject/mpc/wiki). 31 | 32 | #### Development Status 33 | - [x] Open 34 | - [ ] Biased Random Number Generation 35 | - [ ] Unbiased Random Number Generation 36 | - [ ] Random Zero Generation 37 | - [ ] Random KeyPair Generation 38 | - [ ] Multiply and Open 39 | - [ ] Inversion 40 | - [ ] Threshold ECDSA 41 | 42 | ## License 43 | RenVM MPC is [GNU GPL v3](./LICENSE) licensed 44 | 45 | -------------------------------------------------------------------------------- 46 | 47 | Built with ❤ by Ren. 48 | -------------------------------------------------------------------------------- /brng/brng.go: -------------------------------------------------------------------------------- 1 | package brng 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/mpc/params" 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir" 9 | ) 10 | 11 | // New creates a random batch of sharings that will be sent to the other 12 | // players during the BRNG algorithm. The verifiable sharings will correspond 13 | // to the given indices and pedersen parameter h. The given index represents 14 | // the index of the created player. The batch size represents the number of 15 | // instances of the algorithm to be run in parallel. 16 | // 17 | // Panics: This function will panic if either the batch size or the 18 | // reconstruction threshold (k) are less than 1, or if the Pedersen parameter 19 | // is known to be insecure. 20 | func New(batchSize, k uint32, indices []secp256k1.Fn, index secp256k1.Fn, h secp256k1.Point) []Sharing { 21 | if batchSize < 1 { 22 | panic(fmt.Sprintf("batch size must be at least 1: got %v", batchSize)) 23 | } 24 | if k < 1 { 25 | panic(fmt.Sprintf("k must be at least 1: got %v", k)) 26 | } 27 | if !params.ValidPedersenParameter(h) { 28 | panic("insecure choice of pedersen parameter") 29 | } 30 | n := len(indices) 31 | sharings := make([]Sharing, int(batchSize)) 32 | for i := range sharings { 33 | sharings[i].Shares = make(shamir.VerifiableShares, n) 34 | sharings[i].Commitment = shamir.NewCommitmentWithCapacity(int(k)) 35 | shamir.VShareSecret(&sharings[i].Shares, &sharings[i].Commitment, 36 | indices, h, secp256k1.RandomFn(), int(k)) 37 | } 38 | return sharings 39 | } 40 | 41 | // IsValid checks the validity of the given potential consensus outputs. The 42 | // required contributions argument is the minimum number of contributions 43 | // required from other players for the consensus output to be considered valid. 44 | // Usually, this will be set to the reconstruction threshold (k) of the shares. 45 | // A return value of true means that this consensus output can be used to 46 | // construct the output shares and commitments for BRNG. If the return value is 47 | // false, then either the shares or the commitments or both are not valid, and 48 | // a corresponding error is returned based on how they are invalid. 49 | // 50 | // Panics: This function will panic if the given required contributions is less 51 | // than 1. 52 | func IsValid( 53 | batchSize uint32, 54 | ownIndex secp256k1.Fn, 55 | h secp256k1.Point, 56 | sharesBatch []shamir.VerifiableShares, 57 | commitmentsBatch [][]shamir.Commitment, 58 | requiredContributions int, 59 | ) error { 60 | if requiredContributions < 1 { 61 | panic(fmt.Sprintf("required contributions must be at least 1: got %v", requiredContributions)) 62 | } 63 | // Commitments validity. 64 | if uint32(len(commitmentsBatch)) != batchSize { 65 | return ErrIncorrectCommitmentsBatchSize 66 | } 67 | numContributions := len(commitmentsBatch[0]) 68 | if numContributions < requiredContributions { 69 | return ErrNotEnoughContributions 70 | } 71 | for _, commitments := range commitmentsBatch { 72 | if len(commitments) != numContributions { 73 | return ErrInvalidCommitmentDimensions 74 | } 75 | } 76 | k := commitmentsBatch[0][0].Len() 77 | for _, commitments := range commitmentsBatch { 78 | for _, commitment := range commitments { 79 | if commitment.Len() != k { 80 | return ErrInvalidCommitmentDimensions 81 | } 82 | } 83 | } 84 | 85 | // Shares validity. 86 | if uint32(len(sharesBatch)) != batchSize { 87 | return ErrIncorrectSharesBatchSize 88 | } 89 | for i, shares := range sharesBatch { 90 | if len(shares) != numContributions { 91 | return ErrInvalidShareDimensions 92 | } 93 | for j, share := range shares { 94 | if !share.Share.IndexEq(&ownIndex) { 95 | return ErrIncorrectIndex 96 | } 97 | if !shamir.IsValid(h, &commitmentsBatch[i][j], &share) { 98 | return ErrInvalidShares 99 | } 100 | } 101 | } 102 | 103 | return nil 104 | } 105 | 106 | // HandleConsensusOutput computes the output shares and commitments for the 107 | // BRNG algorithm upon receiving the slice of verifiable shares that is output 108 | // by the consensus protocol. It is assumed that the consensus protocol will 109 | // decide on an output such that >=k players will find that their inputs to 110 | // this function are valid. It is assumed that the player will use IsValid 111 | // during the consensus protocol, and if it is found that the shares in the 112 | // output of the consensus protocol are not valid for this player, the shares 113 | // argument should be nil. In this case, the corresponding output shares will 114 | // also be nil. Every time this function is called, it is assumed that the 115 | // given commitments are valid, which should be the case if they came from a 116 | // commited block from the consensus algorithm. 117 | func HandleConsensusOutput( 118 | sharesBatch []shamir.VerifiableShares, commitmentsBatch [][]shamir.Commitment, 119 | ) ( 120 | shamir.VerifiableShares, []shamir.Commitment, 121 | ) { 122 | commitmentSumBatch := make([]shamir.Commitment, len(commitmentsBatch)) 123 | for i, commitments := range commitmentsBatch { 124 | commitmentSumBatch[i].Set(commitments[0]) 125 | for _, com := range commitments[1:] { 126 | commitmentSumBatch[i].Add(commitmentSumBatch[i], com) 127 | } 128 | } 129 | 130 | // If the given shares were nil then they should be ignored, otherwise 131 | // progress to summing them. 132 | if sharesBatch == nil { 133 | return nil, commitmentSumBatch 134 | } 135 | 136 | shareSumBatch := make(shamir.VerifiableShares, len(sharesBatch)) 137 | for i, shares := range sharesBatch { 138 | shareSumBatch[i] = shares[0] 139 | for _, share := range shares[1:] { 140 | shareSumBatch[i].Add(&shareSumBatch[i], &share) 141 | } 142 | } 143 | 144 | return shareSumBatch, commitmentSumBatch 145 | } 146 | -------------------------------------------------------------------------------- /brng/brng_suite_test.go: -------------------------------------------------------------------------------- 1 | package brng_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestBrng(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Brng Suite") 13 | } 14 | -------------------------------------------------------------------------------- /brng/brngutil/message.go: -------------------------------------------------------------------------------- 1 | package brngutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/mpc/brng" 7 | "github.com/renproject/mpc/mpcutil" 8 | "github.com/renproject/shamir" 9 | "github.com/renproject/surge" 10 | ) 11 | 12 | // PlayerMessage represents a message that a player sends to the consensus 13 | // trusted party in an invocation of the BRNG algorithm. 14 | type PlayerMessage struct { 15 | from, to mpcutil.ID 16 | row []brng.Sharing 17 | } 18 | 19 | // From implements the Message interface. 20 | func (pm PlayerMessage) From() mpcutil.ID { 21 | return pm.from 22 | } 23 | 24 | // To implements the Message interface. 25 | func (pm PlayerMessage) To() mpcutil.ID { 26 | return pm.to 27 | } 28 | 29 | // SizeHint implements the surge.SizeHinter interface. 30 | func (pm PlayerMessage) SizeHint() int { 31 | return pm.from.SizeHint() + pm.to.SizeHint() + surge.SizeHint(pm.row) 32 | } 33 | 34 | // Marshal implements the surge.Marshaler interface. 35 | func (pm PlayerMessage) Marshal(buf []byte, rem int) ([]byte, int, error) { 36 | buf, rem, err := pm.from.Marshal(buf, rem) 37 | if err != nil { 38 | return buf, rem, err 39 | } 40 | buf, rem, err = pm.to.Marshal(buf, rem) 41 | if err != nil { 42 | return buf, rem, err 43 | } 44 | buf, rem, err = surge.Marshal(pm.row, buf, rem) 45 | return buf, rem, err 46 | } 47 | 48 | // Unmarshal implements the surge.Unmarshaler interface. 49 | func (pm *PlayerMessage) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 50 | buf, rem, err := pm.from.Unmarshal(buf, rem) 51 | if err != nil { 52 | return buf, rem, err 53 | } 54 | buf, rem, err = pm.to.Unmarshal(buf, rem) 55 | if err != nil { 56 | return buf, rem, err 57 | } 58 | buf, rem, err = surge.Unmarshal(&pm.row, buf, rem) 59 | return buf, rem, err 60 | } 61 | 62 | // ConsensusMessage represents the message that the consensus trusted party 63 | // sends to all of the parties once consensus has been reached in the BRNG 64 | // algorithm. 65 | type ConsensusMessage struct { 66 | from, to mpcutil.ID 67 | sharesBatch []shamir.VerifiableShares 68 | commitmentsBatch [][]shamir.Commitment 69 | } 70 | 71 | // From implements the Message interface. 72 | func (cm ConsensusMessage) From() mpcutil.ID { 73 | return cm.from 74 | } 75 | 76 | // To implements the Message interface. 77 | func (cm ConsensusMessage) To() mpcutil.ID { 78 | return cm.to 79 | } 80 | 81 | // SizeHint implements the surge.SizeHinter interface. 82 | func (cm ConsensusMessage) SizeHint() int { 83 | return cm.from.SizeHint() + 84 | cm.to.SizeHint() + 85 | surge.SizeHint(cm.sharesBatch) + 86 | surge.SizeHint(cm.commitmentsBatch) 87 | } 88 | 89 | // Marshal implements the surge.Marshaler interface. 90 | func (cm ConsensusMessage) Marshal(buf []byte, rem int) ([]byte, int, error) { 91 | buf, rem, err := cm.from.Marshal(buf, rem) 92 | if err != nil { 93 | return buf, rem, err 94 | } 95 | buf, rem, err = cm.to.Marshal(buf, rem) 96 | if err != nil { 97 | return buf, rem, err 98 | } 99 | buf, rem, err = surge.Marshal(cm.sharesBatch, buf, rem) 100 | if err != nil { 101 | return buf, rem, err 102 | } 103 | return surge.Marshal(cm.commitmentsBatch, buf, rem) 104 | } 105 | 106 | // Unmarshal implements the surge.Unmarshaler interface. 107 | func (cm *ConsensusMessage) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 108 | buf, rem, err := cm.from.Unmarshal(buf, rem) 109 | if err != nil { 110 | return buf, rem, err 111 | } 112 | buf, rem, err = cm.to.Unmarshal(buf, rem) 113 | if err != nil { 114 | return buf, rem, err 115 | } 116 | buf, rem, err = surge.Unmarshal(&cm.sharesBatch, buf, rem) 117 | if err != nil { 118 | return buf, rem, err 119 | } 120 | return surge.Unmarshal(&cm.commitmentsBatch, buf, rem) 121 | } 122 | 123 | // BrngMessage is a wrapper for any of the messages that can be sent during an 124 | // invocation of the BRNG algorithm. 125 | type BrngMessage struct { 126 | msg mpcutil.Message 127 | } 128 | 129 | // From implements the Message interface. 130 | func (bm BrngMessage) From() mpcutil.ID { 131 | return bm.msg.From() 132 | } 133 | 134 | // To implements the Message interface. 135 | func (bm BrngMessage) To() mpcutil.ID { 136 | return bm.msg.To() 137 | } 138 | 139 | // SizeHint implements the surge.SizeHinter interface. 140 | func (bm BrngMessage) SizeHint() int { 141 | return 1 + bm.msg.SizeHint() 142 | } 143 | 144 | // Marshal implements the surge.Marshaler interface. 145 | func (bm BrngMessage) Marshal(buf []byte, rem int) ([]byte, int, error) { 146 | var ty TypeID 147 | switch bm.msg.(type) { 148 | case *PlayerMessage: 149 | ty = BrngTypePlayer 150 | case *ConsensusMessage: 151 | ty = BrngTypeConsensus 152 | default: 153 | panic(fmt.Sprintf("unexpected message type %T", bm.msg)) 154 | } 155 | 156 | buf, rem, err := ty.Marshal(buf, rem) 157 | if err != nil { 158 | return buf, rem, fmt.Errorf("error marshaling ty: %v", err) 159 | } 160 | 161 | return bm.msg.Marshal(buf, rem) 162 | } 163 | 164 | // Unmarshal implements the surge.Unmarshaler interface. 165 | func (bm *BrngMessage) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 166 | var ty TypeID 167 | buf, rem, err := ty.Unmarshal(buf, rem) 168 | if err != nil { 169 | return buf, rem, err 170 | } 171 | 172 | switch ty { 173 | case BrngTypePlayer: 174 | bm.msg = new(PlayerMessage) 175 | case BrngTypeConsensus: 176 | bm.msg = new(ConsensusMessage) 177 | default: 178 | return buf, rem, fmt.Errorf("invalid message type %v", ty) 179 | } 180 | 181 | return bm.msg.Unmarshal(buf, rem) 182 | } 183 | -------------------------------------------------------------------------------- /brng/brngutil/type.go: -------------------------------------------------------------------------------- 1 | package brngutil 2 | 3 | import ( 4 | "github.com/renproject/surge" 5 | ) 6 | 7 | // TypeID is a type alias that represents the different types of messages and 8 | // players that exist. 9 | type TypeID uint8 10 | 11 | const ( 12 | // BrngTypePlayer represents the player type that runs the BRNG algorithm. 13 | BrngTypePlayer = TypeID(1) 14 | 15 | // BrngTypeConsensus represents the consensus trusted party. 16 | BrngTypeConsensus = TypeID(2) 17 | ) 18 | 19 | // SizeHint implements the surge.SizeHinter interface. 20 | func (id TypeID) SizeHint() int { return 1 } 21 | 22 | // Marshal implements the surge.Marshaler interface. 23 | func (id TypeID) Marshal(buf []byte, rem int) ([]byte, int, error) { 24 | return surge.MarshalU8(uint8(id), buf, rem) 25 | } 26 | 27 | // Unmarshal implements the surge.Unmarshaler interface. 28 | func (id *TypeID) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 29 | return surge.UnmarshalU8((*uint8)(id), buf, rem) 30 | } 31 | -------------------------------------------------------------------------------- /brng/err.go: -------------------------------------------------------------------------------- 1 | package brng 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrIncorrectCommitmentsBatchSize is returned when the batch size of the 7 | // given commitments is not equal to the batch size of the BRNGer. 8 | ErrIncorrectCommitmentsBatchSize = errors.New("incorrect commitments batch size") 9 | 10 | // ErrIncorrectSharesBatchSize is returned when the batch size of the given 11 | // shares is not equal to the batch size of the BRNGer. 12 | ErrIncorrectSharesBatchSize = errors.New("incorrect shares batch size") 13 | 14 | // ErrInvalidCommitmentDimensions is returned when the batch of commitments 15 | // has inconsistent dimensions. This can occur when not all slices in the 16 | // batch have the same length (this length is equal to the number of 17 | // contributions for the batch), or when not all commitments have the same 18 | // threshold. 19 | ErrInvalidCommitmentDimensions = errors.New("invalid commitment dimensions") 20 | 21 | // ErrInvalidShareDimensions is returned when the batch of shares has 22 | // inconsistent dimensions. This occurs when not all slices in the batch 23 | // have the same length (this length is equal to the number of 24 | // contributions for the batch). 25 | ErrInvalidShareDimensions = errors.New("invalid share dimensions") 26 | 27 | // ErrInvalidShares is returned when not all of the given shares are valid 28 | // with respect to their corresponding commitments. 29 | ErrInvalidShares = errors.New("invalid shares") 30 | 31 | // ErrIncorrectIndex is returned when not all of the shares have index 32 | // equal to the index for the BRNGer. 33 | ErrIncorrectIndex = errors.New("incorrect index") 34 | 35 | // ErrNotEnoughContributions is returned when the number of contributions 36 | // from other players is smaller than the number of required contributions. 37 | ErrNotEnoughContributions = errors.New("not enough contributions") 38 | ) 39 | -------------------------------------------------------------------------------- /brng/mock/consensus.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir" 9 | "github.com/renproject/surge" 10 | 11 | "github.com/renproject/mpc/brng" 12 | ) 13 | 14 | // PullConsensus represents an ideal trusted party for achieving consensus on a 15 | // table of shares to be used during the BRNG protocol. 16 | type PullConsensus struct { 17 | done bool 18 | indices []secp256k1.Fn 19 | honestSubset []secp256k1.Fn 20 | threshold int32 21 | table [][]brng.Sharing 22 | h secp256k1.Point 23 | } 24 | 25 | // SizeHint implements the surge.SizeHinter interface. 26 | func (pc PullConsensus) SizeHint() int { 27 | return surge.SizeHint(pc.done) + 28 | surge.SizeHint(pc.indices) + 29 | surge.SizeHint(pc.honestSubset) + 30 | surge.SizeHint(pc.threshold) + 31 | surge.SizeHint(pc.table) + 32 | pc.h.SizeHint() 33 | } 34 | 35 | // Marshal implements the surge.Marshaler interface. 36 | func (pc PullConsensus) Marshal(buf []byte, rem int) ([]byte, int, error) { 37 | buf, rem, err := surge.MarshalBool(pc.done, buf, rem) 38 | if err != nil { 39 | return buf, rem, fmt.Errorf("error marshaling done: %v", err) 40 | } 41 | buf, rem, err = surge.Marshal(pc.indices, buf, rem) 42 | if err != nil { 43 | return buf, rem, fmt.Errorf("error marshaling indices: %v", err) 44 | } 45 | buf, rem, err = surge.Marshal(pc.honestSubset, buf, rem) 46 | if err != nil { 47 | return buf, rem, fmt.Errorf("error marshaling honestSubset: %v", err) 48 | } 49 | buf, rem, err = surge.MarshalI32(pc.threshold, buf, rem) 50 | if err != nil { 51 | return buf, rem, fmt.Errorf("error marshaling threshold: %v", err) 52 | } 53 | buf, rem, err = surge.Marshal(pc.table, buf, rem) 54 | if err != nil { 55 | return buf, rem, fmt.Errorf("error marshaling table: %v", err) 56 | } 57 | buf, rem, err = pc.h.Marshal(buf, rem) 58 | if err != nil { 59 | return buf, rem, fmt.Errorf("error marshaling h: %v", err) 60 | } 61 | return buf, rem, nil 62 | } 63 | 64 | // Unmarshal implements the surge.Unmarshaler interface. 65 | func (pc PullConsensus) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 66 | buf, rem, err := surge.UnmarshalBool(&pc.done, buf, rem) 67 | if err != nil { 68 | return buf, rem, fmt.Errorf("error unmarshaling done: %v", err) 69 | } 70 | buf, rem, err = surge.Unmarshal(&pc.indices, buf, rem) 71 | if err != nil { 72 | return buf, rem, fmt.Errorf("error unmarshaling indices: %v", err) 73 | } 74 | buf, rem, err = surge.Unmarshal(&pc.honestSubset, buf, rem) 75 | if err != nil { 76 | return buf, rem, fmt.Errorf("error unmarshaling honestSubset: %v", err) 77 | } 78 | buf, rem, err = surge.UnmarshalI32(&pc.threshold, buf, rem) 79 | if err != nil { 80 | return buf, rem, fmt.Errorf("error unmarshaling threshold: %v", err) 81 | } 82 | buf, rem, err = surge.Unmarshal(&pc.table, buf, rem) 83 | if err != nil { 84 | return buf, rem, fmt.Errorf("error unmarshaling table: %v", err) 85 | } 86 | buf, rem, err = pc.h.Unmarshal(buf, rem) 87 | if err != nil { 88 | return buf, rem, fmt.Errorf("error unmarshaling h: %v", err) 89 | } 90 | return buf, rem, nil 91 | } 92 | 93 | // NewPullConsensus constructs a new mock consensus object. The honest indices 94 | // represent the indices of the honest players and the adversary count 95 | // represents the maximum number of adversaries that there will be. `h` 96 | // represents the Pedersen commitment parameter. 97 | func NewPullConsensus(inds, honestIndices []secp256k1.Fn, advCount int, h secp256k1.Point) PullConsensus { 98 | var table [][]brng.Sharing 99 | 100 | done := false 101 | threshold := int32(advCount) + 1 102 | indices := make([]secp256k1.Fn, len(inds)) 103 | copy(indices, inds) 104 | 105 | // Pick a random subset of honest parties that we will require to agree in 106 | // consensus. 107 | honestSubset := make([]secp256k1.Fn, len(honestIndices)) 108 | copy(honestSubset, honestIndices) 109 | rand.Shuffle(len(honestSubset), func(i, j int) { 110 | honestSubset[i], honestSubset[j] = honestSubset[j], honestSubset[i] 111 | }) 112 | honestSubset = honestSubset[:advCount+1] 113 | 114 | return PullConsensus{ 115 | done, 116 | indices, 117 | honestSubset, 118 | threshold, 119 | table, 120 | h, 121 | } 122 | } 123 | 124 | // Table returns the output table of the consensus algorithm. This table will 125 | // only be correct if `HandleRow` has returned `true`. 126 | func (pc PullConsensus) Table() [][]brng.Sharing { 127 | return pc.table 128 | } 129 | 130 | // Done returns if the consensus engine has already reached consensus or not 131 | // yet. 132 | func (pc PullConsensus) Done() bool { 133 | return pc.done 134 | } 135 | 136 | // HandleRow processes a row received from a player. It returns true if 137 | // consensus has completed, at which point the complete output table can be 138 | // accessed, and false otherwise. 139 | func (pc *PullConsensus) HandleRow(row []brng.Sharing) bool { 140 | if pc.done { 141 | return true 142 | } 143 | 144 | for _, sharing := range row { 145 | for _, index := range pc.honestSubset { 146 | var share shamir.VerifiableShare 147 | for _, s := range sharing.Shares { 148 | if s.Share.IndexEq(&index) { 149 | share = s 150 | } 151 | } 152 | 153 | c := sharing.Commitment 154 | if !shamir.IsValid(pc.h, &c, &share) { 155 | return pc.done 156 | } 157 | } 158 | } 159 | 160 | pc.table = append(pc.table, row) 161 | if len(pc.table) == int(pc.threshold) { 162 | pc.done = true 163 | } 164 | 165 | return pc.done 166 | } 167 | -------------------------------------------------------------------------------- /brng/sharing.go: -------------------------------------------------------------------------------- 1 | package brng 2 | 3 | import "github.com/renproject/shamir" 4 | 5 | // A Sharing is a grouping of the shares for a verifiable secret sharing, along 6 | // with the corresponding commitment. 7 | type Sharing struct { 8 | Shares shamir.VerifiableShares 9 | Commitment shamir.Commitment 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/renproject/mpc 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/onsi/ginkgo v1.12.3 7 | github.com/onsi/gomega v1.10.1 8 | github.com/renproject/secp256k1 v0.0.0-20200702062750-43cb0b3fed27 9 | github.com/renproject/shamir v0.0.0-20200802012337-72890748ec8d 10 | github.com/renproject/surge v1.2.6 11 | ) 12 | -------------------------------------------------------------------------------- /inv/inv.go: -------------------------------------------------------------------------------- 1 | package inv 2 | 3 | import ( 4 | "github.com/renproject/mpc/mulopen" 5 | "github.com/renproject/mpc/params" 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | ) 9 | 10 | // An Inverter is a state machine that implements the inversion protocol. 11 | type Inverter struct { 12 | mulopener mulopen.MulOpener 13 | rShareBatch shamir.VerifiableShares 14 | rCommitmentBatch []shamir.Commitment 15 | } 16 | 17 | // New returns a new Inverter state machine along with the initial message that 18 | // is to be broadcast to the other parties. The state machine will handle this 19 | // message before being returned. 20 | func New( 21 | aShareBatch, rShareBatch, rzgShareBatch shamir.VerifiableShares, 22 | aCommitmentBatch, rCommitmentBatch, rzgCommitmentBatch []shamir.Commitment, 23 | indices []secp256k1.Fn, h secp256k1.Point, 24 | ) (Inverter, []mulopen.Message) { 25 | if !params.ValidPedersenParameter(h) { 26 | panic("insecure choice of pedersen parameter") 27 | } 28 | rShareBatchCopy := make(shamir.VerifiableShares, len(rShareBatch)) 29 | rCommitmentBatchCopy := make([]shamir.Commitment, len(rCommitmentBatch)) 30 | copy(rShareBatchCopy, rShareBatch) 31 | copy(rCommitmentBatchCopy, rCommitmentBatch) 32 | mulopener, messages := mulopen.New( 33 | aShareBatch, rShareBatch, rzgShareBatch, 34 | aCommitmentBatch, rCommitmentBatch, rzgCommitmentBatch, 35 | indices, h, 36 | ) 37 | inverter := Inverter{ 38 | mulopener: mulopener, 39 | rShareBatch: rShareBatchCopy, 40 | rCommitmentBatch: rCommitmentBatchCopy, 41 | } 42 | return inverter, messages 43 | } 44 | 45 | // HandleMulOpenMessageBatch applies a state transition upon receiveing the 46 | // given shares from another party during the multiply and open step in the 47 | // inversion protocol. Once enough valid messages have been received to 48 | // complete the inversion protocol, the output, i.e. shares and commitments 49 | // that correspond to the multiplicative inverse of the input secret, is 50 | // computed and returned. If not enough messages have been received, the return 51 | // value will be nil. If the message batch is invalid in any way, an error will 52 | // be returned along with a nil value. 53 | func (inverter *Inverter) HandleMulOpenMessageBatch(messageBatch []mulopen.Message) ( 54 | shamir.VerifiableShares, []shamir.Commitment, error, 55 | ) { 56 | output, err := inverter.mulopener.HandleShareBatch(messageBatch) 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | if output != nil { 61 | var inv secp256k1.Fn 62 | invShares := make(shamir.VerifiableShares, len(inverter.rShareBatch)) 63 | invCommitments := make([]shamir.Commitment, len(inverter.rCommitmentBatch)) 64 | for i := range output { 65 | invCommitments[i] = shamir.NewCommitmentWithCapacity(inverter.rCommitmentBatch[0].Len()) 66 | inv.Inverse(&output[i]) 67 | invShares[i].Scale(&inverter.rShareBatch[i], &inv) 68 | invCommitments[i].Scale(inverter.rCommitmentBatch[i], &inv) 69 | } 70 | return invShares, invCommitments, nil 71 | } 72 | return nil, nil, nil 73 | } 74 | -------------------------------------------------------------------------------- /inv/inv_suite_test.go: -------------------------------------------------------------------------------- 1 | package inv_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestInv(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Inv Suite") 13 | } 14 | -------------------------------------------------------------------------------- /inv/inv_test.go: -------------------------------------------------------------------------------- 1 | package inv_test 2 | 3 | import ( 4 | "math/rand" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | 9 | "github.com/renproject/mpc/inv/invutil" 10 | "github.com/renproject/mpc/mpcutil" 11 | "github.com/renproject/mpc/rkpg/rkpgutil" 12 | "github.com/renproject/secp256k1" 13 | "github.com/renproject/shamir" 14 | "github.com/renproject/shamir/shamirutil" 15 | ) 16 | 17 | var _ = Describe("inverter", func() { 18 | Context("network", func() { 19 | n := 15 20 | k := 4 21 | b := 3 22 | t := k - 1 23 | 24 | tys := []invutil.MachineType{ 25 | invutil.Offline, 26 | invutil.Malicious, 27 | } 28 | for _, ty := range tys { 29 | ty := ty 30 | 31 | Specify("all honest nodes should reconstruct the product of the secrets", func() { 32 | indices := shamirutil.RandomIndices(n) 33 | h := secp256k1.RandomPoint() 34 | machines := make([]mpcutil.Machine, n) 35 | 36 | aShares, aCommitments, aSecrets := rkpgutil.RNGOutputBatch(indices, k, b, h) 37 | rShares, rCommitments, _ := rkpgutil.RNGOutputBatch(indices, k, b, h) 38 | rzgShares, rzgCommitments := rkpgutil.RZGOutputBatch(indices, 2*k-1, b, h) 39 | 40 | ids := make([]mpcutil.ID, n) 41 | for i := range ids { 42 | ids[i] = mpcutil.ID(i + 1) 43 | } 44 | dishonestIDs := make(map[mpcutil.ID]struct{}, t) 45 | { 46 | tmp := make([]mpcutil.ID, n) 47 | copy(tmp, ids) 48 | rand.Shuffle(len(tmp), func(i, j int) { 49 | tmp[i], tmp[j] = tmp[j], tmp[i] 50 | }) 51 | for _, id := range tmp[:t] { 52 | dishonestIDs[id] = struct{}{} 53 | } 54 | } 55 | machineType := make(map[mpcutil.ID]invutil.MachineType, n) 56 | for _, id := range ids { 57 | if _, ok := dishonestIDs[id]; ok { 58 | machineType[id] = ty 59 | } else { 60 | machineType[id] = invutil.Honest 61 | } 62 | } 63 | 64 | honestMachines := make([]*invutil.Machine, 0, n-t) 65 | for i, id := range ids { 66 | var machine mpcutil.Machine 67 | switch machineType[id] { 68 | case invutil.Offline: 69 | m := mpcutil.OfflineMachine(ids[i]) 70 | machine = &m 71 | case invutil.Malicious: 72 | m := invutil.NewMaliciousMachine( 73 | aShares[i], rShares[i], rzgShares[i], 74 | aCommitments, rCommitments, rzgCommitments, 75 | ids, id, indices, h, 76 | ) 77 | machine = &m 78 | case invutil.Honest: 79 | m := invutil.NewMachine( 80 | aShares[i], rShares[i], rzgShares[i], 81 | aCommitments, rCommitments, rzgCommitments, 82 | ids, id, indices, h, 83 | ) 84 | honestMachines = append(honestMachines, &m) 85 | machine = &m 86 | default: 87 | panic("unexpected machine type") 88 | } 89 | machines[i] = machine 90 | } 91 | 92 | shuffleMsgs, _ := mpcutil.MessageShufflerDropper(ids, 0) 93 | network := mpcutil.NewNetwork(machines, shuffleMsgs) 94 | network.SetCaptureHist(true) 95 | err := network.Run() 96 | Expect(err).ToNot(HaveOccurred()) 97 | 98 | for i := 0; i < b; i++ { 99 | var inv secp256k1.Fn 100 | inv.Inverse(&aSecrets[i]) 101 | 102 | // Each player should hold a valid share of the inverse of the 103 | // input. 104 | shares := make(shamir.Shares, 0, n) 105 | vshares := make(shamir.VerifiableShares, 0, n) 106 | for _, machine := range honestMachines { 107 | output := machine.OutputShares[i] 108 | vshares = append(vshares, output) 109 | shares = append(shares, output.Share) 110 | } 111 | commitment := honestMachines[0].OutputCommitments[i] 112 | for _, machine := range honestMachines { 113 | Expect(machine.OutputCommitments[i].Eq(commitment)).To(BeTrue()) 114 | } 115 | 116 | Expect(shamirutil.VsharesAreConsistent(vshares, k-1)).To(BeFalse()) 117 | Expect(shamirutil.VsharesAreConsistent(vshares, k)).To(BeTrue()) 118 | for _, vshare := range vshares { 119 | Expect(shamir.IsValid(h, &commitment, &vshare)).To(BeTrue()) 120 | } 121 | 122 | secret := shamir.Open(shares) 123 | Expect(secret.Eq(&inv)).To(BeTrue()) 124 | } 125 | }) 126 | } 127 | }) 128 | }) 129 | -------------------------------------------------------------------------------- /inv/invutil/machine.go: -------------------------------------------------------------------------------- 1 | package invutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/inv" 5 | "github.com/renproject/mpc/mpcutil" 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // Machine represents a player that honestly carries out the inversion 12 | // protocol. 13 | type Machine struct { 14 | OwnID mpcutil.ID 15 | inv.Inverter 16 | InitMsgs []Message 17 | OutputShares shamir.VerifiableShares 18 | OutputCommitments []shamir.Commitment 19 | } 20 | 21 | // NewMachine constructs a new honest machine for an inversion network test. It 22 | // will have the given inputs and ID. 23 | func NewMachine( 24 | aShareBatch, bShareBatch, rzgShareBatch shamir.VerifiableShares, 25 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch []shamir.Commitment, 26 | ids []mpcutil.ID, ownID mpcutil.ID, indices []secp256k1.Fn, h secp256k1.Point, 27 | ) Machine { 28 | inverter, msgs := inv.New( 29 | aShareBatch, bShareBatch, rzgShareBatch, 30 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch, 31 | indices, h, 32 | ) 33 | initialMessages := make([]Message, 0, len(ids)-1) 34 | for _, id := range ids { 35 | if id == ownID { 36 | continue 37 | } 38 | initialMessages = append(initialMessages, Message{ 39 | FromID: ownID, 40 | ToID: id, 41 | Messages: msgs, 42 | }) 43 | } 44 | return Machine{ 45 | OwnID: ownID, 46 | Inverter: inverter, 47 | InitMsgs: initialMessages, 48 | } 49 | } 50 | 51 | // ID implements the Machine interface. 52 | func (m Machine) ID() mpcutil.ID { return m.OwnID } 53 | 54 | // InitialMessages implements the Machine interface. 55 | func (m Machine) InitialMessages() []mpcutil.Message { 56 | msgs := make([]mpcutil.Message, len(m.InitMsgs)) 57 | for i := range m.InitMsgs { 58 | msgs[i] = &m.InitMsgs[i] 59 | } 60 | return msgs 61 | } 62 | 63 | // Handle implements the Machine interface. 64 | func (m *Machine) Handle(msg mpcutil.Message) []mpcutil.Message { 65 | outputShares, outputCommitments, _ := m.Inverter.HandleMulOpenMessageBatch(msg.(*Message).Messages) 66 | if outputShares != nil && outputCommitments != nil { 67 | m.OutputShares = outputShares 68 | m.OutputCommitments = outputCommitments 69 | } 70 | return nil 71 | } 72 | 73 | // SizeHint implements the surge.SizeHinter interface. 74 | func (m Machine) SizeHint() int { 75 | return m.OwnID.SizeHint() + 76 | m.Inverter.SizeHint() + 77 | surge.SizeHint(m.InitMsgs) + 78 | surge.SizeHint(m.OutputShares) + 79 | surge.SizeHint(m.OutputCommitments) 80 | } 81 | 82 | // Marshal implements the surge.Marshaler interface. 83 | func (m Machine) Marshal(buf []byte, rem int) ([]byte, int, error) { 84 | buf, rem, err := m.OwnID.Marshal(buf, rem) 85 | if err != nil { 86 | return buf, rem, err 87 | } 88 | buf, rem, err = m.Inverter.Marshal(buf, rem) 89 | if err != nil { 90 | return buf, rem, err 91 | } 92 | buf, rem, err = surge.Marshal(m.InitMsgs, buf, rem) 93 | if err != nil { 94 | return buf, rem, err 95 | } 96 | buf, rem, err = surge.Marshal(m.OutputShares, buf, rem) 97 | if err != nil { 98 | return buf, rem, err 99 | } 100 | return surge.Marshal(m.OutputCommitments, buf, rem) 101 | } 102 | 103 | // Unmarshal implements the surge.Unmarshaler interface. 104 | func (m *Machine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 105 | buf, rem, err := m.OwnID.Unmarshal(buf, rem) 106 | if err != nil { 107 | return buf, rem, err 108 | } 109 | buf, rem, err = m.Inverter.Unmarshal(buf, rem) 110 | if err != nil { 111 | return buf, rem, err 112 | } 113 | buf, rem, err = surge.Unmarshal(&m.InitMsgs, buf, rem) 114 | if err != nil { 115 | return buf, rem, err 116 | } 117 | buf, rem, err = surge.Unmarshal(&m.OutputShares, buf, rem) 118 | if err != nil { 119 | return buf, rem, err 120 | } 121 | return surge.Unmarshal(&m.OutputCommitments, buf, rem) 122 | } 123 | -------------------------------------------------------------------------------- /inv/invutil/malicious_machine.go: -------------------------------------------------------------------------------- 1 | package invutil 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/renproject/mpc/inv" 7 | "github.com/renproject/mpc/mpcutil" 8 | "github.com/renproject/mpc/mulopen" 9 | "github.com/renproject/secp256k1" 10 | "github.com/renproject/shamir" 11 | "github.com/renproject/shamir/shamirutil" 12 | "github.com/renproject/surge" 13 | ) 14 | 15 | // MaliciousMachine represents a player that deviates from the inversion 16 | // protocol by sending invalid messages. 17 | type MaliciousMachine struct { 18 | OwnID mpcutil.ID 19 | InitMsgs []Message 20 | } 21 | 22 | // NewMaliciousMachine constructs a new malicious machine for an inversion 23 | // network test. It will have the given inputs and ID. 24 | func NewMaliciousMachine( 25 | aShareBatch, bShareBatch, rzgShareBatch shamir.VerifiableShares, 26 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch []shamir.Commitment, 27 | ids []mpcutil.ID, ownID mpcutil.ID, indices []secp256k1.Fn, h secp256k1.Point, 28 | ) MaliciousMachine { 29 | _, msgs := inv.New( 30 | aShareBatch, bShareBatch, rzgShareBatch, 31 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch, 32 | indices, h, 33 | ) 34 | toBeModified := randomIDSubset(ids) 35 | initialMessages := make([]Message, 0, len(ids)-1) 36 | for _, id := range ids { 37 | if id == ownID { 38 | continue 39 | } 40 | msgsCopy := make([]mulopen.Message, len(msgs)) 41 | copy(msgsCopy, msgs) 42 | message := Message{ 43 | FromID: ownID, 44 | ToID: id, 45 | Messages: msgsCopy, 46 | } 47 | if _, ok := toBeModified[id]; ok { 48 | modifyMessageBatch(message.Messages) 49 | } 50 | initialMessages = append(initialMessages, message) 51 | } 52 | return MaliciousMachine{ 53 | OwnID: ownID, 54 | InitMsgs: initialMessages, 55 | } 56 | } 57 | 58 | func randomIDSubset(ids []mpcutil.ID) map[mpcutil.ID]struct{} { 59 | shuffledIDs := make([]mpcutil.ID, len(ids)) 60 | copy(shuffledIDs, ids) 61 | rand.Shuffle(len(shuffledIDs), func(i, j int) { 62 | shuffledIDs[i], shuffledIDs[j] = shuffledIDs[j], shuffledIDs[i] 63 | }) 64 | numModified := shamirutil.RandRange(1, len(ids)-1) 65 | isInSubset := make(map[mpcutil.ID]struct{}, numModified) 66 | for i := 0; i < numModified; i++ { 67 | isInSubset[shuffledIDs[i]] = struct{}{} 68 | } 69 | return isInSubset 70 | } 71 | 72 | func modifyMessageBatch(messageBatch []mulopen.Message) { 73 | batchToModify := rand.Intn(len(messageBatch)) 74 | switch rand.Intn(3) { 75 | case 0: 76 | messageBatch[batchToModify].VShare.Share.Value = secp256k1.RandomFn() 77 | case 1: 78 | messageBatch[batchToModify].VShare.Decommitment = secp256k1.RandomFn() 79 | case 2: 80 | messageBatch[batchToModify].Commitment = secp256k1.RandomPoint() 81 | default: 82 | panic("invalid case") 83 | } 84 | } 85 | 86 | // ID implements the Machine interface. 87 | func (m MaliciousMachine) ID() mpcutil.ID { return m.OwnID } 88 | 89 | // InitialMessages implements the Machine interface. 90 | func (m MaliciousMachine) InitialMessages() []mpcutil.Message { 91 | msgs := make([]mpcutil.Message, len(m.InitMsgs)) 92 | for i := range m.InitMsgs { 93 | msgs[i] = &m.InitMsgs[i] 94 | } 95 | return msgs 96 | } 97 | 98 | // Handle implements the Machine interface. 99 | func (m *MaliciousMachine) Handle(msg mpcutil.Message) []mpcutil.Message { 100 | return nil 101 | } 102 | 103 | // SizeHint implements the surge.SizeHinter interface. 104 | func (m MaliciousMachine) SizeHint() int { 105 | return m.OwnID.SizeHint() + 106 | surge.SizeHint(m.InitMsgs) 107 | } 108 | 109 | // Marshal implements the surge.Marshaler interface. 110 | func (m MaliciousMachine) Marshal(buf []byte, rem int) ([]byte, int, error) { 111 | buf, rem, err := m.OwnID.Marshal(buf, rem) 112 | if err != nil { 113 | return buf, rem, err 114 | } 115 | return surge.Marshal(m.InitMsgs, buf, rem) 116 | } 117 | 118 | // Unmarshal implements the surge.Unmarshaler interface. 119 | func (m *MaliciousMachine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 120 | buf, rem, err := m.OwnID.Unmarshal(buf, rem) 121 | if err != nil { 122 | return buf, rem, err 123 | } 124 | return surge.Unmarshal(&m.InitMsgs, buf, rem) 125 | } 126 | -------------------------------------------------------------------------------- /inv/invutil/message.go: -------------------------------------------------------------------------------- 1 | package invutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/mpc/mulopen" 6 | "github.com/renproject/surge" 7 | ) 8 | 9 | // Message is the message type that players send to eachother during an 10 | // instance of inversion. 11 | type Message struct { 12 | FromID, ToID mpcutil.ID 13 | Messages []mulopen.Message 14 | } 15 | 16 | // From implements the mpcutil.Message interface. 17 | func (msg Message) From() mpcutil.ID { return msg.FromID } 18 | 19 | // To implements the mpcutil.Message interface. 20 | func (msg Message) To() mpcutil.ID { return msg.ToID } 21 | 22 | // SizeHint implements the surge.SizeHinter interface. 23 | func (msg Message) SizeHint() int { 24 | return msg.FromID.SizeHint() + 25 | msg.ToID.SizeHint() + 26 | surge.SizeHint(msg.Messages) 27 | } 28 | 29 | // Marshal implements the surge.Marshaler interface. 30 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 31 | buf, rem, err := msg.FromID.Marshal(buf, rem) 32 | if err != nil { 33 | return buf, rem, err 34 | } 35 | buf, rem, err = msg.ToID.Marshal(buf, rem) 36 | if err != nil { 37 | return buf, rem, err 38 | } 39 | return surge.Marshal(msg.Messages, buf, rem) 40 | } 41 | 42 | // Unmarshal implements the surge.Unmarshaler interface. 43 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 44 | buf, rem, err := msg.FromID.Unmarshal(buf, rem) 45 | if err != nil { 46 | return buf, rem, err 47 | } 48 | buf, rem, err = msg.ToID.Unmarshal(buf, rem) 49 | if err != nil { 50 | return buf, rem, err 51 | } 52 | return surge.Unmarshal(&msg.Messages, buf, rem) 53 | } 54 | -------------------------------------------------------------------------------- /inv/invutil/type.go: -------------------------------------------------------------------------------- 1 | package invutil 2 | 3 | // MachineType represents a type of player in the network. 4 | type MachineType byte 5 | 6 | const ( 7 | // Honest represents a player that follows the inversion protocol as 8 | // specified. 9 | Honest = MachineType(iota) 10 | 11 | // Offline represents a player that is offline. 12 | Offline 13 | 14 | // Malicious represents a player that deviates from the inversion protocol 15 | // by sending shares or commitments with incorrect values. 16 | Malicious 17 | ) 18 | -------------------------------------------------------------------------------- /inv/marshal.go: -------------------------------------------------------------------------------- 1 | package inv 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen" 8 | "github.com/renproject/secp256k1" 9 | "github.com/renproject/shamir" 10 | "github.com/renproject/surge" 11 | ) 12 | 13 | // SizeHint implements the surge.SizeHinter interface. 14 | func (inverter Inverter) SizeHint() int { 15 | return inverter.mulopener.SizeHint() + 16 | surge.SizeHint(inverter.rShareBatch) + 17 | surge.SizeHint(inverter.rCommitmentBatch) 18 | } 19 | 20 | // Marshal implements the surge.Marshaler interface. 21 | func (inverter Inverter) Marshal(buf []byte, rem int) ([]byte, int, error) { 22 | buf, rem, err := inverter.mulopener.Marshal(buf, rem) 23 | if err != nil { 24 | return buf, rem, err 25 | } 26 | buf, rem, err = surge.Marshal(inverter.rShareBatch, buf, rem) 27 | if err != nil { 28 | return buf, rem, err 29 | } 30 | return surge.Marshal(inverter.rCommitmentBatch, buf, rem) 31 | } 32 | 33 | // Unmarshal implements the surge.Unmarshaler interface. 34 | func (inverter *Inverter) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 35 | buf, rem, err := inverter.mulopener.Unmarshal(buf, rem) 36 | if err != nil { 37 | return buf, rem, err 38 | } 39 | buf, rem, err = surge.Unmarshal(&inverter.rShareBatch, buf, rem) 40 | if err != nil { 41 | return buf, rem, err 42 | } 43 | return surge.Unmarshal(&inverter.rCommitmentBatch, buf, rem) 44 | } 45 | 46 | // Generate implements the quick.Generator interface. 47 | func (inverter Inverter) Generate(rand *rand.Rand, size int) reflect.Value { 48 | size /= 4 49 | b := rand.Intn(size/2) + 1 50 | mulopener := mulopen.MulOpener{}.Generate(rand, size).Interface().(mulopen.MulOpener) 51 | rShareBatch := make(shamir.VerifiableShares, b) 52 | for i := 0; i < b; i++ { 53 | rShareBatch[i] = shamir.VerifiableShare{ 54 | Share: shamir.Share{ 55 | Index: secp256k1.RandomFn(), 56 | Value: secp256k1.RandomFn(), 57 | }, 58 | Decommitment: secp256k1.RandomFn(), 59 | } 60 | } 61 | rCommitmentBatch := make([]shamir.Commitment, b) 62 | for i := 0; i < b; i++ { 63 | rCommitmentBatch[i] = shamir.Commitment{}.Generate(rand, size/2).Interface().(shamir.Commitment) 64 | } 65 | inv := Inverter{ 66 | mulopener, 67 | rShareBatch, 68 | rCommitmentBatch, 69 | } 70 | return reflect.ValueOf(inv) 71 | } 72 | -------------------------------------------------------------------------------- /inv/marshal_test.go: -------------------------------------------------------------------------------- 1 | package inv_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/inv" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | ts := []reflect.Type{ 17 | reflect.TypeOf(inv.Inverter{}), 18 | } 19 | 20 | for _, t := range ts { 21 | t := t 22 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 23 | It("should be the same after marshalling and unmarshalling", func() { 24 | for i := 0; i < trials; i++ { 25 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 26 | } 27 | }) 28 | 29 | It("should not panic when fuzzing", func() { 30 | for i := 0; i < trials; i++ { 31 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 32 | } 33 | }) 34 | 35 | Context("marshalling", func() { 36 | It("should return an error when the buffer is too small", func() { 37 | for i := 0; i < trials; i++ { 38 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 39 | } 40 | }) 41 | 42 | It("should return an error when the memory quota is too small", func() { 43 | for i := 0; i < trials; i++ { 44 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 45 | } 46 | }) 47 | }) 48 | 49 | Context("unmarshalling", func() { 50 | It("should return an error when the buffer is too small", func() { 51 | for i := 0; i < trials; i++ { 52 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 53 | } 54 | }) 55 | 56 | It("should return an error when the memory quota is too small", func() { 57 | for i := 0; i < trials; i++ { 58 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 59 | } 60 | }) 61 | }) 62 | }) 63 | } 64 | }) 65 | -------------------------------------------------------------------------------- /mpcutil/debugger.go: -------------------------------------------------------------------------------- 1 | package mpcutil 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | 7 | "github.com/renproject/surge" 8 | ) 9 | 10 | // A Debugger provides functionality for loading debug states, and performing 11 | // debugging operations on the given debug state (which consists of a message 12 | // history and initial states for the machines). 13 | type Debugger struct { 14 | messages []Message 15 | machines []Machine 16 | 17 | pos int 18 | machbps []machineBreakPoint 19 | msgbps []messageBreakPoint 20 | } 21 | 22 | // NewDebugger creates a new Debugger from the file with the given filename. 23 | // The messageType and machineType arguments are used to know how to correctly 24 | // unmarshal the file. 25 | func NewDebugger(filename string, messageType, machineType interface{}) Debugger { 26 | file, err := os.Open(filename) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // Unmarshal machines. 32 | buf := make([]byte, surge.MaxBytes) 33 | _, err = file.Read(buf) 34 | sl := reflect.New(reflect.SliceOf(reflect.TypeOf(machineType))) 35 | buf, _, err = surge.Unmarshal(sl.Interface(), buf, surge.MaxBytes) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | var machines []Machine 41 | for i := 0; i < reflect.Indirect(sl).Len(); i++ { 42 | machines = append(machines, reflect.Indirect(sl).Index(i).Addr().Interface().(Machine)) 43 | } 44 | 45 | // Unmarshal messages. 46 | sl = reflect.New(reflect.SliceOf(reflect.TypeOf(messageType))) 47 | _, _, err = surge.Unmarshal(sl.Interface(), buf, surge.MaxBytes) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | var messages []Message 53 | for i := 0; i < reflect.Indirect(sl).Len(); i++ { 54 | messages = append(messages, reflect.Indirect(sl).Index(i).Addr().Interface().(Message)) 55 | } 56 | 57 | pos := 0 58 | var machbps []machineBreakPoint 59 | var msgbps []messageBreakPoint 60 | 61 | return Debugger{messages, machines, pos, machbps, msgbps} 62 | } 63 | 64 | // Step processes the next message in the message history. It returns true if 65 | // there are more messages in the hostory, and false otherwise 66 | func (dbg *Debugger) Step() bool { 67 | msg := dbg.messages[dbg.pos] 68 | _ = dbg.machines[msg.To()].Handle(msg) 69 | dbg.pos++ 70 | 71 | if dbg.pos == len(dbg.messages) { 72 | return false 73 | } 74 | return true 75 | } 76 | 77 | // MachineByID returns the machine for the given ID in its current state. 78 | func (dbg Debugger) MachineByID(id ID) Machine { 79 | for _, machine := range dbg.machines { 80 | if machine.ID() == id { 81 | return machine 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // MessagesForID returns all of the messages in the message history that are 89 | // addressed to the given ID. 90 | func (dbg Debugger) MessagesForID(id ID) []Message { 91 | msgsForID := make([]Message, 0) 92 | 93 | for _, m := range dbg.messages { 94 | if m.To() == id { 95 | msgsForID = append(msgsForID, m) 96 | } 97 | } 98 | 99 | return msgsForID 100 | } 101 | 102 | // SetMachineBreakPoint registers and enables a breakpoint for the machine with 103 | // the given ID. The breakpoint will trigger the first time that the machine is 104 | // in a state such that the predicate returns true. 105 | func (dbg *Debugger) SetMachineBreakPoint(id ID, pred func(Machine) bool) { 106 | enabled := true 107 | bp := machineBreakPoint{id, enabled, pred} 108 | dbg.machbps = append(dbg.machbps, bp) 109 | } 110 | 111 | // SetMessageBreakPoint registers and enables a breakpoint that will trigger 112 | // when the next message to be handled (by any machine) satisfies the given 113 | // predicate. 114 | func (dbg *Debugger) SetMessageBreakPoint(pred func(Message) bool) { 115 | enabled := true 116 | bp := messageBreakPoint{enabled, pred} 117 | dbg.msgbps = append(dbg.msgbps, bp) 118 | } 119 | 120 | // Continue handles messages either until a breakpoint is triggered or there 121 | // are no more messages to handle. 122 | func (dbg *Debugger) Continue() { 123 | for { 124 | if dbg.machBpTriggered() || dbg.msgBpTriggered(dbg.messages[dbg.pos]) { 125 | break 126 | } 127 | 128 | if !dbg.Step() { 129 | break 130 | } 131 | } 132 | } 133 | 134 | func (dbg *Debugger) machBpTriggered() bool { 135 | for i := range dbg.machbps { 136 | if !dbg.machbps[i].enabled { 137 | continue 138 | } 139 | if dbg.machbps[i].pred(dbg.MachineByID(dbg.machbps[i].id)) { 140 | dbg.machbps[i].enabled = false 141 | return true 142 | } 143 | } 144 | return false 145 | } 146 | 147 | func (dbg *Debugger) msgBpTriggered(msg Message) bool { 148 | for i := range dbg.msgbps { 149 | if !dbg.msgbps[i].enabled { 150 | continue 151 | } 152 | if dbg.msgbps[i].pred(msg) { 153 | dbg.msgbps[i].enabled = false 154 | return true 155 | } 156 | } 157 | return false 158 | } 159 | 160 | type machineBreakPoint struct { 161 | id ID 162 | enabled bool 163 | pred func(Machine) bool 164 | } 165 | 166 | type messageBreakPoint struct { 167 | enabled bool 168 | pred func(Message) bool 169 | } 170 | -------------------------------------------------------------------------------- /mpcutil/machine.go: -------------------------------------------------------------------------------- 1 | package mpcutil 2 | 3 | import "github.com/renproject/surge" 4 | 5 | // The Machine interface represents one of the players in a distributed 6 | // network. Every machine must have a unique ID, and be able to handle incoming 7 | // messages. 8 | type Machine interface { 9 | surge.MarshalUnmarshaler 10 | 11 | ID() ID 12 | 13 | // InitialMessages should return the messages that a Machine sends at the 14 | // start of a network run, i.e. those messages that it would send before 15 | // having received any, if there are such messages. 16 | InitialMessages() []Message 17 | 18 | // Handle processes an incoming message and returns response messages, if 19 | // any. 20 | Handle(Message) []Message 21 | } 22 | 23 | // An OfflineMachine represents a player that is offline. It does not send any 24 | // messages. 25 | type OfflineMachine ID 26 | 27 | // ID implements the Machine interface. 28 | func (m OfflineMachine) ID() ID { return ID(m) } 29 | 30 | // InitialMessages implements the Machine interface. 31 | func (m OfflineMachine) InitialMessages() []Message { return nil } 32 | 33 | // Handle implements the Machine interface. 34 | func (m OfflineMachine) Handle(_ Message) []Message { return nil } 35 | 36 | // SizeHint implements the surge.SizeHinter interface. 37 | func (m OfflineMachine) SizeHint() int { 38 | return ID(m).SizeHint() 39 | } 40 | 41 | // Marshal implements the surge.Marshaler interface. 42 | func (m OfflineMachine) Marshal(buf []byte, rem int) ([]byte, int, error) { 43 | return ID(m).Marshal(buf, rem) 44 | } 45 | 46 | // Unmarshal implements the surge.Unmarshaler interface. 47 | func (m *OfflineMachine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 48 | return (*ID)(m).Unmarshal(buf, rem) 49 | } 50 | -------------------------------------------------------------------------------- /mpcutil/network.go: -------------------------------------------------------------------------------- 1 | package mpcutil 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // ID represents a unique identifier for a Machine. 12 | type ID int32 13 | 14 | // SizeHint implements the surge.SizeHinter interface. 15 | func (id ID) SizeHint() int { return 4 } 16 | 17 | // Marshal implements the surge.Marshaler interface. 18 | func (id ID) Marshal(buf []byte, rem int) ([]byte, int, error) { 19 | return surge.MarshalI32(int32(id), buf, rem) 20 | } 21 | 22 | // Unmarshal implements the surge.Unmarshaler interface. 23 | func (id *ID) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 24 | return surge.UnmarshalI32((*int32)(id), buf, rem) 25 | } 26 | 27 | // The Message interface represents a message that can be sent during a network 28 | // run. Messages must be able to give the IDs for the sender and receiver of 29 | // the message. 30 | type Message interface { 31 | surge.MarshalUnmarshaler 32 | 33 | From() ID 34 | To() ID 35 | } 36 | 37 | // A Network is used to simulate a network of distributed Machines that send 38 | // and recieve messages from eachother. 39 | type Network struct { 40 | msgBufCurr, msgBufNext []Message 41 | machines []Machine 42 | processMsgs func([]Message) 43 | indexOfID map[ID]int 44 | 45 | captureHist bool 46 | msgHist []Message 47 | initialStates []byte 48 | } 49 | 50 | // NewNetwork creates a new Network object from the given machines and message 51 | // processing function. This message processing function will be applied to all 52 | // of the messages to be sent in a given round, before sending them. For 53 | // example, this can be used to shuffle or drop messages from certain players 54 | // to simulate various network conditions. 55 | func NewNetwork(machines []Machine, processMsgs func([]Message)) Network { 56 | n := len(machines) 57 | indexOfID := make(map[ID]int) 58 | 59 | for i, machine := range machines { 60 | if _, ok := indexOfID[machine.ID()]; ok { 61 | panic(fmt.Sprintf("two machines can't have the same ID: found duplicate ID %v", machine.ID())) 62 | } 63 | indexOfID[machine.ID()] = i 64 | } 65 | 66 | // Save initial machine state. 67 | buf, err := surge.ToBinary(machines) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | return Network{ 73 | msgBufCurr: make([]Message, (n-1)*n)[:0], 74 | msgBufNext: make([]Message, (n-1)*n)[:0], 75 | 76 | // TODO: Copy the machines instead? 77 | machines: machines, 78 | 79 | processMsgs: processMsgs, 80 | indexOfID: indexOfID, 81 | 82 | // TODO: Try to do something clever with the first allocation size? 83 | captureHist: false, 84 | msgHist: make([]Message, n)[:0], 85 | initialStates: buf, 86 | } 87 | } 88 | 89 | // SetCaptureHist sets wether the network will capture the message history and 90 | // create a debug file on a panic. The message history needs to be captured if 91 | // such a debug file is to be used in a later debugging session. 92 | func (net *Network) SetCaptureHist(b bool) { 93 | net.captureHist = b 94 | } 95 | 96 | // Run drives an execution of the network of machines to completion. The run 97 | // will continue until there are no more messages to deliver. An error is 98 | // returned indicating the success of the run; if message history is being 99 | // captured, an error will be returned if any of the machines panic when 100 | // handling a message. In all other cases, a nil error is returned. 101 | func (net *Network) Run() error { 102 | // Fill the message buffer with the first messages. 103 | net.msgBufCurr = net.msgBufCurr[:0] 104 | for _, machine := range net.machines { 105 | messages := machine.InitialMessages() 106 | if messages != nil { 107 | net.msgBufCurr = append(net.msgBufCurr, messages...) 108 | } 109 | } 110 | net.processMsgs(net.msgBufCurr) 111 | 112 | // Each loop is one round in the protocol. 113 | for { 114 | for _, msg := range net.msgBufCurr { 115 | // Ignore nil messages. 116 | if msg == nil { 117 | continue 118 | } 119 | 120 | // Add the about to be delivered message to the history. 121 | if net.captureHist { 122 | net.msgHist = append(net.msgHist, msg) 123 | } 124 | 125 | err := net.deliver(msg) 126 | if err != nil && net.captureHist { 127 | // If we get here then the machine we just tried to deliver the 128 | // message to panicked. 129 | net.Dump("panic.dump") 130 | 131 | return err 132 | } 133 | } 134 | 135 | if len(net.msgBufNext) == 0 { 136 | // All machines have finished sending messages. 137 | break 138 | } 139 | 140 | // switch message buffers 141 | net.msgBufCurr, net.msgBufNext = net.msgBufNext, net.msgBufCurr[:0] 142 | 143 | // Do any processing on the messages for the next round here, e.g. 144 | // shuffling. 145 | net.processMsgs(net.msgBufCurr) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func (net *Network) deliver(msg Message) (err error) { 152 | err = nil 153 | 154 | if net.captureHist { 155 | // Catch any panics and create debug file if they occur. 156 | defer func() { 157 | r := recover() 158 | if r != nil { 159 | if e, ok := r.(error); ok { 160 | err = e 161 | } else { 162 | err = fmt.Errorf("panic: %v", r) 163 | } 164 | } 165 | }() 166 | } 167 | 168 | res := net.machines[net.indexOfID[msg.To()]].Handle(msg) 169 | if res != nil { 170 | net.msgBufNext = append(net.msgBufNext, res...) 171 | } 172 | 173 | return 174 | } 175 | 176 | // Dump saves the initial state of the machines and the message history to the 177 | // file with the given name. This file can be loaded by a Debugger to start a 178 | // debugging session. 179 | func (net Network) Dump(filename string) { 180 | file, err := os.Create(filename) 181 | if err != nil { 182 | fmt.Printf("unable to create dump file: %v", err) 183 | } 184 | defer file.Close() 185 | 186 | fmt.Printf("dumping debug state to file %s\n", filename) 187 | 188 | // Write machine initial states. 189 | _, err = file.Write(net.initialStates) 190 | if err != nil { 191 | fmt.Printf("unable to write initial states to file: %v", err) 192 | } 193 | 194 | buf, err := surge.ToBinary(net.msgHist) 195 | if err != nil { 196 | fmt.Printf("unable to marshal message history: %v", err) 197 | } 198 | _, err = file.Write(buf) 199 | if err != nil { 200 | fmt.Printf("unable to write message history to file: %v", err) 201 | } 202 | } 203 | 204 | // MessageShufflerDropper returns a function that can be used as the message 205 | // processing parameter for a Network object. This message processor will 206 | // simulate there being `offline` number of machines offline, chosen randomly; 207 | // messages to or from these machines will be dropped. The message order will 208 | // also be shuffled each round. 209 | func MessageShufflerDropper(ids []ID, offline int) (func([]Message), map[ID]bool) { 210 | shufIDs := make([]ID, len(ids)) 211 | copy(shufIDs, ids) 212 | rand.Shuffle(len(shufIDs), func(i, j int) { 213 | shufIDs[i], shufIDs[j] = shufIDs[j], shufIDs[i] 214 | }) 215 | isOffline := make(map[ID]bool) 216 | for i := 0; i < offline; i++ { 217 | isOffline[shufIDs[i]] = true 218 | } 219 | for i := offline; i < len(shufIDs); i++ { 220 | isOffline[shufIDs[i]] = false 221 | } 222 | 223 | shuffleMsgs := func(msgs []Message) { 224 | rand.Shuffle(len(msgs), func(i, j int) { 225 | msgs[i], msgs[j] = msgs[j], msgs[i] 226 | }) 227 | 228 | // Delete any messages from the offline machines or to the offline 229 | // machines. 230 | for i, msg := range msgs { 231 | if isOffline[msg.From()] || isOffline[msg.To()] { 232 | msgs[i] = nil 233 | } 234 | } 235 | } 236 | 237 | return shuffleMsgs, isOffline 238 | } 239 | -------------------------------------------------------------------------------- /mulopen/err.go: -------------------------------------------------------------------------------- 1 | package mulopen 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrIncorrectBatchSize is returned when the batch size of the given 7 | // message is not equal to the batch size of the multiply and open 8 | // instance. 9 | ErrIncorrectBatchSize = errors.New("incorrect batch size") 10 | 11 | // ErrInvalidIndex is returned when the index of the shares in the batch 12 | // are not in the index set for the RKPG instance. 13 | ErrInvalidIndex = errors.New("invalid index") 14 | 15 | // ErrInconsistentShares is returned when not all shares in the batch have 16 | // the same index. 17 | ErrInconsistentShares = errors.New("inconsistent shares") 18 | 19 | // ErrDuplicateIndex signifies that the received share has an index that is 20 | // the same as the index of one of the shares that is already in the list 21 | // of valid shares received for the current sharing instance. 22 | ErrDuplicateIndex = errors.New("duplicate index") 23 | 24 | // ErrInvalidZKP is returned when not all of the given ZKPs in the message 25 | // are valid. 26 | ErrInvalidZKP = errors.New("invalid zkp") 27 | 28 | // ErrInvalidShares is returned when not all of the given shares are valid 29 | // with respect to their corresponding commitments. 30 | ErrInvalidShares = errors.New("invalid shares") 31 | ) 32 | -------------------------------------------------------------------------------- /mulopen/marshal.go: -------------------------------------------------------------------------------- 1 | package mulopen 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen/mulzkp" 8 | "github.com/renproject/secp256k1" 9 | "github.com/renproject/shamir" 10 | "github.com/renproject/shamir/shamirutil" 11 | "github.com/renproject/surge" 12 | ) 13 | 14 | // SizeHint implements the surge.SizeHinter interface. 15 | func (mulopener MulOpener) SizeHint() int { 16 | return surge.SizeHint(mulopener.shareBufs) + 17 | surge.SizeHint(mulopener.batchSize) + 18 | surge.SizeHint(mulopener.k) + 19 | surge.SizeHint(mulopener.aCommitmentBatch) + 20 | surge.SizeHint(mulopener.bCommitmentBatch) + 21 | surge.SizeHint(mulopener.rzgCommitmentBatch) + 22 | surge.SizeHint(mulopener.indices) + 23 | mulopener.h.SizeHint() 24 | } 25 | 26 | // Marshal implements the surge.Marshaler interface. 27 | func (mulopener MulOpener) Marshal(buf []byte, rem int) ([]byte, int, error) { 28 | buf, rem, err := surge.Marshal(mulopener.shareBufs, buf, rem) 29 | if err != nil { 30 | return buf, rem, err 31 | } 32 | buf, rem, err = surge.MarshalU32(mulopener.batchSize, buf, rem) 33 | if err != nil { 34 | return buf, rem, err 35 | } 36 | buf, rem, err = surge.MarshalU32(mulopener.k, buf, rem) 37 | if err != nil { 38 | return buf, rem, err 39 | } 40 | buf, rem, err = surge.Marshal(mulopener.aCommitmentBatch, buf, rem) 41 | if err != nil { 42 | return buf, rem, err 43 | } 44 | buf, rem, err = surge.Marshal(mulopener.bCommitmentBatch, buf, rem) 45 | if err != nil { 46 | return buf, rem, err 47 | } 48 | buf, rem, err = surge.Marshal(mulopener.rzgCommitmentBatch, buf, rem) 49 | if err != nil { 50 | return buf, rem, err 51 | } 52 | buf, rem, err = surge.Marshal(mulopener.indices, buf, rem) 53 | if err != nil { 54 | return buf, rem, err 55 | } 56 | return mulopener.h.Marshal(buf, rem) 57 | } 58 | 59 | // Unmarshal implements the surge.Unmarshaler interface. 60 | func (mulopener *MulOpener) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 61 | buf, rem, err := surge.Unmarshal(&mulopener.shareBufs, buf, rem) 62 | if err != nil { 63 | return buf, rem, err 64 | } 65 | buf, rem, err = surge.UnmarshalU32(&mulopener.batchSize, buf, rem) 66 | if err != nil { 67 | return buf, rem, err 68 | } 69 | buf, rem, err = surge.UnmarshalU32(&mulopener.k, buf, rem) 70 | if err != nil { 71 | return buf, rem, err 72 | } 73 | buf, rem, err = surge.Unmarshal(&mulopener.aCommitmentBatch, buf, rem) 74 | if err != nil { 75 | return buf, rem, err 76 | } 77 | buf, rem, err = surge.Unmarshal(&mulopener.bCommitmentBatch, buf, rem) 78 | if err != nil { 79 | return buf, rem, err 80 | } 81 | buf, rem, err = surge.Unmarshal(&mulopener.rzgCommitmentBatch, buf, rem) 82 | if err != nil { 83 | return buf, rem, err 84 | } 85 | buf, rem, err = surge.Unmarshal(&mulopener.indices, buf, rem) 86 | if err != nil { 87 | return buf, rem, err 88 | } 89 | return mulopener.h.Unmarshal(buf, rem) 90 | } 91 | 92 | // Generate implements the quick.Generator interface. 93 | func (mulopener MulOpener) Generate(_ *rand.Rand, size int) reflect.Value { 94 | size /= 5 95 | n := rand.Intn(size/2) + 1 96 | k := uint32(rand.Intn(size/2) + 2) 97 | batchSize := uint32(size) / k 98 | if batchSize == 0 { 99 | batchSize++ 100 | } 101 | shareBufs := make([]shamir.Shares, batchSize) 102 | numReceived := rand.Intn(n) 103 | for i := range shareBufs { 104 | shareBufs[i] = shamir.Shares{} 105 | for j := 0; j < numReceived; j++ { 106 | shareBufs[i] = append(shareBufs[i], 107 | shamir.Share{ 108 | Index: secp256k1.RandomFn(), 109 | Value: secp256k1.RandomFn(), 110 | }, 111 | ) 112 | } 113 | } 114 | aCommitmentBatch := make([]shamir.Commitment, batchSize) 115 | bCommitmentBatch := make([]shamir.Commitment, batchSize) 116 | rzgCommitmentBatch := make([]shamir.Commitment, batchSize) 117 | for i := uint32(0); i < batchSize; i++ { 118 | for j := uint32(0); j < k; j++ { 119 | aCommitmentBatch[i].Append(secp256k1.RandomPoint()) 120 | bCommitmentBatch[i].Append(secp256k1.RandomPoint()) 121 | rzgCommitmentBatch[i].Append(secp256k1.RandomPoint()) 122 | } 123 | } 124 | indices := shamirutil.RandomIndices(n) 125 | h := secp256k1.RandomPoint() 126 | mo := MulOpener{ 127 | shareBufs, 128 | batchSize, 129 | k, 130 | aCommitmentBatch, 131 | bCommitmentBatch, 132 | rzgCommitmentBatch, 133 | indices, 134 | h, 135 | } 136 | return reflect.ValueOf(mo) 137 | } 138 | 139 | // SizeHint implements the surge.SizeHinter interface. 140 | func (msg Message) SizeHint() int { 141 | return msg.VShare.SizeHint() + 142 | msg.Commitment.SizeHint() + 143 | msg.Proof.SizeHint() 144 | } 145 | 146 | // Marshal implements the surge.Marshaler interface. 147 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 148 | buf, rem, err := msg.VShare.Marshal(buf, rem) 149 | if err != nil { 150 | return buf, rem, err 151 | } 152 | buf, rem, err = msg.Commitment.Marshal(buf, rem) 153 | if err != nil { 154 | return buf, rem, err 155 | } 156 | return msg.Proof.Marshal(buf, rem) 157 | } 158 | 159 | // Unmarshal implements the surge.Unmarshaler interface. 160 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 161 | buf, rem, err := msg.VShare.Unmarshal(buf, rem) 162 | if err != nil { 163 | return buf, rem, err 164 | } 165 | buf, rem, err = msg.Commitment.Unmarshal(buf, rem) 166 | if err != nil { 167 | return buf, rem, err 168 | } 169 | return msg.Proof.Unmarshal(buf, rem) 170 | } 171 | 172 | // Generate implements the quick.Generator interface. 173 | func (msg Message) Generate(rand *rand.Rand, size int) reflect.Value { 174 | share := shamir.VerifiableShare{ 175 | Share: shamir.Share{ 176 | Index: secp256k1.RandomFn(), 177 | Value: secp256k1.RandomFn(), 178 | }, 179 | Decommitment: secp256k1.RandomFn(), 180 | } 181 | com := secp256k1.RandomPoint() 182 | proof := mulzkp.Proof{}.Generate(rand, size).Interface().(mulzkp.Proof) 183 | m := Message{ 184 | VShare: share, 185 | Commitment: com, 186 | Proof: proof, 187 | } 188 | return reflect.ValueOf(m) 189 | } 190 | -------------------------------------------------------------------------------- /mulopen/marshal_test.go: -------------------------------------------------------------------------------- 1 | package mulopen_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | ts := []reflect.Type{ 17 | reflect.TypeOf(mulopen.MulOpener{}), 18 | reflect.TypeOf(mulopen.Message{}), 19 | } 20 | 21 | for _, t := range ts { 22 | t := t 23 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 24 | It("should be the same after marshalling and unmarshalling", func() { 25 | for i := 0; i < trials; i++ { 26 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 27 | } 28 | }) 29 | 30 | It("should not panic when fuzzing", func() { 31 | for i := 0; i < trials; i++ { 32 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 33 | } 34 | }) 35 | 36 | Context("marshalling", func() { 37 | It("should return an error when the buffer is too small", func() { 38 | for i := 0; i < trials; i++ { 39 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 40 | } 41 | }) 42 | 43 | It("should return an error when the memory quota is too small", func() { 44 | for i := 0; i < trials; i++ { 45 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 46 | } 47 | }) 48 | }) 49 | 50 | Context("unmarshalling", func() { 51 | It("should return an error when the buffer is too small", func() { 52 | for i := 0; i < trials; i++ { 53 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 54 | } 55 | }) 56 | 57 | It("should return an error when the memory quota is too small", func() { 58 | for i := 0; i < trials; i++ { 59 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 60 | } 61 | }) 62 | }) 63 | }) 64 | } 65 | }) 66 | -------------------------------------------------------------------------------- /mulopen/message.go: -------------------------------------------------------------------------------- 1 | package mulopen 2 | 3 | import ( 4 | "github.com/renproject/mpc/mulopen/mulzkp" 5 | "github.com/renproject/secp256k1" 6 | "github.com/renproject/shamir" 7 | ) 8 | 9 | // The Message type that is sent between parties during an invocation of 10 | // multiply and open. 11 | type Message struct { 12 | VShare shamir.VerifiableShare 13 | Commitment secp256k1.Point 14 | Proof mulzkp.Proof 15 | } 16 | -------------------------------------------------------------------------------- /mulopen/mulopen.go: -------------------------------------------------------------------------------- 1 | package mulopen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/mpc/mulopen/mulzkp" 7 | "github.com/renproject/mpc/params" 8 | "github.com/renproject/secp256k1" 9 | "github.com/renproject/shamir" 10 | ) 11 | 12 | // A MulOpener is a state machine that implements the multiply and open 13 | // protocol. 14 | type MulOpener struct { 15 | shareBufs []shamir.Shares 16 | 17 | batchSize, k uint32 18 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch []shamir.Commitment 19 | 20 | indices []secp256k1.Fn 21 | h secp256k1.Point 22 | } 23 | 24 | // New returns a new MulOpener state machine along with the initial message 25 | // that is to be broadcast to the other parties. The state machine will handle 26 | // this message before being returned. 27 | func New( 28 | aShareBatch, bShareBatch, rzgShareBatch shamir.VerifiableShares, 29 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch []shamir.Commitment, 30 | indices []secp256k1.Fn, h secp256k1.Point, 31 | ) (MulOpener, []Message) { 32 | if !params.ValidPedersenParameter(h) { 33 | panic("insecure choice of pedersen parameter") 34 | } 35 | batchSize := len(aShareBatch) 36 | if batchSize < 1 { 37 | panic(fmt.Sprintf("batch size should be at least 1: got %v", batchSize)) 38 | } 39 | if len(bShareBatch) != batchSize || 40 | len(rzgShareBatch) != batchSize || 41 | len(aCommitmentBatch) != batchSize || 42 | len(bCommitmentBatch) != batchSize || 43 | len(rzgCommitmentBatch) != batchSize { 44 | panic("inconsistent batch size") 45 | } 46 | k := aCommitmentBatch[0].Len() 47 | if k < 2 { 48 | panic(fmt.Sprintf("k should be at least 2: got %v", k)) 49 | } 50 | for i := 0; i < batchSize; i++ { 51 | if aCommitmentBatch[i].Len() != k || bCommitmentBatch[i].Len() != k { 52 | panic("inconsistent threshold (k)") 53 | } 54 | } 55 | for _, com := range rzgCommitmentBatch { 56 | if com.Len() != 2*k-1 { 57 | panic(fmt.Sprintf("incorrect rzg k: expected 2*%v-1 = %v, got %v", k, 2*k-1, com.Len())) 58 | } 59 | } 60 | 61 | index := aShareBatch[0].Share.Index 62 | for _, aShare := range aShareBatch { 63 | if !aShare.Share.Index.Eq(&index) { 64 | panic(fmt.Sprintf("incorrect a_index: expected %v, got %v", index, aShare.Share.Index)) 65 | } 66 | } 67 | for _, bShare := range bShareBatch { 68 | if !bShare.Share.Index.Eq(&index) { 69 | panic(fmt.Sprintf("incorrect b_index: expected %v, got %v", index, bShare.Share.Index)) 70 | } 71 | } 72 | for _, rzgShare := range rzgShareBatch { 73 | if !rzgShare.Share.Index.Eq(&index) { 74 | panic(fmt.Sprintf("incorrect z_index: expected %v, got %v", index, rzgShare.Share.Index)) 75 | } 76 | } 77 | 78 | shareBufs := make([]shamir.Shares, batchSize) 79 | for i := range shareBufs { 80 | shareBufs[i] = make(shamir.Shares, 0, k) 81 | } 82 | 83 | mulopener := MulOpener{ 84 | shareBufs: shareBufs, 85 | batchSize: uint32(batchSize), 86 | k: uint32(2*k - 1), 87 | aCommitmentBatch: aCommitmentBatch, 88 | bCommitmentBatch: bCommitmentBatch, 89 | rzgCommitmentBatch: rzgCommitmentBatch, 90 | indices: indices, 91 | h: h, 92 | } 93 | 94 | var product secp256k1.Fn 95 | messageBatch := make([]Message, batchSize) 96 | for i := 0; i < batchSize; i++ { 97 | product.Mul(&aShareBatch[i].Share.Value, &bShareBatch[i].Share.Value) 98 | tau := secp256k1.RandomFn() 99 | aShareCommitment := pedersenCommit(&aShareBatch[i].Share.Value, &aShareBatch[i].Decommitment, &h) 100 | bShareCommitment := pedersenCommit(&bShareBatch[i].Share.Value, &bShareBatch[i].Decommitment, &h) 101 | productShareCommitment := pedersenCommit(&product, &tau, &h) 102 | proof := mulzkp.CreateProof(&h, &aShareCommitment, &bShareCommitment, &productShareCommitment, 103 | aShareBatch[i].Share.Value, bShareBatch[i].Share.Value, 104 | aShareBatch[i].Decommitment, bShareBatch[i].Decommitment, tau, 105 | ) 106 | share := shamir.VerifiableShare{ 107 | Share: shamir.Share{ 108 | Index: index, 109 | Value: product, 110 | }, 111 | Decommitment: tau, 112 | } 113 | share.Add(&share, &rzgShareBatch[i]) 114 | messageBatch[i] = Message{ 115 | VShare: share, 116 | Commitment: productShareCommitment, 117 | Proof: proof, 118 | } 119 | } 120 | 121 | // Handle own message immediately. 122 | output, err := mulopener.HandleShareBatch(messageBatch) 123 | if output != nil { 124 | panic("unexpected result handling own message") 125 | } else if err != nil { 126 | panic(fmt.Sprintf("unexpected result handling own message: %v", err)) 127 | } 128 | 129 | return mulopener, messageBatch 130 | } 131 | 132 | // HandleShareBatch applies a state transition upon receiveing the given shares 133 | // from another party during the open in the multiply and open protocol. Once 134 | // enough valid shares have been received to reconstruct, the output, i.e. the 135 | // product of the two input secrets, is computed and returned. If not enough 136 | // shares have been received, the return value will be nil. If the message 137 | // batch id invalid in any way, an error will be returned along with a nil 138 | // value. 139 | func (mulopener *MulOpener) HandleShareBatch(messageBatch []Message) ([]secp256k1.Fn, error) { 140 | if uint32(len(messageBatch)) != mulopener.batchSize { 141 | return nil, ErrIncorrectBatchSize 142 | } 143 | index := messageBatch[0].VShare.Share.Index 144 | { 145 | exists := false 146 | for i := range mulopener.indices { 147 | if index.Eq(&mulopener.indices[i]) { 148 | exists = true 149 | break 150 | } 151 | } 152 | if !exists { 153 | return nil, ErrInvalidIndex 154 | } 155 | } 156 | for i := range messageBatch { 157 | if !messageBatch[i].VShare.Share.IndexEq(&index) { 158 | return nil, ErrInconsistentShares 159 | } 160 | } 161 | for _, s := range mulopener.shareBufs[0] { 162 | if s.IndexEq(&index) { 163 | return nil, ErrDuplicateIndex 164 | } 165 | } 166 | 167 | for i := uint32(0); i < mulopener.batchSize; i++ { 168 | aShareCommitment := polyEvalPoint(mulopener.aCommitmentBatch[i], index) 169 | bShareCommitment := polyEvalPoint(mulopener.bCommitmentBatch[i], index) 170 | if !mulzkp.Verify( 171 | &mulopener.h, &aShareCommitment, &bShareCommitment, &messageBatch[i].Commitment, 172 | &messageBatch[i].Proof, 173 | ) { 174 | return nil, ErrInvalidZKP 175 | } 176 | var shareCommitment secp256k1.Point 177 | rzgShareCommitment := polyEvalPoint(mulopener.rzgCommitmentBatch[i], index) 178 | shareCommitment.Add(&messageBatch[i].Commitment, &rzgShareCommitment) 179 | 180 | com := pedersenCommit( 181 | &messageBatch[i].VShare.Share.Value, &messageBatch[i].VShare.Decommitment, 182 | &mulopener.h, 183 | ) 184 | if !shareCommitment.Eq(&com) { 185 | return nil, ErrInvalidShares 186 | } 187 | } 188 | 189 | // Shares are valid so we add them to the buffers. 190 | for i := range mulopener.shareBufs { 191 | mulopener.shareBufs[i] = append(mulopener.shareBufs[i], messageBatch[i].VShare.Share) 192 | } 193 | 194 | // If we have enough shares, reconstruct. 195 | if uint32(len(mulopener.shareBufs[0])) == mulopener.k { 196 | secrets := make([]secp256k1.Fn, mulopener.batchSize) 197 | for i, buf := range mulopener.shareBufs { 198 | secrets[i] = shamir.Open(buf) 199 | } 200 | return secrets, nil 201 | } 202 | 203 | return nil, nil 204 | } 205 | 206 | // TODO: This should probably be a function inside the shamir package. 207 | func polyEvalPoint(commitment shamir.Commitment, index secp256k1.Fn) secp256k1.Point { 208 | var acc secp256k1.Point 209 | acc = commitment[len(commitment)-1] 210 | for l := len(commitment) - 2; l >= 0; l-- { 211 | acc.Scale(&acc, &index) 212 | acc.Add(&acc, &commitment[l]) 213 | } 214 | return acc 215 | } 216 | 217 | // TODO: This should probably be a function inside the shamir package. 218 | func pedersenCommit(value, decommitment *secp256k1.Fn, h *secp256k1.Point) secp256k1.Point { 219 | var commitment, hPow secp256k1.Point 220 | commitment.BaseExp(value) 221 | hPow.Scale(h, decommitment) 222 | commitment.Add(&commitment, &hPow) 223 | return commitment 224 | } 225 | -------------------------------------------------------------------------------- /mulopen/mulopen_suite_test.go: -------------------------------------------------------------------------------- 1 | package mulopen_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestMulopen(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Mulopen Suite") 13 | } 14 | -------------------------------------------------------------------------------- /mulopen/mulopenutil/machine.go: -------------------------------------------------------------------------------- 1 | package mulopenutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/mpc/mulopen" 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // Machine represents a player that honestly carries out the multiply and open 12 | // protocol. 13 | type Machine struct { 14 | OwnID mpcutil.ID 15 | mulopen.MulOpener 16 | InitMsgs []Message 17 | Output []secp256k1.Fn 18 | } 19 | 20 | // NewMachine constructs a new honest machine for a multiply and open network 21 | // test. It will have the given inputs and ID. 22 | func NewMachine( 23 | aShareBatch, bShareBatch, rzgShareBatch shamir.VerifiableShares, 24 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch []shamir.Commitment, 25 | ids []mpcutil.ID, ownID mpcutil.ID, indices []secp256k1.Fn, h secp256k1.Point, 26 | ) Machine { 27 | mulopener, msgs := mulopen.New( 28 | aShareBatch, bShareBatch, rzgShareBatch, 29 | aCommitmentBatch, bCommitmentBatch, rzgCommitmentBatch, 30 | indices, h, 31 | ) 32 | initialMessages := make([]Message, 0, len(ids)-1) 33 | for _, id := range ids { 34 | if id == ownID { 35 | continue 36 | } 37 | initialMessages = append(initialMessages, Message{ 38 | FromID: ownID, 39 | ToID: id, 40 | Messages: msgs, 41 | }) 42 | } 43 | return Machine{ 44 | OwnID: ownID, 45 | MulOpener: mulopener, 46 | InitMsgs: initialMessages, 47 | } 48 | } 49 | 50 | // SizeHint implements the surge.SizeHinter interface. 51 | func (m Machine) SizeHint() int { 52 | return m.OwnID.SizeHint() + 53 | m.MulOpener.SizeHint() + 54 | surge.SizeHint(m.InitMsgs) + 55 | surge.SizeHint(m.Output) 56 | } 57 | 58 | // Marshal implements the surge.Marshaler interface. 59 | func (m Machine) Marshal(buf []byte, rem int) ([]byte, int, error) { 60 | buf, rem, err := m.OwnID.Marshal(buf, rem) 61 | if err != nil { 62 | return buf, rem, err 63 | } 64 | buf, rem, err = m.MulOpener.Marshal(buf, rem) 65 | if err != nil { 66 | return buf, rem, err 67 | } 68 | buf, rem, err = surge.Marshal(m.InitMsgs, buf, rem) 69 | if err != nil { 70 | return buf, rem, err 71 | } 72 | return surge.Marshal(m.Output, buf, rem) 73 | } 74 | 75 | // Unmarshal implements the surge.Unmarshaler interface. 76 | func (m *Machine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 77 | buf, rem, err := m.OwnID.Unmarshal(buf, rem) 78 | if err != nil { 79 | return buf, rem, err 80 | } 81 | buf, rem, err = m.MulOpener.Unmarshal(buf, rem) 82 | if err != nil { 83 | return buf, rem, err 84 | } 85 | buf, rem, err = surge.Unmarshal(&m.InitMsgs, buf, rem) 86 | if err != nil { 87 | return buf, rem, err 88 | } 89 | return surge.Unmarshal(&m.Output, buf, rem) 90 | } 91 | 92 | // ID implements the Machine interface. 93 | func (m Machine) ID() mpcutil.ID { return m.OwnID } 94 | 95 | // InitialMessages implements the Machine interface. 96 | func (m Machine) InitialMessages() []mpcutil.Message { 97 | msgs := make([]mpcutil.Message, len(m.InitMsgs)) 98 | for i := range m.InitMsgs { 99 | msgs[i] = &m.InitMsgs[i] 100 | } 101 | return msgs 102 | } 103 | 104 | // Handle implements the Machine interface. 105 | func (m *Machine) Handle(msg mpcutil.Message) []mpcutil.Message { 106 | output, _ := m.MulOpener.HandleShareBatch(msg.(*Message).Messages) 107 | if output != nil { 108 | m.Output = output 109 | } 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /mulopen/mulopenutil/message.go: -------------------------------------------------------------------------------- 1 | package mulopenutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/mpc/mulopen" 6 | "github.com/renproject/surge" 7 | ) 8 | 9 | // Message is the message type that players send to eachother during an 10 | // instance of multiply and open. 11 | type Message struct { 12 | FromID, ToID mpcutil.ID 13 | Messages []mulopen.Message 14 | } 15 | 16 | // From implements the mpcutil.Message interface. 17 | func (msg Message) From() mpcutil.ID { return msg.FromID } 18 | 19 | // To implements the mpcutil.Message interface. 20 | func (msg Message) To() mpcutil.ID { return msg.ToID } 21 | 22 | // SizeHint implements the surge.SizeHinter interface. 23 | func (msg Message) SizeHint() int { 24 | return msg.FromID.SizeHint() + 25 | msg.ToID.SizeHint() + 26 | surge.SizeHint(msg.Messages) 27 | } 28 | 29 | // Marshal implements the surge.Marshaler interface. 30 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 31 | buf, rem, err := msg.FromID.Marshal(buf, rem) 32 | if err != nil { 33 | return buf, rem, err 34 | } 35 | buf, rem, err = msg.ToID.Marshal(buf, rem) 36 | if err != nil { 37 | return buf, rem, err 38 | } 39 | return surge.Marshal(msg.Messages, buf, rem) 40 | } 41 | 42 | // Unmarshal implements the surge.Unmarshaler interface. 43 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 44 | buf, rem, err := msg.FromID.Unmarshal(buf, rem) 45 | if err != nil { 46 | return buf, rem, err 47 | } 48 | buf, rem, err = msg.ToID.Unmarshal(buf, rem) 49 | if err != nil { 50 | return buf, rem, err 51 | } 52 | return surge.Unmarshal(&msg.Messages, buf, rem) 53 | } 54 | -------------------------------------------------------------------------------- /mulopen/mulzkp/marshal_test.go: -------------------------------------------------------------------------------- 1 | package mulzkp_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen/mulzkp" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | t := reflect.TypeOf(mulzkp.Proof{}) 17 | 18 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 19 | It("should be the same after marshalling and unmarshalling", func() { 20 | for i := 0; i < trials; i++ { 21 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 22 | } 23 | }) 24 | 25 | It("should not panic when fuzzing", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 28 | } 29 | }) 30 | 31 | Context("marshalling", func() { 32 | It("should return an error when the buffer is too small", func() { 33 | for i := 0; i < trials; i++ { 34 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 35 | } 36 | }) 37 | 38 | It("should return an error when the memory quota is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | }) 44 | 45 | Context("unmarshalling", func() { 46 | It("should return an error when the buffer is too small", func() { 47 | for i := 0; i < trials; i++ { 48 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 49 | } 50 | }) 51 | 52 | It("should return an error when the memory quota is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /mulopen/mulzkp/mulzkp.go: -------------------------------------------------------------------------------- 1 | // Package mulzkp provides an implementation of the ZKP for the multiplication 2 | // of Pedersen commited values described in Appendix C of [1], augmented to be 3 | // non interactive by using the Fiat Shamir transform. 4 | // 5 | // [1] Rosario Gennaro, Michael O. Rabin, and Tal Rabin. 1998. 6 | // Simplified VSS and fast-track multiparty computations with applications to 7 | // threshold cryptography. 8 | // In Proceedings of the seventeenth annual ACM symposium on Principles of 9 | // distributed computing (PODC ’98). Association for Computing Machinery, New 10 | // York, NY, USA, 101–111. 11 | // https://doi.org/10.1145/277697.277716 12 | package mulzkp 13 | 14 | import ( 15 | "crypto/sha256" 16 | 17 | "github.com/renproject/mpc/mulopen/mulzkp/zkp" 18 | "github.com/renproject/secp256k1" 19 | ) 20 | 21 | // CreateProof constructs a new ZKP that attests to the fact that 22 | // c = (alpha*beta)G + (tau)H, 23 | // where 24 | // a = (alpha)G + (rho)H, and 25 | // b = (beta)G + (sigma)H. 26 | func CreateProof(h, a, b, c *secp256k1.Point, alpha, beta, rho, sigma, tau secp256k1.Fn) Proof { 27 | msg, w := zkp.New(h, b, alpha, beta, rho, sigma, tau) 28 | e := computeChallenge(a, b, c, &msg) 29 | res := zkp.ResponseForChallenge(&w, &e) 30 | 31 | return Proof{msg, res} 32 | } 33 | 34 | // Verify the given proof. The return value will be true if 35 | // c = (alpha*beta)G + (tau)H, 36 | // where 37 | // a = (alpha)G + (rho)H, and 38 | // b = (beta)G + (sigma)H 39 | // for some alpha, beta, rho, sigma, tau. Otherwise, the return value will be 40 | // false. 41 | func Verify(h, a, b, c *secp256k1.Point, p *Proof) bool { 42 | e := computeChallenge(a, b, c, &p.msg) 43 | return zkp.Verify(h, a, b, c, &p.msg, &p.res, &e) 44 | } 45 | 46 | func computeChallenge(a, b, c *secp256k1.Point, msg *zkp.Message) secp256k1.Fn { 47 | l := a.SizeHint() + b.SizeHint() + c.SizeHint() + msg.SizeHint() 48 | buf := make([]byte, l) 49 | 50 | var tail []byte 51 | rem := l 52 | var err error 53 | 54 | tail, rem, err = a.Marshal(buf, rem) 55 | if err != nil { 56 | panic("unreachable") 57 | } 58 | tail, rem, err = b.Marshal(tail, rem) 59 | if err != nil { 60 | panic("unreachable") 61 | } 62 | tail, rem, err = c.Marshal(tail, rem) 63 | if err != nil { 64 | panic("unreachable") 65 | } 66 | tail, rem, err = msg.Marshal(tail, rem) 67 | if err != nil { 68 | panic("unreachable") 69 | } 70 | hash := sha256.Sum256(buf) 71 | 72 | var e secp256k1.Fn 73 | _ = e.SetB32(hash[:]) 74 | return e 75 | } 76 | -------------------------------------------------------------------------------- /mulopen/mulzkp/mulzkp_suite_test.go: -------------------------------------------------------------------------------- 1 | package mulzkp_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestMulZkp(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "MulZkp Suite") 13 | } 14 | -------------------------------------------------------------------------------- /mulopen/mulzkp/mulzkp_test.go: -------------------------------------------------------------------------------- 1 | package mulzkp_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | . "github.com/renproject/mpc/mulopen/mulzkp" 7 | 8 | "github.com/renproject/secp256k1" 9 | ) 10 | 11 | var _ = Describe("NIZK", func() { 12 | trials := 100 13 | 14 | RandomTestParams := func() ( 15 | secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, 16 | secp256k1.Point, secp256k1.Point, secp256k1.Point, 17 | ) { 18 | h := secp256k1.RandomPoint() 19 | 20 | alpha := secp256k1.RandomFn() 21 | beta := secp256k1.RandomFn() 22 | rho := secp256k1.RandomFn() 23 | sigma := secp256k1.RandomFn() 24 | tau := secp256k1.RandomFn() 25 | 26 | var a, b, hPow secp256k1.Point 27 | hPow.Scale(&h, &rho) 28 | a.BaseExp(&alpha) 29 | a.Add(&a, &hPow) 30 | 31 | hPow.Scale(&h, &sigma) 32 | b.BaseExp(&beta) 33 | b.Add(&b, &hPow) 34 | 35 | return alpha, beta, rho, sigma, tau, a, b, h 36 | } 37 | 38 | RandomCorrectC := func(alpha, beta, tau secp256k1.Fn, h secp256k1.Point) secp256k1.Point { 39 | var c, hPow secp256k1.Point 40 | var tmp secp256k1.Fn 41 | hPow.Scale(&h, &tau) 42 | tmp.Mul(&alpha, &beta) 43 | c.BaseExp(&tmp) 44 | c.Add(&c, &hPow) 45 | return c 46 | } 47 | 48 | Context("verifying proofs", func() { 49 | It("should accept correct proofs", func() { 50 | for i := 0; i < trials; i++ { 51 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 52 | c := RandomCorrectC(alpha, beta, tau, h) 53 | 54 | proof := CreateProof(&h, &a, &b, &c, alpha, beta, rho, sigma, tau) 55 | Expect(Verify(&h, &a, &b, &c, &proof)).To(BeTrue()) 56 | } 57 | }) 58 | 59 | It("should reject incorrect proofs", func() { 60 | for i := 0; i < trials; i++ { 61 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 62 | c := secp256k1.RandomPoint() 63 | 64 | proof := CreateProof(&h, &a, &b, &c, alpha, beta, rho, sigma, tau) 65 | Expect(Verify(&h, &a, &b, &c, &proof)).To(BeFalse()) 66 | } 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /mulopen/mulzkp/proof.go: -------------------------------------------------------------------------------- 1 | package mulzkp 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen/mulzkp/zkp" 8 | ) 9 | 10 | // A Proof for the ZKP. 11 | type Proof struct { 12 | msg zkp.Message 13 | res zkp.Response 14 | } 15 | 16 | // SizeHint implements the surge.SizeHinter interface. 17 | func (p Proof) SizeHint() int { return p.msg.SizeHint() + p.res.SizeHint() } 18 | 19 | // Marshal implements the surge.Marshaler interface. 20 | func (p Proof) Marshal(buf []byte, rem int) ([]byte, int, error) { 21 | buf, rem, err := p.msg.Marshal(buf, rem) 22 | if err != nil { 23 | return buf, rem, err 24 | } 25 | return p.res.Marshal(buf, rem) 26 | } 27 | 28 | // Unmarshal implements the surge.Unmarshaler interface. 29 | func (p *Proof) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 30 | buf, rem, err := p.msg.Unmarshal(buf, rem) 31 | if err != nil { 32 | return buf, rem, err 33 | } 34 | return p.res.Unmarshal(buf, rem) 35 | } 36 | 37 | // Generate implements the quick.Generator interface. 38 | func (p Proof) Generate(r *rand.Rand, size int) reflect.Value { 39 | msg := zkp.Message{}.Generate(r, size).Interface().(zkp.Message) 40 | res := zkp.Response{}.Generate(r, size).Interface().(zkp.Response) 41 | return reflect.ValueOf(Proof{ 42 | msg, 43 | res, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/marshal_test.go: -------------------------------------------------------------------------------- 1 | package zkp_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/mulopen/mulzkp/zkp" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | ts := []reflect.Type{ 17 | reflect.TypeOf(zkp.Message{}), 18 | reflect.TypeOf(zkp.Response{}), 19 | } 20 | 21 | for _, t := range ts { 22 | t := t 23 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 24 | It("should be the same after marshalling and unmarshalling", func() { 25 | for i := 0; i < trials; i++ { 26 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 27 | } 28 | }) 29 | 30 | It("should not panic when fuzzing", func() { 31 | for i := 0; i < trials; i++ { 32 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 33 | } 34 | }) 35 | 36 | Context("marshalling", func() { 37 | It("should return an error when the buffer is too small", func() { 38 | for i := 0; i < trials; i++ { 39 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 40 | } 41 | }) 42 | 43 | It("should return an error when the memory quota is too small", func() { 44 | for i := 0; i < trials; i++ { 45 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 46 | } 47 | }) 48 | }) 49 | 50 | Context("unmarshalling", func() { 51 | It("should return an error when the buffer is too small", func() { 52 | for i := 0; i < trials; i++ { 53 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 54 | } 55 | }) 56 | 57 | It("should return an error when the memory quota is too small", func() { 58 | for i := 0; i < trials; i++ { 59 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 60 | } 61 | }) 62 | }) 63 | }) 64 | } 65 | }) 66 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/message.go: -------------------------------------------------------------------------------- 1 | package zkp 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | ) 9 | 10 | // The Message that is initially sent in the ZKP. 11 | type Message struct { 12 | m, m1, m2 secp256k1.Point 13 | } 14 | 15 | // SizeHint implements the surge.SizeHinter interface. 16 | func (msg Message) SizeHint() int { 17 | return msg.m.SizeHint() + msg.m1.SizeHint() + msg.m2.SizeHint() 18 | } 19 | 20 | // Marshal implements the surge.Marshaler interface. 21 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 22 | buf, rem, err := msg.m.Marshal(buf, rem) 23 | if err != nil { 24 | return buf, rem, err 25 | } 26 | 27 | buf, rem, err = msg.m1.Marshal(buf, rem) 28 | if err != nil { 29 | return buf, rem, err 30 | } 31 | 32 | return msg.m2.Marshal(buf, rem) 33 | } 34 | 35 | // Unmarshal implements the surge.UnUnmarshaler interface. 36 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 37 | buf, rem, err := msg.m.Unmarshal(buf, rem) 38 | if err != nil { 39 | return buf, rem, err 40 | } 41 | 42 | buf, rem, err = msg.m1.Unmarshal(buf, rem) 43 | if err != nil { 44 | return buf, rem, err 45 | } 46 | 47 | return msg.m2.Unmarshal(buf, rem) 48 | } 49 | 50 | // Generate implements the quick.Generator interface. 51 | func (msg Message) Generate(_ *rand.Rand, _ int) reflect.Value { 52 | m := Message{ 53 | m: secp256k1.RandomPoint(), 54 | m1: secp256k1.RandomPoint(), 55 | m2: secp256k1.RandomPoint(), 56 | } 57 | return reflect.ValueOf(m) 58 | } 59 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/response.go: -------------------------------------------------------------------------------- 1 | package zkp 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | ) 9 | 10 | // The Response for a challenge in the ZKP. 11 | type Response struct { 12 | y, w, z, w1, w2 secp256k1.Fn 13 | } 14 | 15 | // SizeHint implements the surge.SizeHinter interface. 16 | func (res Response) SizeHint() int { 17 | return res.y.SizeHint() + 18 | res.w.SizeHint() + 19 | res.z.SizeHint() + 20 | res.w1.SizeHint() + 21 | res.w2.SizeHint() 22 | } 23 | 24 | // Marshal implements the surge.Marshaler interface. 25 | func (res Response) Marshal(buf []byte, rem int) ([]byte, int, error) { 26 | buf, rem, err := res.y.Marshal(buf, rem) 27 | if err != nil { 28 | return buf, rem, err 29 | } 30 | buf, rem, err = res.w.Marshal(buf, rem) 31 | if err != nil { 32 | return buf, rem, err 33 | } 34 | buf, rem, err = res.z.Marshal(buf, rem) 35 | if err != nil { 36 | return buf, rem, err 37 | } 38 | buf, rem, err = res.w1.Marshal(buf, rem) 39 | if err != nil { 40 | return buf, rem, err 41 | } 42 | return res.w2.Marshal(buf, rem) 43 | } 44 | 45 | // Unmarshal implements the surge.Unmarshaler interface. 46 | func (res *Response) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 47 | buf, rem, err := res.y.Unmarshal(buf, rem) 48 | if err != nil { 49 | return buf, rem, err 50 | } 51 | buf, rem, err = res.w.Unmarshal(buf, rem) 52 | if err != nil { 53 | return buf, rem, err 54 | } 55 | buf, rem, err = res.z.Unmarshal(buf, rem) 56 | if err != nil { 57 | return buf, rem, err 58 | } 59 | buf, rem, err = res.w1.Unmarshal(buf, rem) 60 | if err != nil { 61 | return buf, rem, err 62 | } 63 | return res.w2.Unmarshal(buf, rem) 64 | } 65 | 66 | // Generate implements the quick.Generator interface. 67 | func (res Response) Generate(_ *rand.Rand, _ int) reflect.Value { 68 | r := Response{ 69 | y: secp256k1.RandomFn(), 70 | w: secp256k1.RandomFn(), 71 | z: secp256k1.RandomFn(), 72 | w1: secp256k1.RandomFn(), 73 | w2: secp256k1.RandomFn(), 74 | } 75 | return reflect.ValueOf(r) 76 | } 77 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/witness.go: -------------------------------------------------------------------------------- 1 | package zkp 2 | 3 | import "github.com/renproject/secp256k1" 4 | 5 | // The Witness for the ZKP. 6 | type Witness struct { 7 | d, s, x, s1, s2 secp256k1.Fn 8 | alpha, beta, rho, sigma, tau secp256k1.Fn 9 | } 10 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/zkp.go: -------------------------------------------------------------------------------- 1 | // Package zkp provides an implementation of the ZKP for the multiplication of 2 | // Pedersen commited values described in Appendix C of [1]. 3 | // 4 | // [1] Rosario Gennaro, Michael O. Rabin, and Tal Rabin. 1998. 5 | // Simplified VSS and fast-track multiparty computations with applications to 6 | // threshold cryptography. 7 | // In Proceedings of the seventeenth annual ACM symposium on Principles of 8 | // distributed computing (PODC ’98). Association for Computing Machinery, New 9 | // York, NY, USA, 101–111. 10 | // https://doi.org/10.1145/277697.277716 11 | package zkp 12 | 13 | import "github.com/renproject/secp256k1" 14 | 15 | // New constructs a new message and witness for the ZKP for the given 16 | // parameters. 17 | func New(h, b *secp256k1.Point, alpha, beta, rho, sigma, tau secp256k1.Fn) (Message, Witness) { 18 | msg := Message{} 19 | w := Witness{ 20 | d: secp256k1.RandomFn(), 21 | s: secp256k1.RandomFn(), 22 | x: secp256k1.RandomFn(), 23 | s1: secp256k1.RandomFn(), 24 | s2: secp256k1.RandomFn(), 25 | 26 | alpha: alpha, 27 | beta: beta, 28 | rho: rho, 29 | sigma: sigma, 30 | tau: tau, 31 | } 32 | 33 | var hPow secp256k1.Point 34 | 35 | hPow.Scale(h, &w.s) 36 | msg.m.BaseExp(&w.d) 37 | msg.m.Add(&msg.m, &hPow) 38 | 39 | hPow.Scale(h, &w.s1) 40 | msg.m1.BaseExp(&w.x) 41 | msg.m1.Add(&msg.m1, &hPow) 42 | 43 | hPow.Scale(h, &w.s2) 44 | msg.m2.Scale(b, &w.x) 45 | msg.m2.Add(&msg.m2, &hPow) 46 | 47 | return msg, w 48 | } 49 | 50 | // ResponseForChallenge constructs a valid response for the given challenge and 51 | // witness. 52 | func ResponseForChallenge(w *Witness, e *secp256k1.Fn) Response { 53 | var res Response 54 | 55 | res.y.Mul(e, &w.beta) 56 | res.y.Add(&res.y, &w.d) 57 | 58 | res.w.Mul(e, &w.sigma) 59 | res.w.Add(&res.w, &w.s) 60 | 61 | res.z.Mul(e, &w.alpha) 62 | res.z.Add(&res.z, &w.x) 63 | 64 | res.w1.Mul(e, &w.rho) 65 | res.w1.Add(&res.w1, &w.s1) 66 | 67 | res.w2.Mul(&w.sigma, &w.alpha) 68 | res.w2.Negate(&res.w2) 69 | res.w2.Add(&res.w2, &w.tau) 70 | res.w2.Mul(&res.w2, e) 71 | res.w2.Add(&res.w2, &w.s2) 72 | 73 | return res 74 | } 75 | 76 | // Verify returns true if the given message, challenge and response are valid 77 | // for the ZKP, and false otherwise. 78 | func Verify(h, a, b, c *secp256k1.Point, msg *Message, res *Response, e *secp256k1.Fn) bool { 79 | var actual, expected, hPow secp256k1.Point 80 | 81 | expected.BaseExp(&res.y) 82 | hPow.Scale(h, &res.w) 83 | expected.Add(&expected, &hPow) 84 | 85 | actual.Scale(b, e) 86 | actual.Add(&actual, &msg.m) 87 | 88 | if !actual.Eq(&expected) { 89 | return false 90 | } 91 | 92 | expected.BaseExp(&res.z) 93 | hPow.Scale(h, &res.w1) 94 | expected.Add(&expected, &hPow) 95 | 96 | actual.Scale(a, e) 97 | actual.Add(&actual, &msg.m1) 98 | 99 | if !actual.Eq(&expected) { 100 | return false 101 | } 102 | 103 | expected.Scale(b, &res.z) 104 | hPow.Scale(h, &res.w2) 105 | expected.Add(&expected, &hPow) 106 | 107 | actual.Scale(c, e) 108 | actual.Add(&actual, &msg.m2) 109 | 110 | if !actual.Eq(&expected) { 111 | return false 112 | } 113 | 114 | return true 115 | } 116 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/zkp_suite_test.go: -------------------------------------------------------------------------------- 1 | package zkp_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestMulZkp(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Zkp Suite") 13 | } 14 | -------------------------------------------------------------------------------- /mulopen/mulzkp/zkp/zkp_test.go: -------------------------------------------------------------------------------- 1 | package zkp_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | . "github.com/renproject/mpc/mulopen/mulzkp/zkp" 7 | 8 | "github.com/renproject/secp256k1" 9 | ) 10 | 11 | var _ = Describe("ZKP", func() { 12 | trials := 100 13 | 14 | RandomTestParams := func() ( 15 | secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, secp256k1.Fn, 16 | secp256k1.Point, secp256k1.Point, secp256k1.Point, 17 | ) { 18 | h := secp256k1.RandomPoint() 19 | 20 | alpha := secp256k1.RandomFn() 21 | beta := secp256k1.RandomFn() 22 | rho := secp256k1.RandomFn() 23 | sigma := secp256k1.RandomFn() 24 | tau := secp256k1.RandomFn() 25 | 26 | var a, b, hPow secp256k1.Point 27 | hPow.Scale(&h, &rho) 28 | a.BaseExp(&alpha) 29 | a.Add(&a, &hPow) 30 | 31 | hPow.Scale(&h, &sigma) 32 | b.BaseExp(&beta) 33 | b.Add(&b, &hPow) 34 | 35 | return alpha, beta, rho, sigma, tau, a, b, h 36 | } 37 | 38 | RandomCorrectC := func(alpha, beta, tau secp256k1.Fn, h secp256k1.Point) secp256k1.Point { 39 | var c, hPow secp256k1.Point 40 | var tmp secp256k1.Fn 41 | hPow.Scale(&h, &tau) 42 | tmp.Mul(&alpha, &beta) 43 | c.BaseExp(&tmp) 44 | c.Add(&c, &hPow) 45 | return c 46 | } 47 | 48 | Context("correct proofs", func() { 49 | It("should verify correct proofs", func() { 50 | var e secp256k1.Fn 51 | var msg Message 52 | var w Witness 53 | var res Response 54 | 55 | for i := 0; i < trials; i++ { 56 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 57 | c := RandomCorrectC(alpha, beta, tau, h) 58 | 59 | msg, w = New(&h, &b, alpha, beta, rho, sigma, tau) 60 | e = secp256k1.RandomFn() 61 | res = ResponseForChallenge(&w, &e) 62 | 63 | Expect(Verify(&h, &a, &b, &c, &msg, &res, &e)).To(BeTrue()) 64 | } 65 | }) 66 | }) 67 | 68 | Context("incorrect proofs", func() { 69 | It("should identify when the commitment is not to the product", func() { 70 | var e secp256k1.Fn 71 | var msg Message 72 | var w Witness 73 | var res Response 74 | 75 | var c, hPow secp256k1.Point 76 | var tmp secp256k1.Fn 77 | 78 | for i := 0; i < trials; i++ { 79 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 80 | hPow.ScaleUnsafe(&h, &tau) 81 | tmp = secp256k1.RandomFn() // Pick exponent not equal to alpha * beta 82 | c.BaseExpUnsafe(&tmp) 83 | c.AddUnsafe(&c, &hPow) 84 | 85 | msg, w = New(&h, &b, alpha, beta, rho, sigma, tau) 86 | e = secp256k1.RandomFn() 87 | res = ResponseForChallenge(&w, &e) 88 | 89 | Expect(Verify(&h, &a, &b, &c, &msg, &res, &e)).To(BeFalse()) 90 | } 91 | }) 92 | 93 | It("should identify when the commitment to alpha is modified", func() { 94 | var e secp256k1.Fn 95 | var msg Message 96 | var w Witness 97 | var res Response 98 | 99 | for i := 0; i < trials; i++ { 100 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 101 | c := RandomCorrectC(alpha, beta, tau, h) 102 | 103 | msg, w = New(&h, &b, alpha, beta, rho, sigma, tau) 104 | e = secp256k1.RandomFn() 105 | res = ResponseForChallenge(&w, &e) 106 | 107 | a = secp256k1.RandomPoint() 108 | 109 | Expect(Verify(&h, &a, &b, &c, &msg, &res, &e)).To(BeFalse()) 110 | } 111 | }) 112 | 113 | It("should identify when the commitment to beta is modified", func() { 114 | var e secp256k1.Fn 115 | var msg Message 116 | var w Witness 117 | var res Response 118 | 119 | for i := 0; i < trials; i++ { 120 | alpha, beta, rho, sigma, tau, a, b, h := RandomTestParams() 121 | c := RandomCorrectC(alpha, beta, tau, h) 122 | 123 | msg, w = New(&h, &b, alpha, beta, rho, sigma, tau) 124 | e = secp256k1.RandomFn() 125 | res = ResponseForChallenge(&w, &e) 126 | 127 | b = secp256k1.RandomPoint() 128 | 129 | Expect(Verify(&h, &a, &b, &c, &msg, &res, &e)).To(BeFalse()) 130 | } 131 | }) 132 | }) 133 | }) 134 | -------------------------------------------------------------------------------- /open/err.go: -------------------------------------------------------------------------------- 1 | package open 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrDuplicateIndex signifies that the received share has an index that is 7 | // the same as the index of one of the shares that is already in the list 8 | // of valid shares received for the current sharing instance. 9 | ErrDuplicateIndex = errors.New("duplicate index") 10 | 11 | // ErrIndexOutOfRange signifies that the received share has an index that 12 | // is not in the set of indices that the state machine was constructed 13 | // with. 14 | ErrIndexOutOfRange = errors.New("index out of range") 15 | 16 | // ErrInvalidShares signifies that at least one out of the received shares 17 | // is not valid with respect to the commitment for the current sharing 18 | // instance. 19 | ErrInvalidShares = errors.New("invalid shares") 20 | 21 | // ErrIncorrectBatchSize signifies that the batch size of the received 22 | // shares is different to that specified by the opener instance. 23 | ErrIncorrectBatchSize = errors.New("incorrect batch size") 24 | ) 25 | -------------------------------------------------------------------------------- /open/marshal.go: -------------------------------------------------------------------------------- 1 | package open 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "reflect" 7 | 8 | "github.com/renproject/secp256k1" 9 | "github.com/renproject/shamir" 10 | "github.com/renproject/shamir/shamirutil" 11 | "github.com/renproject/surge" 12 | ) 13 | 14 | // Generate implements the quick.Generator interface. 15 | func (opener Opener) Generate(_ *rand.Rand, size int) reflect.Value { 16 | // A curve point is more or less 3 field elements that contain 4 uint64s. 17 | size /= 12 18 | 19 | k := rand.Intn(size) + 1 20 | b := size/k + 1 21 | commitmentBatch := make([]shamir.Commitment, b) 22 | for i := range commitmentBatch { 23 | commitmentBatch[i] = shamir.NewCommitmentWithCapacity(k) 24 | for j := 0; j < k; j++ { 25 | commitmentBatch[i] = append(commitmentBatch[i], secp256k1.RandomPoint()) 26 | } 27 | } 28 | indices := shamirutil.RandomIndices(rand.Intn(20)) 29 | h := secp256k1.RandomPoint() 30 | return reflect.ValueOf(New(commitmentBatch, indices, h)) 31 | } 32 | 33 | // SizeHint implements the surge.SizeHinter interface. 34 | func (opener Opener) SizeHint() int { 35 | return surge.SizeHint(opener.commitmentBatch) + 36 | surge.SizeHint(opener.shareBufs) + 37 | opener.h.SizeHint() + 38 | surge.SizeHint(opener.indices) 39 | } 40 | 41 | // Marshal implements the surge.Marshaler interface. 42 | func (opener Opener) Marshal(buf []byte, rem int) ([]byte, int, error) { 43 | buf, rem, err := surge.Marshal(opener.shareBufs, buf, rem) 44 | if err != nil { 45 | return buf, rem, fmt.Errorf("marshaling share buffers: %v", err) 46 | } 47 | buf, rem, err = surge.Marshal(opener.commitmentBatch, buf, rem) 48 | if err != nil { 49 | return buf, rem, fmt.Errorf("marshaling commitmentBatch: %v", err) 50 | } 51 | buf, rem, err = opener.h.Marshal(buf, rem) 52 | if err != nil { 53 | return buf, rem, fmt.Errorf("marshaling h: %v", err) 54 | } 55 | buf, rem, err = surge.Marshal(opener.indices, buf, rem) 56 | if err != nil { 57 | return buf, rem, fmt.Errorf("marshaling indices: %v", err) 58 | } 59 | return buf, rem, nil 60 | } 61 | 62 | // Unmarshal implements the surge.Unmarshaler interface. 63 | func (opener *Opener) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 64 | buf, rem, err := surge.Unmarshal(&opener.shareBufs, buf, rem) 65 | if err != nil { 66 | return buf, rem, fmt.Errorf("unmarshaling share buffers: %v", err) 67 | } 68 | buf, rem, err = surge.Unmarshal(&opener.commitmentBatch, buf, rem) 69 | if err != nil { 70 | return buf, rem, fmt.Errorf("unmarshaling commitment: %v", err) 71 | } 72 | buf, rem, err = opener.h.Unmarshal(buf, rem) 73 | if err != nil { 74 | return buf, rem, fmt.Errorf("unmarshaling h: %v", err) 75 | } 76 | buf, rem, err = surge.Unmarshal(&opener.indices, buf, rem) 77 | if err != nil { 78 | return buf, rem, fmt.Errorf("unmarshaling indices: %v", err) 79 | } 80 | return buf, rem, nil 81 | } 82 | -------------------------------------------------------------------------------- /open/marshal_test.go: -------------------------------------------------------------------------------- 1 | package open_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/open" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | tys := []reflect.Type{ 17 | reflect.TypeOf(open.Opener{}), 18 | } 19 | 20 | for _, t := range tys { 21 | t := t 22 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 23 | It("should be the same after marshalling and unmarshalling", func() { 24 | for i := 0; i < trials; i++ { 25 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 26 | } 27 | }) 28 | 29 | It("should not panic when fuzzing", func() { 30 | for i := 0; i < trials; i++ { 31 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 32 | } 33 | }) 34 | 35 | Context("marshalling", func() { 36 | It("should return an error when the buffer is too small", func() { 37 | for i := 0; i < trials; i++ { 38 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 39 | } 40 | }) 41 | 42 | It("should return an error when the memory quota is too small", func() { 43 | for i := 0; i < trials; i++ { 44 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 45 | } 46 | }) 47 | }) 48 | 49 | Context("unmarshalling", func() { 50 | It("should return an error when the buffer is too small", func() { 51 | for i := 0; i < trials; i++ { 52 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 53 | } 54 | }) 55 | 56 | It("should return an error when the memory quota is too small", func() { 57 | for i := 0; i < trials; i++ { 58 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 59 | } 60 | }) 61 | }) 62 | }) 63 | } 64 | }) 65 | -------------------------------------------------------------------------------- /open/open.go: -------------------------------------------------------------------------------- 1 | package open 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/mpc/params" 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir" 9 | ) 10 | 11 | // Opener is a state machine that is responsible for opening the secret value 12 | // for a verifiable sharing. An instance of this state machine has a specific 13 | // commitment used for share verification, a specific value of the Pedersen 14 | // parameter (known as "h"), and a specific set of indices for the participating 15 | // players. 16 | // 17 | // The description of the state machine is simple: the state is a buffer of 18 | // shares that have been validated. When a share is received, it is checked 19 | // against the commitment and other parameters, and if it is valid it is added 20 | // to the buffer. Once enough shares have been added to the buffer, the secret 21 | // is opened (the required number of shares is known as "k"). 22 | // 23 | // The state machine supports batching. If several secrets need to be opened at 24 | // once, instead of having multiple state machines, just one can be used and the 25 | // incoming shares are processed in batches. This batching functionality 26 | // requires the secrets to have all been shared using the same parameters, i.e. 27 | // - the number of players and their corresponding indices, 28 | // - the reconstruction threshold (k), 29 | // - and the Pedersen parameter (h). 30 | type Opener struct { 31 | // State 32 | shareBufs []shamir.VerifiableShares 33 | 34 | // Instance parameters 35 | commitmentBatch []shamir.Commitment 36 | 37 | // Global parameters 38 | indices []secp256k1.Fn 39 | h secp256k1.Point 40 | } 41 | 42 | // K returns the number of shares required to open secrets. It assumes that all 43 | // batches require the same number of shares (this assumption is enforced by all 44 | // other methods). 45 | func (opener Opener) K() int { 46 | return opener.commitmentBatch[0].Len() 47 | } 48 | 49 | // BatchSize of the opener. 50 | func (opener Opener) BatchSize() int { 51 | return len(opener.commitmentBatch) 52 | } 53 | 54 | // I returns the current number of valid shares that the opener has received. It 55 | // assumes that all batches contain the same number of shares (this assumption 56 | // is enforced by all other methods). 57 | func (opener Opener) I() int { 58 | return len(opener.shareBufs[0]) 59 | } 60 | 61 | // New returns a new instance of the Opener state machine for the given 62 | // Pedersen commitments for the verifiable sharing(s), indices, and Pedersen 63 | // commitment system parameter. The length of the commitment slice determines 64 | // the batch size. 65 | // 66 | // Panics: This function will panic if any of the following conditions are met. 67 | // - The batch size is less than 1. 68 | // - The reconstruction threshold (k) is less than 1. 69 | // - Not all commitments in the batch of commitments have the same 70 | // reconstruction threshold (k). 71 | func New(commitmentBatch []shamir.Commitment, indices []secp256k1.Fn, h secp256k1.Point) Opener { 72 | if !params.ValidPedersenParameter(h) { 73 | panic("insecure choice of pedersen parameter") 74 | } 75 | // The batch size must be at least 1. 76 | b := uint32(len(commitmentBatch)) 77 | if b < 1 { 78 | panic(fmt.Sprintf("b must be greater than 0, got: %v", b)) 79 | } 80 | // Make sure each commitment is for the same threshold and that that 81 | // threshold is greater than 0. 82 | k := commitmentBatch[0].Len() 83 | if k < 1 { 84 | panic(fmt.Sprintf("k must be greater than 0, got: %v", k)) 85 | } 86 | for _, c := range commitmentBatch[1:] { 87 | if c.Len() != k { 88 | panic(fmt.Sprintf("k must be equal for all commitments in the batch")) 89 | } 90 | } 91 | 92 | comBatchCopy := make([]shamir.Commitment, b) 93 | for i := range comBatchCopy { 94 | comBatchCopy[i].Set(commitmentBatch[i]) 95 | } 96 | shareBufs := make([]shamir.VerifiableShares, b) 97 | for i := range shareBufs { 98 | shareBufs[i] = shamir.VerifiableShares{} 99 | } 100 | indicesCopy := make([]secp256k1.Fn, len(indices)) 101 | copy(indicesCopy, indices) 102 | 103 | return Opener{ 104 | shareBufs: shareBufs, 105 | commitmentBatch: comBatchCopy, 106 | indices: indicesCopy, 107 | h: h, 108 | } 109 | } 110 | 111 | // HandleShareBatch handles the state transition logic upon receiving a batch 112 | // of shares. If enough shares have been received to reconstruct the secret, 113 | // then this is returned, otherwise the corresponding return value is nil. 114 | // Similarly, the decommitment (or hiding) value for the verifiable sharing 115 | // will also be returned. If the share batch was invalid in any way, an error 116 | // is returned. 117 | func (opener *Opener) HandleShareBatch(shareBatch shamir.VerifiableShares) ( 118 | []secp256k1.Fn, 119 | []secp256k1.Fn, 120 | error, 121 | ) { 122 | // The number of shares should equal the batch size. 123 | if len(shareBatch) != int(opener.BatchSize()) { 124 | return nil, nil, ErrIncorrectBatchSize 125 | } 126 | 127 | // All shares should have the same index. 128 | for i := 1; i < len(shareBatch); i++ { 129 | if !shareBatch[i].Share.IndexEq(&shareBatch[0].Share.Index) { 130 | return nil, nil, ErrInvalidShares 131 | } 132 | } 133 | index := shareBatch[0].Share.Index 134 | 135 | // The share index must be in the index set. 136 | { 137 | exists := false 138 | for i := range opener.indices { 139 | if index.Eq(&opener.indices[i]) { 140 | exists = true 141 | } 142 | } 143 | if !exists { 144 | return nil, nil, ErrIndexOutOfRange 145 | } 146 | } 147 | 148 | // There should be no duplicate indices. 149 | for _, s := range opener.shareBufs[0] { 150 | if s.Share.IndexEq(&index) { 151 | return nil, nil, ErrDuplicateIndex 152 | } 153 | } 154 | 155 | // No shares should be invalid. If even a single share is invalid, we mark 156 | // the entire batch of shares to be invalid. 157 | for i, share := range shareBatch { 158 | if !shamir.IsValid(opener.h, &opener.commitmentBatch[i], &share) { 159 | return nil, nil, ErrInvalidShares 160 | } 161 | } 162 | 163 | // At this stage we know that the shares are allowed to be added to the 164 | // respective buffers. 165 | for i := 0; i < int(opener.BatchSize()); i++ { 166 | opener.shareBufs[i] = append(opener.shareBufs[i], shareBatch[i]) 167 | } 168 | 169 | // If we have just added the kth share, we can reconstruct. 170 | numShares := len(opener.shareBufs[0]) 171 | if numShares == opener.K() { 172 | secrets := make([]secp256k1.Fn, opener.BatchSize()) 173 | decommitments := make([]secp256k1.Fn, opener.BatchSize()) 174 | shareBuf := make(shamir.Shares, numShares) 175 | for i := 0; i < int(opener.BatchSize()); i++ { 176 | for j := range opener.shareBufs[i] { 177 | shareBuf[j].Index = opener.shareBufs[i][j].Share.Index 178 | shareBuf[j].Value = opener.shareBufs[i][j].Share.Value 179 | } 180 | secrets[i] = shamir.Open(shareBuf) 181 | for j := range opener.shareBufs[i] { 182 | shareBuf[j].Index = opener.shareBufs[i][j].Share.Index 183 | shareBuf[j].Value = opener.shareBufs[i][j].Decommitment 184 | } 185 | decommitments[i] = shamir.Open(shareBuf) 186 | } 187 | 188 | return secrets, decommitments, nil 189 | } 190 | 191 | // We have added the shares to the respective buffers but we were not yet 192 | // able to reconstruct the secrets. 193 | return nil, nil, nil 194 | } 195 | -------------------------------------------------------------------------------- /open/open_suite_test.go: -------------------------------------------------------------------------------- 1 | package open_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestOpen(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Open Suite") 13 | } 14 | -------------------------------------------------------------------------------- /open/open_test.go: -------------------------------------------------------------------------------- 1 | package open_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/renproject/mpc/open" 9 | "github.com/renproject/mpc/open/openutil" 10 | "github.com/renproject/secp256k1" 11 | "github.com/renproject/shamir" 12 | "github.com/renproject/shamir/shamirutil" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/renproject/mpc/mpcutil" 17 | ) 18 | 19 | var _ = Describe("Opener", func() { 20 | rand.Seed(int64(time.Now().Nanosecond())) 21 | 22 | // Pedersen commitment system parameter. For testing this can be random, 23 | // but in a real world use case this should be chosen appropriately. 24 | h := secp256k1.RandomPoint() 25 | 26 | TransposeShares := func(shares []shamir.VerifiableShares) []shamir.VerifiableShares { 27 | numRows := len(shares) 28 | numCols := len(shares[0]) 29 | transposed := make([]shamir.VerifiableShares, numCols) 30 | for i := range transposed { 31 | transposed[i] = make(shamir.VerifiableShares, numRows) 32 | for j := range transposed[i] { 33 | transposed[i][j] = shares[j][i] 34 | } 35 | } 36 | return transposed 37 | } 38 | 39 | RandomVerifiableSharingBatch := func(indices []secp256k1.Fn, k, b int) ( 40 | []shamir.VerifiableShares, []shamir.Commitment, []secp256k1.Fn, []secp256k1.Fn, 41 | ) { 42 | n := len(indices) 43 | 44 | sharingsBatch := make([]shamir.VerifiableShares, b) 45 | commitmentBatch := make([]shamir.Commitment, b) 46 | secrets := make([]secp256k1.Fn, b) 47 | decommitments := make([]secp256k1.Fn, b) 48 | 49 | coeffs := make([]secp256k1.Fn, k) 50 | sharing := make(shamir.Shares, n) 51 | decommitmentSharing := make(shamir.Shares, n) 52 | for i := 0; i < b; i++ { 53 | sharingsBatch[i] = make(shamir.VerifiableShares, n) 54 | commitmentBatch[i] = shamir.NewCommitmentWithCapacity(k) 55 | 56 | secrets[i] = secp256k1.RandomFn() 57 | shamir.ShareAndGetCoeffs(&sharing, coeffs, indices, secrets[i], k) 58 | commitmentBatch[i] = commitmentBatch[i][:k] 59 | for j, c := range coeffs { 60 | commitmentBatch[i][j].BaseExp(&c) 61 | } 62 | decommitments[i] = secp256k1.RandomFn() 63 | shamir.ShareAndGetCoeffs(&decommitmentSharing, coeffs, indices, decommitments[i], k) 64 | var tmp secp256k1.Point 65 | for j, c := range coeffs { 66 | tmp.Scale(&h, &c) 67 | commitmentBatch[i][j].Add(&commitmentBatch[i][j], &tmp) 68 | } 69 | 70 | for j := range sharingsBatch[i] { 71 | sharingsBatch[i][j].Share = sharing[j] 72 | sharingsBatch[i][j].Decommitment = decommitmentSharing[j].Value 73 | } 74 | } 75 | 76 | shareBatchesByPlayer := TransposeShares(sharingsBatch) 77 | return shareBatchesByPlayer, commitmentBatch, secrets, decommitments 78 | } 79 | 80 | PerturbRandomShareInBatch := func(shareBatch shamir.VerifiableShares) shamir.VerifiableShares { 81 | r := rand.Uint32() 82 | // Make sure that we always perturb. 83 | doAll := r&0b111 == 0 84 | 85 | perturbedBatch := make(shamir.VerifiableShares, len(shareBatch)) 86 | copy(perturbedBatch, shareBatch) 87 | i := rand.Intn(len(perturbedBatch)) 88 | if r&0b001 != 0 || doAll { 89 | perturbedBatch[i].Share.Index = secp256k1.RandomFn() 90 | } 91 | if r&0b010 != 0 || doAll { 92 | perturbedBatch[i].Share.Value = secp256k1.RandomFn() 93 | } 94 | if r&0b100 != 0 || doAll { 95 | perturbedBatch[i].Decommitment = secp256k1.RandomFn() 96 | } 97 | 98 | return perturbedBatch 99 | } 100 | 101 | Describe("Properties", func() { 102 | b := 5 103 | n := 20 104 | k := 7 105 | 106 | Setup := func(n, k, b int) ( 107 | []secp256k1.Fn, 108 | open.Opener, 109 | []secp256k1.Fn, 110 | []secp256k1.Fn, 111 | []shamir.VerifiableShares, 112 | []shamir.Commitment, 113 | ) { 114 | indices := shamirutil.RandomIndices(n) 115 | shareBatchesByPlayer, commitments, secrets, decommitments := 116 | RandomVerifiableSharingBatch(indices, k, b) 117 | opener := open.New(commitments, indices, h) 118 | return indices, opener, secrets, decommitments, shareBatchesByPlayer, commitments 119 | } 120 | 121 | CheckInvalidBatchBehaviour := func( 122 | opener *open.Opener, invalidBatch shamir.VerifiableShares, err error, 123 | ) { 124 | initialBufCount := opener.I() 125 | secrets, decommitments, err := opener.HandleShareBatch(invalidBatch) 126 | Expect(secrets).To(BeNil()) 127 | Expect(decommitments).To(BeNil()) 128 | Expect(err).To(Equal(err)) 129 | Expect(opener.I()).To(Equal(initialBufCount)) 130 | } 131 | 132 | Context("state transitions", func() { 133 | It("should add the share to the buffer if it is valid", func() { 134 | _, opener, secrets, decommitments, shareBatchesByPlayer, _ := Setup(n, k, b) 135 | 136 | for i, shareBatch := range shareBatchesByPlayer { 137 | reconstructedSecrets, reconstructedDecommitments, err := opener.HandleShareBatch(shareBatch) 138 | Expect(err).ToNot(HaveOccurred()) 139 | Expect(opener.I()).To(Equal(i + 1)) 140 | if opener.I() == k { 141 | // If the secrets and decommitments were reconstructed, 142 | // check that they have the right form and are equal to 143 | // the correct values. 144 | Expect(reconstructedSecrets).ToNot(BeNil()) 145 | Expect(reconstructedDecommitments).ToNot(BeNil()) 146 | Expect(len(reconstructedSecrets)).To(Equal(len(secrets))) 147 | Expect(len(reconstructedSecrets)).To(Equal(b)) 148 | Expect(len(decommitments)).To(Equal(b)) 149 | for i, secret := range reconstructedSecrets { 150 | Expect(secret.Eq(&secrets[i])).To(BeTrue()) 151 | } 152 | for i, decommitment := range reconstructedDecommitments { 153 | Expect(decommitment.Eq(&decommitments[i])).To(BeTrue()) 154 | } 155 | } else { 156 | Expect(reconstructedSecrets).To(BeNil()) 157 | Expect(reconstructedDecommitments).To(BeNil()) 158 | } 159 | } 160 | }) 161 | 162 | It("should return an error when the share batch is invalid", func() { 163 | // Setup with n + 1 and treat the last share batch and index as 164 | // extras. The commitment will still have the correct form when 165 | // used with only the first n shar batches and indices. 166 | indicesEx, _, _, _, shareBatchesByPlayerEx, commitmentBatch := Setup(n+1, k, b) 167 | indices := indicesEx[:len(indicesEx)-1] 168 | opener := open.New(commitmentBatch, indices, h) 169 | shareBatchesByPlayer := shareBatchesByPlayerEx[:len(shareBatchesByPlayerEx)-1] 170 | extraShareBatch := shareBatchesByPlayerEx[len(shareBatchesByPlayerEx)-1] 171 | 172 | for i, shareBatch := range shareBatchesByPlayer { 173 | // Share batch with the wrong batch size. 174 | CheckInvalidBatchBehaviour(&opener, shareBatch[1:], open.ErrIncorrectBatchSize) 175 | 176 | // Share batch with invalid index/value/decommitment. 177 | invalidBatch := PerturbRandomShareInBatch(shareBatch) 178 | CheckInvalidBatchBehaviour(&opener, invalidBatch, open.ErrInvalidShares) 179 | 180 | _, _, _ = opener.HandleShareBatch(shareBatch) 181 | Expect(opener.I()).To(Equal(i + 1)) 182 | 183 | // Share batch with an index that has already been handled. 184 | CheckInvalidBatchBehaviour(&opener, shareBatch, open.ErrDuplicateIndex) 185 | } 186 | 187 | // Otherwise valid share batch that has an index outside of the 188 | // perscribed index set. 189 | CheckInvalidBatchBehaviour(&opener, extraShareBatch, open.ErrIndexOutOfRange) 190 | }) 191 | }) 192 | 193 | Context("panics", func() { 194 | Specify("insecure pedersen parameter", func() { 195 | indices := []secp256k1.Fn{} 196 | inf := secp256k1.NewPointInfinity() 197 | Expect(func() { open.New([]shamir.Commitment{}, indices, inf) }).To(Panic()) 198 | }) 199 | 200 | Specify("invalid batch size", func() { 201 | indices := []secp256k1.Fn{} 202 | Expect(func() { open.New([]shamir.Commitment{}, indices, h) }).To(Panic()) 203 | }) 204 | 205 | Specify("invalid reconstruction threshold (k)", func() { 206 | indices := []secp256k1.Fn{} 207 | Expect(func() { open.New(make([]shamir.Commitment, b), indices, h) }).To(Panic()) 208 | }) 209 | 210 | Specify("commitment batch with inconsistent reconstruction thresholds", func() { 211 | indices := []secp256k1.Fn{} 212 | commitmentBatch := make([]shamir.Commitment, b) 213 | for i := range commitmentBatch { 214 | commitmentBatch[i].Append(secp256k1.RandomPoint()) 215 | } 216 | // First commitment will have k = 2, others will have k = 1. 217 | commitmentBatch[0].Append(secp256k1.RandomPoint()) 218 | Expect(func() { open.New(commitmentBatch, indices, h) }).To(Panic()) 219 | }) 220 | }) 221 | }) 222 | 223 | // 224 | // Network 225 | // 226 | 227 | Context("Network (4)", func() { 228 | b := 5 229 | n := 20 230 | k := 7 231 | 232 | indices := shamirutil.RandomIndices(n) 233 | machines := make([]Machine, n) 234 | shareBatchesByPlayer, commitments, secrets, decommitments := 235 | RandomVerifiableSharingBatch(indices, k, b) 236 | 237 | ids := make([]ID, n) 238 | for i := range indices { 239 | id := ID(i + 1) 240 | machine := openutil.NewMachine(id, ids, uint32(n), shareBatchesByPlayer[i], commitments, 241 | open.New(commitments, indices, h)) 242 | machines[i] = &machine 243 | ids[i] = id 244 | } 245 | 246 | // Pick the IDs that will be simulated as offline. 247 | offline := rand.Intn(n - k + 1) 248 | offline = n - k 249 | shuffleMsgs, isOffline := MessageShufflerDropper(ids, offline) 250 | network := NewNetwork(machines, shuffleMsgs) 251 | network.SetCaptureHist(true) 252 | 253 | It("all openers should eventaully open the correct secret", func() { 254 | err := network.Run() 255 | Expect(err).ToNot(HaveOccurred()) 256 | 257 | for _, machine := range machines { 258 | if isOffline[machine.ID()] { 259 | continue 260 | } 261 | reconstructedSecrets := machine.(*openutil.Machine).Secrets 262 | reconstructedDecommitments := machine.(*openutil.Machine).Decommitments 263 | 264 | for i := 0; i < b; i++ { 265 | if !reconstructedSecrets[i].Eq(&secrets[i]) || 266 | !reconstructedDecommitments[i].Eq(&decommitments[i]) { 267 | network.Dump("test.dump") 268 | Fail(fmt.Sprintf("machine with ID %v got the wrong secret", machine.ID())) 269 | } 270 | } 271 | 272 | Expect(len(reconstructedDecommitments)).To(Equal(b)) 273 | } 274 | }) 275 | }) 276 | }) 277 | -------------------------------------------------------------------------------- /open/openutil/machine.go: -------------------------------------------------------------------------------- 1 | package openutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/mpc/open" 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // The Machine type used for the opener network test. 12 | type Machine struct { 13 | ownID mpcutil.ID 14 | ids []mpcutil.ID 15 | n uint32 16 | shares shamir.VerifiableShares 17 | commitments []shamir.Commitment 18 | opener open.Opener 19 | Secrets, Decommitments []secp256k1.Fn 20 | } 21 | 22 | // NewMachine constructs a new Machine. 23 | func NewMachine( 24 | ownID mpcutil.ID, 25 | ids []mpcutil.ID, 26 | n uint32, 27 | shares shamir.VerifiableShares, 28 | commitments []shamir.Commitment, 29 | opener open.Opener, 30 | ) Machine { 31 | secrets, decommitments, _ := opener.HandleShareBatch(shares) 32 | return Machine{ownID, ids, n, shares, commitments, opener, secrets, decommitments} 33 | } 34 | 35 | // ID implements the mpcutil.Machine interface. 36 | func (m Machine) ID() mpcutil.ID { 37 | return m.ownID 38 | } 39 | 40 | // InitialMessages implements the mpcutil.Machine interface. 41 | func (m Machine) InitialMessages() []mpcutil.Message { 42 | messages := make([]mpcutil.Message, m.n-1)[:0] 43 | for _, id := range m.ids { 44 | if id == m.ownID { 45 | continue 46 | } 47 | messages = append(messages, &Message{ 48 | shares: m.shares, 49 | from: m.ownID, 50 | to: id, 51 | }) 52 | } 53 | return messages 54 | } 55 | 56 | // Handle implements the mpcutil.Machine interface. 57 | func (m *Machine) Handle(msg mpcutil.Message) []mpcutil.Message { 58 | message := msg.(*Message) 59 | secrets, decommitments, _ := m.opener.HandleShareBatch(message.shares) 60 | if secrets != nil && decommitments != nil { 61 | m.Secrets, m.Decommitments = secrets, decommitments 62 | } 63 | return nil 64 | } 65 | 66 | // SizeHint implements the surge.SizeHinter interface. 67 | func (m Machine) SizeHint() int { 68 | return m.ownID.SizeHint() + 69 | surge.SizeHint(m.ids) + 70 | surge.SizeHint(m.n) + 71 | m.shares.SizeHint() + 72 | surge.SizeHint(m.commitments) + 73 | m.opener.SizeHint() 74 | } 75 | 76 | // Marshal implements the surge.Marshaler interface. 77 | func (m Machine) Marshal(buf []byte, rem int) ([]byte, int, error) { 78 | buf, rem, err := m.ownID.Marshal(buf, rem) 79 | if err != nil { 80 | return buf, rem, err 81 | } 82 | buf, rem, err = surge.Marshal(m.ids, buf, rem) 83 | if err != nil { 84 | return buf, rem, err 85 | } 86 | buf, rem, err = surge.MarshalU32(m.n, buf, rem) 87 | if err != nil { 88 | return buf, rem, err 89 | } 90 | buf, rem, err = m.shares.Marshal(buf, rem) 91 | if err != nil { 92 | return buf, rem, err 93 | } 94 | buf, rem, err = surge.Marshal(m.commitments, buf, rem) 95 | if err != nil { 96 | return buf, rem, err 97 | } 98 | return m.opener.Marshal(buf, rem) 99 | } 100 | 101 | // Unmarshal implements the surge.Unmarshaler interface. 102 | func (m *Machine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 103 | buf, rem, err := m.ownID.Unmarshal(buf, rem) 104 | if err != nil { 105 | return buf, rem, err 106 | } 107 | buf, rem, err = surge.Unmarshal(m.ids, buf, rem) 108 | if err != nil { 109 | return buf, rem, err 110 | } 111 | buf, rem, err = surge.UnmarshalU32(&m.n, buf, rem) 112 | if err != nil { 113 | return buf, rem, err 114 | } 115 | buf, rem, err = m.shares.Unmarshal(buf, rem) 116 | if err != nil { 117 | return buf, rem, err 118 | } 119 | buf, rem, err = surge.Unmarshal(&m.commitments, buf, rem) 120 | if err != nil { 121 | return buf, rem, err 122 | } 123 | return m.opener.Unmarshal(buf, rem) 124 | } 125 | -------------------------------------------------------------------------------- /open/openutil/message.go: -------------------------------------------------------------------------------- 1 | package openutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/shamir" 6 | ) 7 | 8 | // The Message type used for network testing the opener. 9 | type Message struct { 10 | shares shamir.VerifiableShares 11 | from, to mpcutil.ID 12 | } 13 | 14 | // From implements the mpcutil.Message interface. 15 | func (msg Message) From() mpcutil.ID { return msg.from } 16 | 17 | // To implements the mpcutil.Message interface. 18 | func (msg Message) To() mpcutil.ID { return msg.to } 19 | 20 | // SizeHint implements the surge.SizeHinter interface. 21 | func (msg Message) SizeHint() int { 22 | return msg.shares.SizeHint() + msg.from.SizeHint() + msg.to.SizeHint() 23 | } 24 | 25 | // Marshal implements the surge.Marshaler interface. 26 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 27 | buf, rem, err := msg.shares.Marshal(buf, rem) 28 | if err != nil { 29 | return buf, rem, err 30 | } 31 | buf, rem, err = msg.from.Marshal(buf, rem) 32 | if err != nil { 33 | return buf, rem, err 34 | } 35 | buf, rem, err = msg.to.Marshal(buf, rem) 36 | return buf, rem, err 37 | } 38 | 39 | // Unmarshal implements the surge.Unmarshaler interface. 40 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 41 | buf, rem, err := msg.shares.Unmarshal(buf, rem) 42 | if err != nil { 43 | return buf, rem, err 44 | } 45 | buf, rem, err = msg.from.Unmarshal(buf, rem) 46 | if err != nil { 47 | return buf, rem, err 48 | } 49 | buf, rem, err = msg.to.Unmarshal(buf, rem) 50 | return buf, rem, err 51 | } 52 | -------------------------------------------------------------------------------- /params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import "github.com/renproject/secp256k1" 4 | 5 | // ValiValidPedersenParameter returns false when the given curve point cannot 6 | // be securely used as a Pedersen commitment scheme parameter. This function 7 | // does NOT guarantee that the Pedersen parameter is secure, but simply checks 8 | // a small number of basic cases that are known to be insecure. 9 | func ValidPedersenParameter(h secp256k1.Point) bool { 10 | var g secp256k1.Point 11 | one := secp256k1.NewFnFromU16(1) 12 | g.BaseExp(&one) 13 | return !h.IsInfinity() && !h.Eq(&g) 14 | } 15 | -------------------------------------------------------------------------------- /rkpg/err.go: -------------------------------------------------------------------------------- 1 | package rkpg 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrWrongBatchSize is returned when the batch size of the given shares is 7 | // not equal to the batch size for the RKPG instance. 8 | ErrWrongBatchSize = errors.New("wrong batch size") 9 | 10 | // ErrInvalidIndex is returned when the index of the shares in the batch 11 | // are not in the index set for the RKPG instance. 12 | ErrInvalidIndex = errors.New("invalid index") 13 | 14 | // ErrDuplicateIndex is returned when the index of the shares in the batch 15 | // has already been seen before. 16 | ErrDuplicateIndex = errors.New("duplicate index") 17 | 18 | // ErrInconsistentShares is returned when not all shares in the batch have 19 | // the same index. 20 | ErrInconsistentShares = errors.New("inconsistent shares") 21 | 22 | // ErrTooManyErrors is returned when during a reconstruction attempt using 23 | // RS decoding, there were too many errant shares to obtain a result. 24 | ErrTooManyErrors = errors.New("too many errors") 25 | ) 26 | -------------------------------------------------------------------------------- /rkpg/marshal.go: -------------------------------------------------------------------------------- 1 | package rkpg 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir/rs" 9 | "github.com/renproject/surge" 10 | ) 11 | 12 | // Generate implements the quick.Generator interface. 13 | func (rkpger RKPGer) Generate(rand *rand.Rand, size int) reflect.Value { 14 | size /= 4 15 | state := State{}.Generate(rand, size).Interface().(State) 16 | points := make([]secp256k1.Point, size/4) 17 | for i := range points { 18 | points[i] = secp256k1.RandomPoint() 19 | } 20 | decoder := rs.Decoder{}.Generate(rand, size).Interface().(rs.Decoder) 21 | indices := make([]secp256k1.Fn, size/4) 22 | for i := range indices { 23 | indices[i] = secp256k1.RandomFn() 24 | } 25 | r := RKPGer{ 26 | state: state, 27 | k: rand.Int31(), 28 | points: points, 29 | decoder: decoder, 30 | indices: indices, 31 | h: secp256k1.RandomPoint(), 32 | } 33 | return reflect.ValueOf(r) 34 | } 35 | 36 | // SizeHint implements the surge.SizeHinter interface. 37 | func (rkpger RKPGer) SizeHint() int { 38 | return rkpger.state.SizeHint() + 39 | surge.SizeHint(rkpger.k) + 40 | surge.SizeHint(rkpger.points) + 41 | rkpger.decoder.SizeHint() + 42 | surge.SizeHint(rkpger.indices) + 43 | rkpger.h.SizeHint() 44 | } 45 | 46 | // Marshal implements the surge.Marshaler interface. 47 | func (rkpger RKPGer) Marshal(buf []byte, rem int) ([]byte, int, error) { 48 | buf, rem, err := rkpger.state.Marshal(buf, rem) 49 | if err != nil { 50 | return buf, rem, err 51 | } 52 | buf, rem, err = surge.MarshalI32(rkpger.k, buf, rem) 53 | if err != nil { 54 | return buf, rem, err 55 | } 56 | buf, rem, err = surge.Marshal(rkpger.points, buf, rem) 57 | if err != nil { 58 | return buf, rem, err 59 | } 60 | buf, rem, err = rkpger.decoder.Marshal(buf, rem) 61 | if err != nil { 62 | return buf, rem, err 63 | } 64 | buf, rem, err = surge.Marshal(rkpger.indices, buf, rem) 65 | if err != nil { 66 | return buf, rem, err 67 | } 68 | return rkpger.h.Marshal(buf, rem) 69 | } 70 | 71 | // Unmarshal implements the surge.Unmarshaler interface. 72 | func (rkpger *RKPGer) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 73 | buf, rem, err := rkpger.state.Unmarshal(buf, rem) 74 | if err != nil { 75 | return buf, rem, err 76 | } 77 | buf, rem, err = surge.UnmarshalI32(&rkpger.k, buf, rem) 78 | if err != nil { 79 | return buf, rem, err 80 | } 81 | buf, rem, err = surge.Unmarshal(&rkpger.points, buf, rem) 82 | if err != nil { 83 | return buf, rem, err 84 | } 85 | buf, rem, err = rkpger.decoder.Unmarshal(buf, rem) 86 | if err != nil { 87 | return buf, rem, err 88 | } 89 | buf, rem, err = surge.Unmarshal(&rkpger.indices, buf, rem) 90 | if err != nil { 91 | return buf, rem, err 92 | } 93 | return rkpger.h.Unmarshal(buf, rem) 94 | } 95 | -------------------------------------------------------------------------------- /rkpg/marshal_test.go: -------------------------------------------------------------------------------- 1 | package rkpg_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/rkpg" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | ts := []reflect.Type{ 17 | reflect.TypeOf(rkpg.State{}), 18 | reflect.TypeOf(rkpg.RKPGer{}), 19 | } 20 | 21 | for _, t := range ts { 22 | t := t 23 | 24 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 25 | It("should be the same after marshalling and unmarshalling", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 28 | } 29 | }) 30 | 31 | It("should not panic when fuzzing", func() { 32 | for i := 0; i < trials; i++ { 33 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 34 | } 35 | }) 36 | 37 | Context("marshalling", func() { 38 | It("should return an error when the buffer is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | 44 | It("should return an error when the memory quota is too small", func() { 45 | for i := 0; i < trials; i++ { 46 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 47 | } 48 | }) 49 | }) 50 | 51 | Context("unmarshalling", func() { 52 | It("should return an error when the buffer is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | 58 | It("should return an error when the memory quota is too small", func() { 59 | for i := 0; i < trials; i++ { 60 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 61 | } 62 | }) 63 | }) 64 | }) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /rkpg/rkpg.go: -------------------------------------------------------------------------------- 1 | package rkpg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/mpc/params" 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir" 9 | "github.com/renproject/shamir/rs" 10 | ) 11 | 12 | // An RKPGer is a state machine that implements the RKPG protocol. 13 | type RKPGer struct { 14 | state State 15 | 16 | // Instance parameters 17 | k int32 18 | points []secp256k1.Point 19 | decoder rs.Decoder 20 | 21 | // Global parameters 22 | indices []secp256k1.Fn 23 | h secp256k1.Point 24 | } 25 | 26 | // New returns a new RKPG state machine along with the initial message that is 27 | // to be broadcast to the other parties. The state machine will handle this 28 | // message before being returned. 29 | func New( 30 | indices []secp256k1.Fn, 31 | h secp256k1.Point, 32 | rngShares, rzgShares shamir.VerifiableShares, 33 | rngComs []shamir.Commitment, 34 | ) (RKPGer, shamir.Shares) { 35 | if !params.ValidPedersenParameter(h) { 36 | panic("insecure choice of pedersen parameter") 37 | } 38 | n := len(indices) 39 | b := len(rngShares) 40 | if len(rzgShares) != b { 41 | panic(fmt.Sprintf( 42 | "rng and rzg shares have different batch sizes: expected %v (rng) to equal %v (rzg)", 43 | len(rngShares), len(rzgShares), 44 | )) 45 | } 46 | if len(rngComs) != b { 47 | panic(fmt.Sprintf( 48 | "invalid commitment batch size: expected %v (rngShares), got %v", 49 | b, len(rngComs), 50 | )) 51 | } 52 | k := rngComs[0].Len() 53 | 54 | shares := make(shamir.Shares, b) 55 | for i := range shares { 56 | ind := rzgShares[i].Share.Index 57 | dRnShare := shamir.NewShare(ind, rngShares[i].Decommitment) 58 | shares[i].Add(&dRnShare, &rzgShares[i].Share) 59 | } 60 | 61 | state := NewState(n, b) 62 | points := make([]secp256k1.Point, b) 63 | for i := range points { 64 | points[i] = rngComs[i][0] 65 | } 66 | indicesCopy := make([]secp256k1.Fn, n) 67 | copy(indicesCopy, indices) 68 | rkpger := RKPGer{ 69 | state: state, 70 | k: int32(k), 71 | points: points, 72 | decoder: rs.NewDecoder(indices, k), 73 | indices: indicesCopy, 74 | h: h, 75 | } 76 | 77 | // Proccess own share. 78 | _, err := rkpger.HandleShareBatch(shares) 79 | if err != nil { 80 | panic("error handling own share") 81 | } 82 | 83 | return rkpger, shares 84 | } 85 | 86 | // HandleShareBatch applies a state transition to the given state upon 87 | // receiveing the given shares from another party during the open in the RKPG 88 | // protocol. Once enough shares have been received to reconstruct, the output 89 | // public key batch is computed and returned. If not enough shares have been 90 | // received, the return value will be nil. 91 | func (rkpger *RKPGer) HandleShareBatch(shares shamir.Shares) ( 92 | []secp256k1.Point, error, 93 | ) { 94 | n := len(rkpger.indices) 95 | b := len(rkpger.points) 96 | if len(shares) != int(b) { 97 | return nil, ErrWrongBatchSize 98 | } 99 | // Check that the index of the first share is in the list of indices. 100 | ind := -1 101 | index := shares[0].Index 102 | for i := range rkpger.indices { 103 | if index.Eq(&rkpger.indices[i]) { 104 | ind = i 105 | } 106 | } 107 | if ind < 0 { 108 | return nil, ErrInvalidIndex 109 | } 110 | 111 | if rkpger.state.shareReceived[ind] { 112 | return nil, ErrDuplicateIndex 113 | } 114 | // Check that all indices in the share batch are the same. 115 | for i := 1; i < len(shares); i++ { 116 | if !shares[i].IndexEq(&index) { 117 | return nil, ErrInconsistentShares 118 | } 119 | } 120 | 121 | // Checks have passed so we update the rkpger.state. 122 | for i, buf := range rkpger.state.buffers { 123 | buf[ind] = shares[i].Value 124 | } 125 | rkpger.state.shareReceived[ind] = true 126 | rkpger.state.count++ 127 | 128 | if int(rkpger.state.count) < n-int(rkpger.k)+1 { 129 | // Not enough shares have been received for reconstruction. 130 | return nil, nil 131 | } 132 | secrets := make([]secp256k1.Fn, b) 133 | for i, buf := range rkpger.state.buffers { 134 | poly, ok := rkpger.decoder.Decode(buf) 135 | if !ok { 136 | // The RS decoder was not able to reconstruct the polynomial 137 | // because there are too many incorrect shares. 138 | return nil, ErrTooManyErrors 139 | } 140 | secrets[i] = *poly.Coefficient(0) 141 | } 142 | 143 | pubKeys := make([]secp256k1.Point, b) 144 | for i, secret := range secrets { 145 | // Compute xG = (xG + sH) + (-s)H 146 | secret.Negate(&secret) 147 | pubKeys[i].Scale(&rkpger.h, &secret) 148 | pubKeys[i].Add(&pubKeys[i], &rkpger.points[i]) 149 | } 150 | return pubKeys, nil 151 | } 152 | -------------------------------------------------------------------------------- /rkpg/rkpg_suite_test.go: -------------------------------------------------------------------------------- 1 | package rkpg_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestRkpg(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Rkpg Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rkpg/rkpg_test.go: -------------------------------------------------------------------------------- 1 | package rkpg_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/renproject/mpc/mpcutil" 9 | "github.com/renproject/mpc/rkpg/rkpgutil" 10 | "github.com/renproject/secp256k1" 11 | "github.com/renproject/shamir" 12 | "github.com/renproject/shamir/shamirutil" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/renproject/mpc/rkpg" 17 | ) 18 | 19 | var _ = Describe("RKPG", func() { 20 | rand.Seed(int64(time.Now().Nanosecond())) 21 | trials := 10 22 | 23 | RandomTestParams := func() (int, int, int, int, secp256k1.Point, []secp256k1.Fn) { 24 | k := shamirutil.RandRange(4, 15) 25 | n := 3 * k 26 | t := k - 2 27 | b := shamirutil.RandRange(2, 10) 28 | h := secp256k1.RandomPoint() 29 | indices := shamirutil.RandomIndices(n) 30 | return n, k, t, b, h, indices 31 | } 32 | 33 | RXGOutputs := func(k, b int, indices []secp256k1.Fn, h secp256k1.Point) ( 34 | []shamir.VerifiableShares, 35 | []shamir.VerifiableShares, 36 | []shamir.Commitment, 37 | []secp256k1.Fn, 38 | ) { 39 | rngShares, rngComs, secrets := rkpgutil.RNGOutputBatch(indices, k, b, h) 40 | rzgShares, _ := rkpgutil.RZGOutputBatch(indices, k, b, h) 41 | return rngShares, rzgShares, rngComs, secrets 42 | } 43 | 44 | RKPGShare := func(rngShare, rzgShare shamir.VerifiableShare) shamir.Share { 45 | var share shamir.Share 46 | ind := rzgShare.Share.Index 47 | dRnShare := shamir.NewShare(ind, rngShare.Decommitment) 48 | share.Add(&dRnShare, &rzgShare.Share) 49 | return share 50 | } 51 | 52 | Context("state transitions", func() { 53 | Specify("shares with invalid batch size", func() { 54 | for i := 0; i < trials; i++ { 55 | _, k, _, b, h, indices := RandomTestParams() 56 | rngShares, rzgShares, rngComs, _ := RXGOutputs(k, b, indices, h) 57 | rkpger, _ := New(indices, h, rngShares[1], rzgShares[1], rngComs) 58 | 59 | res, err := rkpger.HandleShareBatch(make(shamir.Shares, b-1)) 60 | Expect(res).To(BeNil()) 61 | Expect(err).To(Equal(ErrWrongBatchSize)) 62 | } 63 | }) 64 | 65 | Specify("shares with invalid index", func() { 66 | for i := 0; i < trials; i++ { 67 | _, k, _, b, h, indices := RandomTestParams() 68 | rngShares, rzgShares, rngComs, _ := RXGOutputs(k, b, indices, h) 69 | rkpger, _ := New(indices, h, rngShares[1], rzgShares[1], rngComs) 70 | 71 | // As it is an uninitialised slice, all of the shares in 72 | // `shares` should have index zero, which should not be in the 73 | // set `indices` with overwhelming probability. 74 | res, err := rkpger.HandleShareBatch(make(shamir.Shares, b)) 75 | Expect(res).To(BeNil()) 76 | Expect(err).To(Equal(ErrInvalidIndex)) 77 | } 78 | }) 79 | 80 | Specify("shares with duplicate indices", func() { 81 | for i := 0; i < trials; i++ { 82 | _, k, _, b, h, indices := RandomTestParams() 83 | rngShares, rzgShares, rngComs, _ := RXGOutputs(k, b, indices, h) 84 | rkpger, shares := New(indices, h, rngShares[0], rzgShares[0], rngComs) 85 | 86 | // The RKPGer has already handled its own shares, so this 87 | // should trigger a duplciate index error. 88 | res, err := rkpger.HandleShareBatch(shares) 89 | Expect(res).To(BeNil()) 90 | Expect(err).To(Equal(ErrDuplicateIndex)) 91 | } 92 | }) 93 | 94 | Specify("shares with inconsistent indices", func() { 95 | for i := 0; i < trials; i++ { 96 | _, k, _, b, h, indices := RandomTestParams() 97 | rngShares, rzgShares, rngComs, _ := RXGOutputs(k, b, indices, h) 98 | rkpger, _ := New(indices, h, rngShares[0], rzgShares[0], rngComs) 99 | 100 | shares := make(shamir.Shares, b) 101 | shares[0] = shamir.NewShare(indices[1], secp256k1.Fn{}) 102 | for j := 1; j < len(shares); j++ { 103 | shares[j] = shamir.NewShare(indices[2], secp256k1.Fn{}) 104 | } 105 | 106 | res, err := rkpger.HandleShareBatch(shares) 107 | Expect(res).To(BeNil()) 108 | Expect(err).To(Equal(ErrInconsistentShares)) 109 | } 110 | }) 111 | 112 | Specify("valid shares", func() { 113 | for i := 0; i < 1; i++ { 114 | n, k, _, b, h, indices := RandomTestParams() 115 | rngShares, rzgShares, rngComs, secrets := RXGOutputs(k, b, indices, h) 116 | rkpger, _ := New(indices, h, rngShares[0], rzgShares[0], rngComs) 117 | 118 | var err error 119 | shares := make([]shamir.Shares, n-1) 120 | for j := range shares { 121 | _, shares[j] = New(indices, h, rngShares[j+1], rzgShares[j+1], rngComs) 122 | } 123 | 124 | threshold := n - k + 1 125 | for j := 0; j < threshold-2; j++ { 126 | res, err := rkpger.HandleShareBatch(shares[j]) 127 | Expect(err).ToNot(HaveOccurred()) 128 | Expect(res).To(BeNil()) 129 | } 130 | pubkeys, err := rkpger.HandleShareBatch(shares[threshold-1]) 131 | Expect(err).ToNot(HaveOccurred()) 132 | for j := range pubkeys { 133 | var expected secp256k1.Point 134 | expected.BaseExpUnsafe(&secrets[j]) 135 | Expect(expected.Eq(&pubkeys[j])).To(BeTrue()) 136 | } 137 | } 138 | }) 139 | 140 | Specify("invalid shares", func() { 141 | for i := 0; i < trials; i++ { 142 | n, k, t, b, h, indices := RandomTestParams() 143 | rngShares, rzgShares, rngComs, _ := RXGOutputs(k, b, indices, h) 144 | rkpger, _ := New(indices, h, rngShares[0], rzgShares[0], rngComs) 145 | 146 | // Create invalid shares. 147 | shares := make([]shamir.Shares, n-1) 148 | for i := range shares { 149 | shares[i] = make(shamir.Shares, b) 150 | for j := range shares[i] { 151 | shares[i][j] = RKPGShare(rngShares[i+1][j], rzgShares[i+1][j]) 152 | } 153 | } 154 | badBuf := rand.Intn(b) 155 | for i := 0; i < t; i++ { 156 | shares[i][badBuf] = shamir.NewShare(shares[i][badBuf].Index, secp256k1.NewFnFromU16(0)) 157 | } 158 | 159 | threshold := n - k + 1 160 | errThreshold := n - 2 161 | for i := 0; i < threshold-2; i++ { 162 | res, err := rkpger.HandleShareBatch(shares[i]) 163 | Expect(err).ToNot(HaveOccurred()) 164 | Expect(res).To(BeNil()) 165 | } 166 | for i := threshold - 2; i < errThreshold-2; i++ { 167 | res, err := rkpger.HandleShareBatch(shares[i]) 168 | Expect(err).To(Equal(ErrTooManyErrors)) 169 | Expect(res).To(BeNil()) 170 | } 171 | res, err := rkpger.HandleShareBatch(shares[errThreshold-1]) 172 | Expect(res).ToNot(BeNil()) 173 | Expect(err).ToNot(HaveOccurred()) 174 | } 175 | }) 176 | }) 177 | 178 | Context("initial messages", func() { 179 | Specify("insecure pedersen parameter", func() { 180 | _, _, _, b, _, indices := RandomTestParams() 181 | inf := secp256k1.NewPointInfinity() 182 | rngShares := make(shamir.VerifiableShares, b) 183 | rzgShares := make(shamir.VerifiableShares, b) 184 | rngComs := make([]shamir.Commitment, b) 185 | 186 | Expect(func() { New(indices, inf, rngShares, rzgShares, rngComs) }).To(Panic()) 187 | }) 188 | 189 | Specify("shares with the wrong batch size", func() { 190 | for i := 0; i < trials; i++ { 191 | _, _, _, b, h, indices := RandomTestParams() 192 | rngShares := make(shamir.VerifiableShares, b) 193 | rzgShares := make(shamir.VerifiableShares, b) 194 | rngComs := make([]shamir.Commitment, b) 195 | 196 | Expect(func() { New(indices, h, rngShares[:b-1], rzgShares, rngComs) }).To(Panic()) 197 | Expect(func() { New(indices, h, rngShares, rzgShares[:b-1], rngComs) }).To(Panic()) 198 | Expect(func() { New(indices, h, rngShares, rzgShares, rngComs[:b-1]) }).To(Panic()) 199 | } 200 | }) 201 | 202 | Specify("inconsistent share indices", func() { 203 | for i := 0; i < trials; i++ { 204 | _, _, _, b, h, indices := RandomTestParams() 205 | rngShares := make(shamir.VerifiableShares, b) 206 | rzgShares := make(shamir.VerifiableShares, b) 207 | rngComs := make([]shamir.Commitment, b) 208 | 209 | rngShares[0] = shamir.NewVerifiableShare( 210 | shamir.NewShare(secp256k1.RandomFn(), secp256k1.Fn{}), 211 | secp256k1.Fn{}, 212 | ) 213 | Expect(func() { New(indices, h, rngShares, rzgShares, rngComs) }).To(Panic()) 214 | } 215 | }) 216 | }) 217 | 218 | Context("network simulation", func() { 219 | tys := []rkpgutil.MachineType{ 220 | rkpgutil.Offline, 221 | rkpgutil.Malicious, 222 | rkpgutil.MaliciousZero, 223 | } 224 | 225 | for _, ty := range tys { 226 | Context(fmt.Sprintf("dishonest machine type %v", ty), func() { 227 | Specify("players should end up with the same correct public key", func() { 228 | n, k, t, b, h, indices := RandomTestParams() 229 | rngShares, rzgShares, rngComs, secrets := RXGOutputs(k, b, indices, h) 230 | ids := make([]mpcutil.ID, n) 231 | for i := range ids { 232 | ids[i] = mpcutil.ID(i + 1) 233 | } 234 | dishonestIDs := make(map[mpcutil.ID]struct{}, t) 235 | { 236 | tmp := make([]mpcutil.ID, n) 237 | copy(tmp, ids) 238 | rand.Shuffle(len(tmp), func(i, j int) { 239 | tmp[i], tmp[j] = tmp[j], tmp[i] 240 | }) 241 | for _, id := range tmp[:t] { 242 | dishonestIDs[id] = struct{}{} 243 | } 244 | } 245 | machineType := make(map[mpcutil.ID]rkpgutil.MachineType, n) 246 | for _, id := range ids { 247 | if _, ok := dishonestIDs[id]; ok { 248 | machineType[id] = ty 249 | } else { 250 | machineType[id] = rkpgutil.Honest 251 | } 252 | } 253 | 254 | machines := make([]mpcutil.Machine, n) 255 | for i, id := range ids { 256 | var machine mpcutil.Machine 257 | switch machineType[id] { 258 | case rkpgutil.Offline: 259 | m := mpcutil.OfflineMachine(ids[i]) 260 | machine = &m 261 | case rkpgutil.Malicious: 262 | m := rkpgutil.NewMaliciousMachine(ids[i], ids, int32(b), indices, false) 263 | machine = &m 264 | case rkpgutil.MaliciousZero: 265 | m := rkpgutil.NewMaliciousMachine(ids[i], ids, int32(b), indices, true) 266 | machine = &m 267 | case rkpgutil.Honest: 268 | m := rkpgutil.NewHonestMachine( 269 | ids[i], 270 | ids, 271 | indices, 272 | h, 273 | rngComs, 274 | rngShares[i], 275 | rzgShares[i], 276 | ) 277 | machine = &m 278 | } 279 | machines[i] = machine 280 | } 281 | shuffleMsgs, _ := mpcutil.MessageShufflerDropper(ids, 0) 282 | network := mpcutil.NewNetwork(machines, shuffleMsgs) 283 | network.SetCaptureHist(true) 284 | err := network.Run() 285 | Expect(err).ToNot(HaveOccurred()) 286 | 287 | // All players should have the same public keys. 288 | var refPoints []secp256k1.Point 289 | for i := range machines { 290 | if machineType[machines[i].ID()] == rkpgutil.Honest { 291 | refPoints = machines[i].(*rkpgutil.HonestMachine).Points 292 | break 293 | } 294 | } 295 | for i := range machines { 296 | if machineType[machines[i].ID()] != rkpgutil.Honest { 297 | continue 298 | } 299 | points := machines[i].(*rkpgutil.HonestMachine).Points 300 | for j := range refPoints { 301 | Expect(refPoints[j].Eq(&points[j])).To(BeTrue()) 302 | } 303 | } 304 | 305 | // The public keys should correspond to the private keys. 306 | for i := range refPoints { 307 | var expected secp256k1.Point 308 | expected.BaseExpUnsafe(&secrets[i]) 309 | Expect(expected.Eq(&refPoints[i])).To(BeTrue()) 310 | } 311 | }) 312 | }) 313 | } 314 | }) 315 | }) 316 | -------------------------------------------------------------------------------- /rkpg/rkpgutil/machine.go: -------------------------------------------------------------------------------- 1 | package rkpgutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/mpc/rkpg" 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // MachineType represents a type of player in the network. 12 | type MachineType byte 13 | 14 | const ( 15 | // Honest represents a player that follows the RKPG protocol as specified. 16 | Honest = MachineType(iota) 17 | 18 | // Offline represents a player that is offline. 19 | Offline 20 | 21 | // Malicious represents a player that deviates from the RKPG protocol by 22 | // sending shares with incorrect values. 23 | Malicious 24 | 25 | // MaliciousZero represents a player that deviates from the RKPG protocol 26 | // by sending shares with values equal to zero. 27 | MaliciousZero 28 | ) 29 | 30 | func (ty MachineType) String() string { 31 | switch ty { 32 | case Honest: 33 | return "Honest" 34 | case Offline: 35 | return "Offline" 36 | case Malicious: 37 | return "Malicious" 38 | case MaliciousZero: 39 | return "MaliciousZero" 40 | default: 41 | return "Unknown" 42 | } 43 | } 44 | 45 | // HonestMachine is a machine that follows the RKPG protocol as specified. 46 | type HonestMachine struct { 47 | OwnID mpcutil.ID 48 | IDs []mpcutil.ID 49 | 50 | RKPGer rkpg.RKPGer 51 | Messages []mpcutil.Message 52 | Points []secp256k1.Point 53 | 54 | RNGShares, RZGShares shamir.VerifiableShares 55 | } 56 | 57 | // NewHonestMachine constructs and returns a new honest machine. 58 | func NewHonestMachine( 59 | ownID mpcutil.ID, 60 | ids []mpcutil.ID, 61 | indices []secp256k1.Fn, 62 | h secp256k1.Point, 63 | coms []shamir.Commitment, 64 | rngShares, rzgShares shamir.VerifiableShares, 65 | ) HonestMachine { 66 | rkpger, shares := rkpg.New(indices, h, rngShares, rzgShares, coms) 67 | messages := make([]mpcutil.Message, len(ids)) 68 | for i, to := range ids { 69 | msgShares := make(shamir.Shares, len(shares)) 70 | copy(msgShares, shares) 71 | messages[i] = &Message{ 72 | ToID: to, 73 | FromID: ownID, 74 | ShareBatch: msgShares, 75 | } 76 | } 77 | return HonestMachine{ 78 | OwnID: ownID, 79 | IDs: ids, 80 | 81 | RKPGer: rkpger, 82 | Messages: messages, 83 | Points: []secp256k1.Point{}, 84 | 85 | RNGShares: rngShares, RZGShares: rzgShares, 86 | } 87 | } 88 | 89 | // ID implements the mpcutil.Machine interface. 90 | func (m HonestMachine) ID() mpcutil.ID { return m.OwnID } 91 | 92 | // InitialMessages implements the mpcutil.Machine interface. 93 | func (m HonestMachine) InitialMessages() []mpcutil.Message { 94 | return m.Messages 95 | } 96 | 97 | // Handle implements the mpcutil.Machine interface. 98 | func (m *HonestMachine) Handle(msg mpcutil.Message) []mpcutil.Message { 99 | message := msg.(*Message) 100 | points, _ := m.RKPGer.HandleShareBatch(message.ShareBatch) 101 | if points != nil { 102 | m.Points = points 103 | } 104 | return nil 105 | } 106 | 107 | // A MaliciousMachine represents a player that acts maliciously by sending 108 | // shares with incorrect values. 109 | type MaliciousMachine struct { 110 | OwnID mpcutil.ID 111 | IDs []mpcutil.ID 112 | B int32 113 | Indices []secp256k1.Fn 114 | 115 | // If set, the player will send shares that have values equal to zero. 116 | // Otherwise, these values will be random. 117 | Zero bool 118 | } 119 | 120 | // NewMaliciousMachine constructs and returns a new malicious machine. 121 | func NewMaliciousMachine( 122 | ownID mpcutil.ID, 123 | ids []mpcutil.ID, 124 | b int32, 125 | indices []secp256k1.Fn, 126 | zero bool, 127 | ) MaliciousMachine { 128 | return MaliciousMachine{ 129 | OwnID: ownID, 130 | IDs: ids, 131 | B: b, 132 | Indices: indices, 133 | Zero: zero, 134 | } 135 | } 136 | 137 | // ID implements the mpcutil.Machine interface. 138 | func (m MaliciousMachine) ID() mpcutil.ID { return m.OwnID } 139 | 140 | // InitialMessages implements the mpcutil.Machine interface. 141 | func (m MaliciousMachine) InitialMessages() []mpcutil.Message { 142 | messages := make([]mpcutil.Message, len(m.IDs)) 143 | var val secp256k1.Fn 144 | for i, to := range m.IDs { 145 | msgShares := make(shamir.Shares, m.B) 146 | for j := range msgShares { 147 | if m.Zero { 148 | val.SetU16(0) 149 | } else { 150 | val = secp256k1.RandomFn() 151 | } 152 | msgShares[j] = shamir.NewShare(m.Indices[i], val) 153 | } 154 | messages[i] = &Message{ 155 | ToID: to, 156 | FromID: m.OwnID, 157 | ShareBatch: msgShares, 158 | } 159 | } 160 | return messages 161 | } 162 | 163 | // Handle implements the mpcutil.Machine interface. 164 | func (m *MaliciousMachine) Handle(msg mpcutil.Message) []mpcutil.Message { 165 | return nil 166 | } 167 | 168 | // SizeHint implements the surge.SizeHinter interface. 169 | func (m HonestMachine) SizeHint() int { 170 | return m.OwnID.SizeHint() + 171 | surge.SizeHint(m.IDs) + 172 | m.RKPGer.SizeHint() + 173 | surge.SizeHint(m.Messages) + 174 | surge.SizeHint(m.Points) + 175 | m.RNGShares.SizeHint() + 176 | m.RZGShares.SizeHint() 177 | } 178 | 179 | // Marshal implements the surge.Marshaler interface. 180 | func (m HonestMachine) Marshal(buf []byte, rem int) ([]byte, int, error) { 181 | buf, rem, err := m.OwnID.Marshal(buf, rem) 182 | if err != nil { 183 | return buf, rem, err 184 | } 185 | buf, rem, err = surge.Marshal(m.IDs, buf, rem) 186 | if err != nil { 187 | return buf, rem, err 188 | } 189 | buf, rem, err = m.RKPGer.Marshal(buf, rem) 190 | if err != nil { 191 | return buf, rem, err 192 | } 193 | buf, rem, err = surge.Marshal(m.Messages, buf, rem) 194 | if err != nil { 195 | return buf, rem, err 196 | } 197 | buf, rem, err = surge.Marshal(m.Points, buf, rem) 198 | if err != nil { 199 | return buf, rem, err 200 | } 201 | buf, rem, err = m.RNGShares.Marshal(buf, rem) 202 | if err != nil { 203 | return buf, rem, err 204 | } 205 | return m.RZGShares.Marshal(buf, rem) 206 | } 207 | 208 | // Unmarshal implements the surge.Unmarshaler interface. 209 | func (m *HonestMachine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 210 | buf, rem, err := m.OwnID.Unmarshal(buf, rem) 211 | if err != nil { 212 | return buf, rem, err 213 | } 214 | buf, rem, err = surge.Unmarshal(&m.IDs, buf, rem) 215 | if err != nil { 216 | return buf, rem, err 217 | } 218 | buf, rem, err = m.RKPGer.Unmarshal(buf, rem) 219 | if err != nil { 220 | return buf, rem, err 221 | } 222 | buf, rem, err = surge.Unmarshal(&m.Messages, buf, rem) 223 | if err != nil { 224 | return buf, rem, err 225 | } 226 | buf, rem, err = surge.Unmarshal(&m.Points, buf, rem) 227 | if err != nil { 228 | return buf, rem, err 229 | } 230 | buf, rem, err = m.RNGShares.Unmarshal(buf, rem) 231 | if err != nil { 232 | return buf, rem, err 233 | } 234 | return m.RZGShares.Unmarshal(buf, rem) 235 | } 236 | 237 | // SizeHint implements the surge.SizeHinter interface. 238 | func (m MaliciousMachine) SizeHint() int { 239 | return m.OwnID.SizeHint() + 240 | surge.SizeHint(m.IDs) + 241 | surge.SizeHint(m.B) + 242 | surge.SizeHint(m.Indices) + 243 | surge.SizeHint(m.Zero) 244 | } 245 | 246 | // Marshal implements the surge.Marshaler interface. 247 | func (m MaliciousMachine) Marshal(buf []byte, rem int) ([]byte, int, error) { 248 | buf, rem, err := m.OwnID.Marshal(buf, rem) 249 | if err != nil { 250 | return buf, rem, err 251 | } 252 | buf, rem, err = surge.Marshal(m.IDs, buf, rem) 253 | if err != nil { 254 | return buf, rem, err 255 | } 256 | buf, rem, err = surge.MarshalI32(m.B, buf, rem) 257 | if err != nil { 258 | return buf, rem, err 259 | } 260 | buf, rem, err = surge.MarshalBool(m.Zero, buf, rem) 261 | if err != nil { 262 | return buf, rem, err 263 | } 264 | return surge.Marshal(m.Indices, buf, rem) 265 | } 266 | 267 | // Unmarshal implements the surge.Unmarshaler interface. 268 | func (m *MaliciousMachine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 269 | buf, rem, err := m.OwnID.Unmarshal(buf, rem) 270 | if err != nil { 271 | return buf, rem, err 272 | } 273 | buf, rem, err = surge.Unmarshal(&m.IDs, buf, rem) 274 | if err != nil { 275 | return buf, rem, err 276 | } 277 | buf, rem, err = surge.UnmarshalI32(&m.B, buf, rem) 278 | if err != nil { 279 | return buf, rem, err 280 | } 281 | buf, rem, err = surge.UnmarshalBool(&m.Zero, buf, rem) 282 | if err != nil { 283 | return buf, rem, err 284 | } 285 | return surge.Unmarshal(&m.Indices, buf, rem) 286 | } 287 | -------------------------------------------------------------------------------- /rkpg/rkpgutil/message.go: -------------------------------------------------------------------------------- 1 | package rkpgutil 2 | 3 | import ( 4 | "github.com/renproject/mpc/mpcutil" 5 | "github.com/renproject/shamir" 6 | ) 7 | 8 | // A Message is sent between machines during a RKPG simulation. 9 | type Message struct { 10 | ToID, FromID mpcutil.ID 11 | ShareBatch shamir.Shares 12 | } 13 | 14 | // To implements the mpcutil.Message interface. 15 | func (msg Message) To() mpcutil.ID { return msg.ToID } 16 | 17 | // From implements the mpcutil.Message interface. 18 | func (msg Message) From() mpcutil.ID { return msg.FromID } 19 | 20 | // SizeHint implements the surge.SizeHinter interface. 21 | func (msg Message) SizeHint() int { 22 | return msg.ToID.SizeHint() + 23 | msg.FromID.SizeHint() + 24 | msg.ShareBatch.SizeHint() 25 | } 26 | 27 | // Marshal implements the surge.Marshaler interface. 28 | func (msg Message) Marshal(buf []byte, rem int) ([]byte, int, error) { 29 | buf, rem, err := msg.ToID.Marshal(buf, rem) 30 | if err != nil { 31 | return buf, rem, err 32 | } 33 | buf, rem, err = msg.FromID.Marshal(buf, rem) 34 | if err != nil { 35 | return buf, rem, err 36 | } 37 | return msg.ShareBatch.Marshal(buf, rem) 38 | } 39 | 40 | // Unmarshal implements the surge.Unmarshaler interface. 41 | func (msg *Message) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 42 | buf, rem, err := msg.ToID.Unmarshal(buf, rem) 43 | if err != nil { 44 | return buf, rem, err 45 | } 46 | buf, rem, err = msg.FromID.Unmarshal(buf, rem) 47 | if err != nil { 48 | return buf, rem, err 49 | } 50 | return msg.ShareBatch.Unmarshal(buf, rem) 51 | } 52 | -------------------------------------------------------------------------------- /rkpg/rkpgutil/testutil.go: -------------------------------------------------------------------------------- 1 | package rkpgutil 2 | 3 | import ( 4 | "github.com/renproject/secp256k1" 5 | "github.com/renproject/shamir" 6 | ) 7 | 8 | // RNGOutputBatch returns a random valid output of an instance of the RNG 9 | // protocol. In the returned shares, shares[i] are the outputs for player i and 10 | // has length equal to the batch size. The returned Fn values are the secret 11 | // values for each sharing in the batch. 12 | func RNGOutputBatch( 13 | indices []secp256k1.Fn, 14 | k, b int, 15 | h secp256k1.Point, 16 | ) ([]shamir.VerifiableShares, []shamir.Commitment, []secp256k1.Fn) { 17 | return RXGOutputBatch(indices, k, b, h, false) 18 | } 19 | 20 | // RZGOutputBatch returns a random valid output of an instance of the RZG 21 | // protocol. In the returned shares, shares[i] are the outputs for player i and 22 | // has length equal to the batch size. 23 | func RZGOutputBatch( 24 | indices []secp256k1.Fn, 25 | k, b int, 26 | h secp256k1.Point, 27 | ) ([]shamir.VerifiableShares, []shamir.Commitment) { 28 | shares, coms, _ := RXGOutputBatch(indices, k, b, h, true) 29 | return shares, coms 30 | } 31 | 32 | // RXGOutputBatch returns either RNG or RZG output based on the flag zero. 33 | func RXGOutputBatch( 34 | indices []secp256k1.Fn, 35 | k, b int, 36 | h secp256k1.Point, 37 | zero bool, 38 | ) ([]shamir.VerifiableShares, []shamir.Commitment, []secp256k1.Fn) { 39 | shares := make([]shamir.VerifiableShares, b) 40 | coms := make([]shamir.Commitment, b) 41 | secrets := make([]secp256k1.Fn, b) 42 | for i := range shares { 43 | if zero { 44 | shares[i], coms[i] = RXGOutput(indices, k, h, secp256k1.NewFnFromU16(0)) 45 | } else { 46 | secrets[i] = secp256k1.RandomFn() 47 | shares[i], coms[i] = RXGOutput(indices, k, h, secrets[i]) 48 | } 49 | } 50 | sharesTrans := make([]shamir.VerifiableShares, len(indices)) 51 | for i := range sharesTrans { 52 | sharesTrans[i] = make(shamir.VerifiableShares, b) 53 | } 54 | for i := range shares { 55 | for j, share := range shares[i] { 56 | sharesTrans[j][i] = share 57 | } 58 | } 59 | return sharesTrans, coms, secrets 60 | } 61 | 62 | // RXGOutput returns the shares and a commitment for a valid verifiable sharing 63 | // of the value x with threshold k and Pedersen parameter h. 64 | func RXGOutput( 65 | indices []secp256k1.Fn, 66 | k int, 67 | h secp256k1.Point, 68 | x secp256k1.Fn, 69 | ) (shamir.VerifiableShares, shamir.Commitment) { 70 | shares := make(shamir.VerifiableShares, len(indices)) 71 | com := shamir.NewCommitmentWithCapacity(k) 72 | shamir.VShareSecret(&shares, &com, indices, h, x, k) 73 | return shares, com 74 | } 75 | -------------------------------------------------------------------------------- /rkpg/state.go: -------------------------------------------------------------------------------- 1 | package rkpg 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/rng/rngutil" 8 | "github.com/renproject/secp256k1" 9 | "github.com/renproject/surge" 10 | ) 11 | 12 | // State represents the current state of an instance of the RKPG protocol. 13 | type State struct { 14 | count int32 15 | shareReceived []bool 16 | buffers [][]secp256k1.Fn 17 | } 18 | 19 | // NewState constructs a new state object for n players with a batch size of b. 20 | func NewState(n, b int) State { 21 | count := int32(0) 22 | shareReceived := make([]bool, n) 23 | buffers := make([][]secp256k1.Fn, b) 24 | for i := range buffers { 25 | buffers[i] = make([]secp256k1.Fn, n) 26 | } 27 | 28 | return State{ 29 | count: count, 30 | shareReceived: shareReceived, 31 | buffers: buffers, 32 | } 33 | } 34 | 35 | // Generate implements the quick.Generator interface. 36 | func (state State) Generate(_ *rand.Rand, size int) reflect.Value { 37 | b := rand.Intn(size + 1) 38 | n := size / rngutil.Max(b, 1) 39 | count := rand.Int31n(int32(n)) 40 | shareReceived := make([]bool, n) 41 | for i := range shareReceived { 42 | shareReceived[i] = rand.Int()&1 == 1 43 | } 44 | buffers := make([][]secp256k1.Fn, b) 45 | for i := range buffers { 46 | buffers[i] = make([]secp256k1.Fn, n) 47 | for j := range buffers[i] { 48 | buffers[i][j] = secp256k1.RandomFn() 49 | } 50 | } 51 | s := State{ 52 | count: count, 53 | shareReceived: shareReceived, 54 | buffers: buffers, 55 | } 56 | return reflect.ValueOf(s) 57 | } 58 | 59 | // SizeHint implements the surge.SizeHinter interface. 60 | func (state State) SizeHint() int { 61 | return surge.SizeHint(int32(state.count)) + 62 | surge.SizeHint(state.shareReceived) + 63 | surge.SizeHint(state.buffers) 64 | } 65 | 66 | // Marshal implements the surge.Marshaler interface. 67 | func (state State) Marshal(buf []byte, rem int) ([]byte, int, error) { 68 | buf, rem, err := surge.MarshalI32(int32(state.count), buf, rem) 69 | if err != nil { 70 | return buf, rem, err 71 | } 72 | buf, rem, err = surge.Marshal(state.shareReceived, buf, rem) 73 | if err != nil { 74 | return buf, rem, err 75 | } 76 | buf, rem, err = surge.Marshal(state.buffers, buf, rem) 77 | return buf, rem, err 78 | } 79 | 80 | // Unmarshal implements the surge.Unmarshaler interface. 81 | func (state *State) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 82 | buf, rem, err := surge.UnmarshalI32(&state.count, buf, rem) 83 | if err != nil { 84 | return buf, rem, err 85 | } 86 | buf, rem, err = surge.Unmarshal(&state.shareReceived, buf, rem) 87 | if err != nil { 88 | return buf, rem, err 89 | } 90 | return surge.Unmarshal(&state.buffers, buf, rem) 91 | } 92 | -------------------------------------------------------------------------------- /rng/compute/compute.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | import ( 4 | "github.com/renproject/secp256k1" 5 | "github.com/renproject/shamir" 6 | ) 7 | 8 | // ShareCommitment accepts the set of commitments and computes a weighted 9 | // linear combination of those commitments. This accumulated value represents 10 | // the commitment for the share of the final unbiased random number for the 11 | // given index. 12 | // 13 | // Panics: This function panics if the length of the slice of commitments is 14 | // less than 1. 15 | func ShareCommitment(index secp256k1.Fn, coms []shamir.Commitment) shamir.Commitment { 16 | var acc shamir.Commitment 17 | 18 | acc.Set(coms[len(coms)-1]) 19 | for l := len(coms) - 2; l >= 0; l-- { 20 | acc.Scale(acc, &index) 21 | acc.Add(acc, coms[l]) 22 | } 23 | 24 | return acc 25 | } 26 | 27 | // ShareOfShare accepts the set of verifiable shares and computes a weighted 28 | // linear combination of those shares. Assuming that the input shares' secrets 29 | // are coefficients of a polynomial, the output share is a share of this 30 | // polynomial evaluated at the given index. 31 | // 32 | // Panics: This function panics if the length of the slice of commitments is 33 | // less than 1. 34 | func ShareOfShare(index secp256k1.Fn, vshares shamir.VerifiableShares) shamir.VerifiableShare { 35 | acc := vshares[len(vshares)-1] 36 | for l := len(vshares) - 2; l >= 0; l-- { 37 | acc.Scale(&acc, &index) 38 | acc.Add(&acc, &vshares[l]) 39 | } 40 | 41 | return acc 42 | } 43 | -------------------------------------------------------------------------------- /rng/compute/compute_suite_test.go: -------------------------------------------------------------------------------- 1 | package compute_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestCompute(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Compute Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rng/compute/compute_test.go: -------------------------------------------------------------------------------- 1 | package compute_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | . "github.com/renproject/mpc/rng/compute" 7 | "github.com/renproject/secp256k1" 8 | 9 | "github.com/renproject/shamir" 10 | ) 11 | 12 | var _ = Describe("RNG computation helper functions", func() { 13 | trials := 50 14 | k := 5 15 | 16 | polyEval := func(x secp256k1.Fn, coeffs []secp256k1.Fn) secp256k1.Fn { 17 | acc := coeffs[len(coeffs)-1] 18 | 19 | for i := len(coeffs) - 2; i >= 0; i-- { 20 | acc.Mul(&acc, &x) 21 | acc.Add(&acc, &coeffs[i]) 22 | } 23 | 24 | return acc 25 | } 26 | 27 | Specify("commitments for shares should be computed correctly", func() { 28 | var index secp256k1.Fn 29 | 30 | coeffs := make([][]secp256k1.Fn, k) 31 | for i := range coeffs { 32 | coeffs[i] = make([]secp256k1.Fn, k) 33 | } 34 | 35 | points := make([][]secp256k1.Point, k) 36 | for i := range points { 37 | points[i] = make([]secp256k1.Point, k) 38 | } 39 | 40 | coms := make([]shamir.Commitment, k) 41 | for i := range coms { 42 | coms[i] = shamir.NewCommitmentWithCapacity(k) 43 | } 44 | 45 | // The idea for this test is to compute the output in two ways. The 46 | // first way is simply using the function that we are testing. The 47 | // second way is to compute the result for the scalar type (rather than 48 | // the elliptice curve point type), and then exponentiate this result 49 | // to obtain the corresponding curve point. In other words, we are 50 | // checking that evaluating the polynomial in the exponent (i.e. 51 | // computing on the commitments) is the same as evaluating the 52 | // polynomial, and then obtaining the corresponding curve point (i.e. 53 | // the result in the exponent). 54 | 55 | for i := 0; i < trials; i++ { 56 | index = secp256k1.RandomFn() 57 | 58 | for j := range coeffs { 59 | for l := range coeffs[j] { 60 | coeffs[j][l] = secp256k1.RandomFn() 61 | points[j][l].BaseExp(&coeffs[j][l]) 62 | } 63 | } 64 | 65 | for j := range coms { 66 | coms[j].Set(shamir.Commitment{}) 67 | for l := range points[j] { 68 | coms[j].Append(points[l][j]) 69 | } 70 | } 71 | 72 | output := ShareCommitment(index, coms) 73 | 74 | expected := secp256k1.Point{} 75 | for j := 0; j < output.Len(); j++ { 76 | y := polyEval(index, coeffs[j]) 77 | 78 | actual := output[j] 79 | expected.BaseExp(&y) 80 | 81 | Expect(actual.Eq(&expected)).To(BeTrue()) 82 | } 83 | } 84 | }) 85 | 86 | Specify("shares of shares should be computed correctly", func() { 87 | var to, from secp256k1.Fn 88 | 89 | values := make([]secp256k1.Fn, k) 90 | decoms := make([]secp256k1.Fn, k) 91 | vshares := make(shamir.VerifiableShares, k) 92 | 93 | for i := 0; i < trials; i++ { 94 | to = secp256k1.RandomFn() 95 | from = secp256k1.RandomFn() 96 | 97 | for j := 0; j < k; j++ { 98 | values[j] = secp256k1.RandomFn() 99 | decoms[j] = secp256k1.RandomFn() 100 | } 101 | 102 | for j := range vshares { 103 | vshares[j] = shamir.NewVerifiableShare( 104 | shamir.NewShare(from, values[j]), 105 | decoms[j], 106 | ) 107 | } 108 | 109 | output := ShareOfShare(to, vshares) 110 | 111 | // The value of the share should be correct. 112 | actual := output.Share.Value 113 | expected := polyEval(to, values) 114 | 115 | Expect(actual.Eq(&expected)).To(BeTrue()) 116 | 117 | // The decommitment of the share should be correct. 118 | actual = output.Decommitment 119 | expected = polyEval(to, decoms) 120 | 121 | Expect(actual.Eq(&expected)).To(BeTrue()) 122 | } 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /rng/event.go: -------------------------------------------------------------------------------- 1 | package rng 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrSharesIgnored represents the event returned when the RNG state machine 7 | // received `b` sets of verifiable shares that were invalid in some way 8 | ErrSharesIgnored = errors.New("shares ignored") 9 | ) 10 | -------------------------------------------------------------------------------- /rng/marshal.go: -------------------------------------------------------------------------------- 1 | package rng 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "reflect" 7 | 8 | "github.com/renproject/mpc/open" 9 | "github.com/renproject/secp256k1" 10 | ) 11 | 12 | // SizeHint implements the surge.SizeHinter interface. 13 | func (rnger RNGer) SizeHint() int { 14 | return rnger.index.SizeHint() + 15 | rnger.opener.SizeHint() 16 | } 17 | 18 | // Marshal implements the surge.Marshaler interface. 19 | func (rnger RNGer) Marshal(buf []byte, rem int) ([]byte, int, error) { 20 | buf, rem, err := rnger.index.Marshal(buf, rem) 21 | if err != nil { 22 | return buf, rem, fmt.Errorf("marshaling index: %v", err) 23 | } 24 | buf, rem, err = rnger.opener.Marshal(buf, rem) 25 | if err != nil { 26 | return buf, rem, fmt.Errorf("marshaling opener: %v", err) 27 | } 28 | return buf, rem, nil 29 | } 30 | 31 | // Unmarshal implements the surge.Unmarshaler interface. 32 | func (rnger *RNGer) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 33 | buf, rem, err := rnger.index.Unmarshal(buf, rem) 34 | if err != nil { 35 | return buf, rem, fmt.Errorf("unmarshaling index: %v", err) 36 | } 37 | buf, rem, err = rnger.opener.Unmarshal(buf, rem) 38 | if err != nil { 39 | return buf, rem, fmt.Errorf("unmarshaling opener: %v", err) 40 | } 41 | return buf, rem, nil 42 | } 43 | 44 | // Generate implements the quick.Generator interface. 45 | func (rnger RNGer) Generate(rand *rand.Rand, size int) reflect.Value { 46 | index := secp256k1.RandomFn() 47 | opener := open.Opener{}.Generate(rand, size).Interface().(open.Opener) 48 | return reflect.ValueOf(RNGer{index, opener}) 49 | } 50 | -------------------------------------------------------------------------------- /rng/marshal_test.go: -------------------------------------------------------------------------------- 1 | package rng_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/mpc/rng" 8 | "github.com/renproject/surge/surgeutil" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | t := reflect.TypeOf(rng.RNGer{}) 17 | 18 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 19 | It("should be the same after marshalling and unmarshalling", func() { 20 | for i := 0; i < trials; i++ { 21 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 22 | } 23 | }) 24 | 25 | It("should not panic when fuzzing", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 28 | } 29 | }) 30 | 31 | Context("marshalling", func() { 32 | It("should return an error when the buffer is too small", func() { 33 | for i := 0; i < trials; i++ { 34 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 35 | } 36 | }) 37 | 38 | It("should return an error when the memory quota is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | }) 44 | 45 | Context("unmarshalling", func() { 46 | It("should return an error when the buffer is too small", func() { 47 | for i := 0; i < trials; i++ { 48 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 49 | } 50 | }) 51 | 52 | It("should return an error when the memory quota is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /rng/rng.go: -------------------------------------------------------------------------------- 1 | package rng 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | 9 | "github.com/renproject/mpc/open" 10 | "github.com/renproject/mpc/params" 11 | "github.com/renproject/mpc/rng/compute" 12 | ) 13 | 14 | // An RNGer implements the RNG or RZG protocol. 15 | type RNGer struct { 16 | index secp256k1.Fn 17 | opener open.Opener 18 | } 19 | 20 | // New creates a new intance of a state machine that carries out either the RNG 21 | // or RZG protocol. Which one of these two cases is instantiated is determined 22 | // by the isZero argument. The share and commitment batch arguments are outputs 23 | // from the BRNG protocol; they are expected to be valid, and if the given 24 | // shares are nil then they will be ignored. Along with the state machine, the 25 | // initial messages to be sent to the other parties are returned, which is a 26 | // map indexed by the index of the player that the message is destined for. If 27 | // this function is called with a nil share batch, this returned map will also 28 | // be nil, as the initial messages cannot be computed without input shares. 29 | // 30 | // Panics: This function will panic in the following cases. 31 | // - The batch size is less than 1. 32 | // - The batch size of the BRNG outputs is less than 1 in the case of RZG, or 33 | // less than 2 in the case of RNG. 34 | // - Not all batch sizes of the BRNG outputs (both commitments and shares) are 35 | // the same. 36 | // - Not all commitments have the correct threshold (k). 37 | // - The shares and commitments have a different batch size. 38 | func New( 39 | ownIndex secp256k1.Fn, 40 | indices []secp256k1.Fn, 41 | h secp256k1.Point, 42 | brngShareBatch []shamir.VerifiableShares, 43 | brngCommitmentBatch [][]shamir.Commitment, 44 | isZero bool, 45 | ) (RNGer, map[secp256k1.Fn]shamir.VerifiableShares, []shamir.Commitment) { 46 | if !params.ValidPedersenParameter(h) { 47 | panic("insecure choice of pedersen parameter") 48 | } 49 | b := uint32(len(brngCommitmentBatch)) 50 | if b <= 0 { 51 | panic(fmt.Sprintf("b must be greater than 0, got: %v", b)) 52 | } 53 | k := uint32(len(brngCommitmentBatch[0])) 54 | if isZero { 55 | k++ 56 | } 57 | if k <= 1 { 58 | panic(fmt.Sprintf("k must be greater than 1, got: %v", k)) 59 | } 60 | threshold := brngCommitmentBatch[0][0].Len() 61 | if threshold < 2 { 62 | panic(fmt.Sprintf("threshold must be greater than 1, got: %v", threshold)) 63 | } 64 | 65 | var requiredBrngBatchSize int 66 | if isZero { 67 | // The constant term of the polynomial is zero so we don't need a share 68 | // for it. 69 | requiredBrngBatchSize = int(k - 1) 70 | } else { 71 | requiredBrngBatchSize = int(k) 72 | } 73 | 74 | for _, commitments := range brngCommitmentBatch { 75 | if len(commitments) != requiredBrngBatchSize { 76 | panic("invalid commitment dimensions") 77 | } 78 | for _, commitment := range commitments { 79 | if commitment.Len() != threshold { 80 | panic(fmt.Sprintf( 81 | "inconsistent commitment threshold: expected %v, got %v", 82 | threshold, commitment.Len(), 83 | )) 84 | } 85 | } 86 | } 87 | 88 | // If the supplied shares are nil, they are to be ignored (this means that 89 | // the output from BRNG was not valid for this player). Any non-nil slice 90 | // of shares is assumed to be valid. 91 | ignoreShares := brngShareBatch == nil 92 | 93 | if !ignoreShares { 94 | if len(brngShareBatch) != int(b) { 95 | panic(fmt.Sprintf( 96 | "incorrect share batch size: expected %v (commitments), got %v\n", 97 | b, len(brngShareBatch), 98 | )) 99 | } 100 | 101 | // Each set of shares in the batch should have the correct length. 102 | for _, shares := range brngShareBatch { 103 | if len(shares) != requiredBrngBatchSize { 104 | panic("invalid set of shares") 105 | } 106 | } 107 | } 108 | 109 | ownCommitments := make([]shamir.Commitment, b) 110 | outputCommitments := make([]shamir.Commitment, b) 111 | for i, setOfCommitments := range brngCommitmentBatch { 112 | // Compute the output commitment. 113 | outputCommitments[i] = shamir.NewCommitmentWithCapacity(int(k)) 114 | if isZero { 115 | outputCommitments[i].Append(secp256k1.NewPointInfinity()) 116 | } 117 | 118 | for _, c := range setOfCommitments { 119 | outputCommitments[i].Append(c[0]) 120 | } 121 | 122 | // Compute the share commitment and add it to the local set of 123 | // outputCommitments. 124 | accCommitment := compute.ShareCommitment(ownIndex, setOfCommitments) 125 | if isZero { 126 | accCommitment.Scale(accCommitment, &ownIndex) 127 | } 128 | 129 | ownCommitments[i].Set(accCommitment) 130 | } 131 | opener := open.New(ownCommitments, indices, h) 132 | 133 | // If the sets of shares are valid, construct the directed openings to 134 | // other players in the network. 135 | var directedOpenings map[secp256k1.Fn]shamir.VerifiableShares = nil 136 | if !ignoreShares { 137 | directedOpenings = make(map[secp256k1.Fn]shamir.VerifiableShares, len(indices)) 138 | for _, j := range indices { 139 | for _, setOfShares := range brngShareBatch { 140 | accShare := compute.ShareOfShare(j, setOfShares) 141 | if isZero { 142 | accShare.Scale(&accShare, &j) 143 | } 144 | directedOpenings[j] = append(directedOpenings[j], accShare) 145 | } 146 | } 147 | 148 | // Handle own share. 149 | secrets, decommitments, err := opener.HandleShareBatch(directedOpenings[ownIndex]) 150 | if err != nil { 151 | panic(fmt.Sprintf("unexpected error: %v", err)) 152 | } 153 | if secrets != nil || decommitments != nil { 154 | panic("opener should not have reconstructed after one share") 155 | } 156 | } 157 | 158 | rnger := RNGer{ 159 | index: ownIndex, 160 | opener: opener, 161 | } 162 | 163 | return rnger, directedOpenings, outputCommitments 164 | } 165 | 166 | // HandleShareBatch handles a batch of shares received from another player. If 167 | // the share batch was invalid in any way, an error will be returned. If the 168 | // given share batch was the kth valid batch to be received, reconstruction is 169 | // possible and the return value will be the reconstructed secrets. Otherwise, 170 | // the return value will be nil. 171 | func (rnger *RNGer) HandleShareBatch(shareBatch shamir.VerifiableShares) (shamir.VerifiableShares, error) { 172 | secrets, decommitments, err := rnger.opener.HandleShareBatch(shareBatch) 173 | if err != nil { 174 | return nil, err 175 | } 176 | if secrets == nil { 177 | return nil, nil 178 | } 179 | shares := make(shamir.VerifiableShares, len(secrets)) 180 | for i, secret := range secrets { 181 | share := shamir.NewShare(rnger.index, secret) 182 | shares[i] = shamir.NewVerifiableShare(share, decommitments[i]) 183 | } 184 | return shares, nil 185 | } 186 | -------------------------------------------------------------------------------- /rng/rng_suite_test.go: -------------------------------------------------------------------------------- 1 | package rng_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestRng(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Rng Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rng/rng_test.go: -------------------------------------------------------------------------------- 1 | package rng_test 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "github.com/renproject/secp256k1" 11 | "github.com/renproject/shamir" 12 | "github.com/renproject/shamir/shamirutil" 13 | 14 | "github.com/renproject/mpc/mpcutil" 15 | "github.com/renproject/mpc/rng/rngutil" 16 | ) 17 | 18 | var _ = Describe("RNG", func() { 19 | rand.Seed(int64(time.Now().Nanosecond())) 20 | 21 | Describe("Network Simulation", func() { 22 | var n, b, k, nOffline int 23 | var indices []secp256k1.Fn 24 | var h secp256k1.Point 25 | var isZero bool 26 | var ids []mpcutil.ID 27 | var setsOfSharesByPlayer map[secp256k1.Fn][]shamir.VerifiableShares 28 | var setsOfCommitmentsByPlayer [][]shamir.Commitment 29 | var shuffleMsgs func([]mpcutil.Message) 30 | var isOffline map[mpcutil.ID]bool 31 | var machines []mpcutil.Machine 32 | 33 | CheckMachines := func( 34 | machines []mpcutil.Machine, 35 | isOffline map[mpcutil.ID]bool, 36 | b, k int, 37 | h secp256k1.Point, 38 | ) { 39 | // ID of the first online machine 40 | i := 0 41 | for isOffline[machines[i].ID()] { 42 | i = i + 1 43 | } 44 | 45 | // Get the unbiased random numbers calculated by that RNG machine 46 | referenceRNShares := machines[i].(*rngutil.RngMachine).RandomNumbersShares() 47 | referenceCommitments := machines[i].(*rngutil.RngMachine).Commitments() 48 | 49 | for j := i + 1; j < len(machines); j++ { 50 | // Ignore if that machine is offline 51 | if isOffline[machines[j].ID()] { 52 | continue 53 | } 54 | 55 | rnShares := machines[j].(*rngutil.RngMachine).RandomNumbersShares() 56 | rnCommitments := machines[j].(*rngutil.RngMachine).Commitments() 57 | Expect(len(referenceRNShares)).To(Equal(len(rnShares))) 58 | 59 | // Every player has computed the same commitments 60 | for l, c := range rnCommitments { 61 | Expect(c.Eq(referenceCommitments[l])).To(BeTrue()) 62 | } 63 | 64 | // Verify that each machine's share is valid with respect to 65 | // the reference commitments 66 | for l, vshare := range rnShares { 67 | Expect(shamir.IsValid(h, &rnCommitments[l], &vshare)).To(BeTrue()) 68 | } 69 | } 70 | 71 | // For every batch in batch size, the shares that every player has 72 | // should be consistent 73 | for i := 0; i < b; i++ { 74 | shares := make(shamir.Shares, 0, len(machines)) 75 | 76 | for j := 0; j < len(machines); j++ { 77 | if isOffline[machines[j].ID()] { 78 | continue 79 | } 80 | 81 | vshare := machines[j].(*rngutil.RngMachine).RandomNumbersShares()[i] 82 | shares = append(shares, vshare.Share) 83 | } 84 | 85 | Expect(shamirutil.SharesAreConsistent(shares, k-1)).ToNot(BeTrue()) 86 | Expect(shamirutil.SharesAreConsistent(shares, k)).To(BeTrue()) 87 | } 88 | } 89 | 90 | Setup := func() { 91 | // Randomise RNG network scenario 92 | n = 15 + rand.Intn(6) 93 | indices = shamirutil.SequentialIndices(n) 94 | b = 3 + rand.Intn(3) 95 | k = rngutil.Min(3+rand.Intn(n-3), 7) 96 | h = secp256k1.RandomPoint() 97 | isZero = false 98 | 99 | // Machines (players) participating in the RNG protocol 100 | ids = make([]mpcutil.ID, n) 101 | 102 | // Get BRNG outputs for all players 103 | setsOfSharesByPlayer, setsOfCommitmentsByPlayer = 104 | rngutil.BRNGOutputFullBatch(indices, b, k, k, h) 105 | 106 | // Append machine IDs and get offline machines 107 | for i := range indices { 108 | id := mpcutil.ID(i) 109 | ids[i] = id 110 | } 111 | nOffline = rand.Intn(n - k + 1) 112 | shuffleMsgs, isOffline = mpcutil.MessageShufflerDropper(ids, nOffline) 113 | } 114 | 115 | MakeMachines := func() { 116 | machines = make([]mpcutil.Machine, n) 117 | for i, index := range indices { 118 | rngMachine := rngutil.NewRngMachine( 119 | mpcutil.ID(i), index, indices, b, k, h, isZero, 120 | setsOfSharesByPlayer[index], 121 | setsOfCommitmentsByPlayer, 122 | ) 123 | machines[i] = &rngMachine 124 | } 125 | } 126 | 127 | BeforeEach(func() { 128 | Setup() 129 | }) 130 | 131 | Specify("RNG machines should reconstruct the consistent shares for random numbers", func() { 132 | MakeMachines() 133 | network := mpcutil.NewNetwork(machines, shuffleMsgs) 134 | network.SetCaptureHist(true) 135 | 136 | err := network.Run() 137 | Expect(err).ToNot(HaveOccurred()) 138 | 139 | CheckMachines(machines, isOffline, b, k, h) 140 | }) 141 | 142 | Specify("With not all RNG machines contributing their BRNG shares", func() { 143 | // Mark some machines as being idle specifically, at the most k+1 144 | // should not be idle so (n - nOffline) - k - 1 should be idle 145 | // because only (n - nOffline) machines are online 146 | idleCount := 0 147 | for j, index := range indices { 148 | if isOffline[mpcutil.ID(j)] { 149 | continue 150 | } 151 | if idleCount == rngutil.Max(0, (n-nOffline)-k-1) { 152 | break 153 | } 154 | 155 | setsOfSharesByPlayer[index] = nil 156 | idleCount++ 157 | } 158 | 159 | MakeMachines() 160 | network := mpcutil.NewNetwork(machines, shuffleMsgs) 161 | network.SetCaptureHist(true) 162 | 163 | err := network.Run() 164 | Expect(err).ToNot(HaveOccurred()) 165 | 166 | CheckMachines(machines, isOffline, b, k, h) 167 | }) 168 | }) 169 | }) 170 | -------------------------------------------------------------------------------- /rng/rngutil/machine.go: -------------------------------------------------------------------------------- 1 | package rngutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | "github.com/renproject/surge" 9 | 10 | "github.com/renproject/mpc/mpcutil" 11 | 12 | "github.com/renproject/mpc/rng" 13 | ) 14 | 15 | // RngMachine type represents the structure of an RNG machine 16 | // in the execution of the RNG protocol 17 | type RngMachine struct { 18 | id mpcutil.ID 19 | index secp256k1.Fn 20 | indices []secp256k1.Fn 21 | rnger rng.RNGer 22 | 23 | directedOpenings map[secp256k1.Fn]shamir.VerifiableShares 24 | outputShares shamir.VerifiableShares 25 | outputCommitments []shamir.Commitment 26 | } 27 | 28 | // NewRngMachine creates a new instance of RNG machine 29 | // and transitions it to the WaitingOpen state by supplying its own shares 30 | func NewRngMachine( 31 | id mpcutil.ID, 32 | index secp256k1.Fn, 33 | indices []secp256k1.Fn, 34 | b, k int, 35 | h secp256k1.Point, 36 | isZero bool, 37 | ownSetsOfShares []shamir.VerifiableShares, 38 | ownSetsOfCommitments [][]shamir.Commitment, 39 | ) RngMachine { 40 | rnger, directedOpenings, commitments := 41 | rng.New(index, indices, h, ownSetsOfShares, ownSetsOfCommitments, isZero) 42 | 43 | return RngMachine{ 44 | id: id, 45 | index: index, 46 | indices: indices, 47 | rnger: rnger, 48 | 49 | directedOpenings: directedOpenings, 50 | outputShares: nil, 51 | outputCommitments: commitments, 52 | } 53 | } 54 | 55 | // ID returns the index of the RNG machine in the list of machines 56 | func (machine RngMachine) ID() mpcutil.ID { 57 | return machine.id 58 | } 59 | 60 | // Index returns the index assigned to the machine in the network of RNG machines 61 | func (machine RngMachine) Index() secp256k1.Fn { 62 | return machine.index 63 | } 64 | 65 | // RandomNumbersShares returns the reconstructed shares for the 66 | // unbiased random numbers. 67 | func (machine RngMachine) RandomNumbersShares() shamir.VerifiableShares { 68 | return machine.outputShares 69 | } 70 | 71 | // Commitments returns the commitments for the batch of unbiased random numbers 72 | func (machine RngMachine) Commitments() []shamir.Commitment { 73 | return machine.outputCommitments 74 | } 75 | 76 | // InitialMessages implements the interface as required by a Network machine 77 | // It returns the initial messages to be sent by a machine to another machine 78 | // participating in the said protocol 79 | func (machine RngMachine) InitialMessages() []mpcutil.Message { 80 | messages := make([]mpcutil.Message, 0, len(machine.indices)-1) 81 | for i, to := range machine.indices { 82 | if machine.id == mpcutil.ID(i) { 83 | continue 84 | } 85 | 86 | openings := machine.directedOpenings[to] 87 | messages = append(messages, &RngMessage{ 88 | from: machine.id, 89 | to: mpcutil.ID(i), 90 | fromIndex: machine.index, 91 | openings: openings, 92 | }) 93 | } 94 | 95 | return messages 96 | } 97 | 98 | // Handle implements the interface as required by a Network machine 99 | // It receives a message sent by another machine participating in the said 100 | // protocol, and handles the message appropriately, and returns response 101 | // messages if required 102 | func (machine *RngMachine) Handle(msg mpcutil.Message) []mpcutil.Message { 103 | switch msg := msg.(type) { 104 | case *RngMessage: 105 | shares, _ := machine.rnger.HandleShareBatch(msg.openings) 106 | if shares != nil { 107 | machine.outputShares = shares 108 | } 109 | return nil 110 | 111 | default: 112 | panic("unexpected message type") 113 | } 114 | } 115 | 116 | // SizeHint implements surge SizeHinter 117 | func (machine RngMachine) SizeHint() int { 118 | return machine.id.SizeHint() + 119 | machine.index.SizeHint() + 120 | surge.SizeHint(machine.indices) + 121 | machine.rnger.SizeHint() 122 | } 123 | 124 | // Marshal implements surge Marshaler 125 | func (machine RngMachine) Marshal(buf []byte, rem int) ([]byte, int, error) { 126 | buf, rem, err := machine.id.Marshal(buf, rem) 127 | if err != nil { 128 | return buf, rem, fmt.Errorf("marshaling id: %v", err) 129 | } 130 | buf, rem, err = machine.index.Marshal(buf, rem) 131 | if err != nil { 132 | return buf, rem, fmt.Errorf("marshaling index: %v", err) 133 | } 134 | buf, rem, err = surge.Marshal(machine.indices, buf, rem) 135 | if err != nil { 136 | return buf, rem, fmt.Errorf("marshaling indices: %v", err) 137 | } 138 | buf, rem, err = machine.rnger.Marshal(buf, rem) 139 | if err != nil { 140 | return buf, rem, fmt.Errorf("marshaling rnger: %v", err) 141 | } 142 | return buf, rem, nil 143 | } 144 | 145 | // Unmarshal implements surge Unmarshaler 146 | func (machine *RngMachine) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 147 | buf, rem, err := machine.id.Unmarshal(buf, rem) 148 | if err != nil { 149 | return buf, rem, fmt.Errorf("unmarshaling id: %v", err) 150 | } 151 | buf, rem, err = machine.index.Unmarshal(buf, rem) 152 | if err != nil { 153 | return buf, rem, fmt.Errorf("unmarshaling index: %v", err) 154 | } 155 | buf, rem, err = surge.Unmarshal(&machine.indices, buf, rem) 156 | if err != nil { 157 | return buf, rem, fmt.Errorf("unmarshaling indices: %v", err) 158 | } 159 | buf, rem, err = machine.rnger.Unmarshal(buf, rem) 160 | if err != nil { 161 | return buf, rem, fmt.Errorf("unmarshaling rnger: %v", err) 162 | } 163 | return buf, rem, nil 164 | } 165 | -------------------------------------------------------------------------------- /rng/rngutil/message.go: -------------------------------------------------------------------------------- 1 | package rngutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | 9 | "github.com/renproject/mpc/mpcutil" 10 | ) 11 | 12 | // RngMessage type represents the message structure in the RNG protocol 13 | type RngMessage struct { 14 | from, to mpcutil.ID 15 | fromIndex secp256k1.Fn 16 | openings shamir.VerifiableShares 17 | } 18 | 19 | // From returns the player ID of message sender 20 | func (msg RngMessage) From() mpcutil.ID { 21 | return msg.from 22 | } 23 | 24 | // To returns the player ID of message recipient 25 | func (msg RngMessage) To() mpcutil.ID { 26 | return msg.to 27 | } 28 | 29 | // SizeHint implements surge SizeHinter 30 | func (msg RngMessage) SizeHint() int { 31 | return msg.from.SizeHint() + 32 | msg.to.SizeHint() + 33 | msg.fromIndex.SizeHint() + 34 | msg.openings.SizeHint() 35 | } 36 | 37 | // Marshal implements surge Marshaler 38 | func (msg RngMessage) Marshal(buf []byte, rem int) ([]byte, int, error) { 39 | buf, rem, err := msg.from.Marshal(buf, rem) 40 | if err != nil { 41 | return buf, rem, fmt.Errorf("marshaling from: %v", err) 42 | } 43 | buf, rem, err = msg.to.Marshal(buf, rem) 44 | if err != nil { 45 | return buf, rem, fmt.Errorf("marshaling to: %v", err) 46 | } 47 | buf, rem, err = msg.fromIndex.Marshal(buf, rem) 48 | if err != nil { 49 | return buf, rem, fmt.Errorf("marshaling fromIndex: %v", err) 50 | } 51 | buf, rem, err = msg.openings.Marshal(buf, rem) 52 | if err != nil { 53 | return buf, rem, fmt.Errorf("marshaling openings: %v", err) 54 | } 55 | 56 | return buf, rem, nil 57 | } 58 | 59 | // Unmarshal implements surge Unmarshaler 60 | func (msg *RngMessage) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 61 | buf, rem, err := msg.from.Unmarshal(buf, rem) 62 | if err != nil { 63 | return buf, rem, fmt.Errorf("unmarshaling from: %v", err) 64 | } 65 | buf, rem, err = msg.to.Unmarshal(buf, rem) 66 | if err != nil { 67 | return buf, rem, fmt.Errorf("unmarshaling to: %v", err) 68 | } 69 | buf, rem, err = msg.fromIndex.Unmarshal(buf, rem) 70 | if err != nil { 71 | return buf, rem, fmt.Errorf("unmarshaling fromIndex: %v", err) 72 | } 73 | buf, rem, err = msg.openings.Unmarshal(buf, rem) 74 | if err != nil { 75 | return buf, rem, fmt.Errorf("unmarshaling openings: %v", err) 76 | } 77 | 78 | return buf, rem, nil 79 | } 80 | -------------------------------------------------------------------------------- /rng/rngutil/testutil.go: -------------------------------------------------------------------------------- 1 | package rngutil 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/renproject/mpc/rng/compute" 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir" 9 | ) 10 | 11 | // Max returns the maximum of the two arguments 12 | func Max(a, b int) int { 13 | if a > b { 14 | return a 15 | } 16 | return b 17 | } 18 | 19 | // Min returns the minimum of the two arguments 20 | func Min(a, b int) int { 21 | if a < b { 22 | return a 23 | } 24 | return b 25 | } 26 | 27 | // RandomOtherIndex returns a random index from the list of given indices that 28 | // is not equal to the given index. 29 | func RandomOtherIndex(indices []secp256k1.Fn, avoid *secp256k1.Fn) secp256k1.Fn { 30 | index := indices[rand.Intn(len(indices))] 31 | for index.Eq(avoid) { 32 | index = indices[rand.Intn(len(indices))] 33 | } 34 | return index 35 | } 36 | 37 | // BRNGOutputBatch creates a random output for one player from BRNG, with the 38 | // given batch number. 39 | func BRNGOutputBatch(index secp256k1.Fn, b, c, k int, h secp256k1.Point) ( 40 | []shamir.VerifiableShares, 41 | [][]shamir.Commitment, 42 | ) { 43 | shares := make([]shamir.VerifiableShares, b) 44 | coms := make([][]shamir.Commitment, b) 45 | 46 | for i := 0; i < b; i++ { 47 | shares[i], coms[i] = BRNGOutput(index, c, k, h) 48 | } 49 | 50 | return shares, coms 51 | } 52 | 53 | // BRNGOutput creates a random output for one player from BRNG. 54 | func BRNGOutput(index secp256k1.Fn, b, k int, h secp256k1.Point) ( 55 | shamir.VerifiableShares, 56 | []shamir.Commitment, 57 | ) { 58 | shares := make(shamir.VerifiableShares, b) 59 | coms := make([]shamir.Commitment, b) 60 | 61 | gPow := secp256k1.Point{} 62 | hPow := secp256k1.Point{} 63 | sCoeffs := make([]secp256k1.Fn, k) 64 | rCoeffs := make([]secp256k1.Fn, k) 65 | for i := 0; i < b; i++ { 66 | for j := 0; j < k; j++ { 67 | sCoeffs[j] = secp256k1.RandomFn() 68 | rCoeffs[j] = secp256k1.RandomFn() 69 | 70 | gPow.BaseExp(&sCoeffs[j]) 71 | hPow.Scale(&h, &rCoeffs[j]) 72 | gPow.Add(&gPow, &hPow) 73 | coms[i].Append(gPow) 74 | } 75 | 76 | shares[i] = shamir.NewVerifiableShare(shamir.NewShare(index, sCoeffs[k-1]), rCoeffs[k-1]) 77 | for j := k - 2; j >= 0; j-- { 78 | share := shamir.NewVerifiableShare(shamir.NewShare(index, sCoeffs[j]), rCoeffs[j]) 79 | shares[i].Scale(&shares[i], &index) 80 | shares[i].Add(&shares[i], &share) 81 | } 82 | } 83 | 84 | return shares, coms 85 | } 86 | 87 | // BRNGOutputFullBatch creates a random output of BRNG for all players with the 88 | // given batch size. The returned map of shares is indexed by the index of the 89 | // player, and the returned commitments are the same for all players. 90 | func BRNGOutputFullBatch( 91 | indices []secp256k1.Fn, 92 | b, c, k int, 93 | h secp256k1.Point, 94 | ) ( 95 | map[secp256k1.Fn][]shamir.VerifiableShares, 96 | [][]shamir.Commitment, 97 | ) { 98 | n := len(indices) 99 | 100 | shares := make(map[secp256k1.Fn][]shamir.VerifiableShares, n) 101 | coms := make([][]shamir.Commitment, b) 102 | 103 | var shareBatch []shamir.VerifiableShares 104 | for i := 0; i < b; i++ { 105 | shareBatch, coms[i] = BRNGOutputFull(indices, c, k, h) 106 | 107 | for j, ind := range indices { 108 | shares[ind] = append(shares[ind], shareBatch[j]) 109 | } 110 | } 111 | 112 | return shares, coms 113 | } 114 | 115 | // BRNGOutputFull creates a random output of BRNG for all players. 116 | func BRNGOutputFull( 117 | indices []secp256k1.Fn, 118 | c, k int, 119 | h secp256k1.Point, 120 | ) ( 121 | []shamir.VerifiableShares, 122 | []shamir.Commitment, 123 | ) { 124 | n := len(indices) 125 | 126 | coefShares := make([]shamir.VerifiableShares, c) 127 | coefComms := make([]shamir.Commitment, c) 128 | 129 | for i := range coefShares { 130 | coefShares[i] = make(shamir.VerifiableShares, n) 131 | coefComms[i] = shamir.NewCommitmentWithCapacity(k) 132 | shamir.VShareSecret(&coefShares[i], &coefComms[i], indices, h, secp256k1.RandomFn(), k) 133 | } 134 | 135 | coefSharesTrans := make([]shamir.VerifiableShares, n) 136 | for i := range coefSharesTrans { 137 | coefSharesTrans[i] = make(shamir.VerifiableShares, c) 138 | } 139 | 140 | for i, sharing := range coefShares { 141 | for j, share := range sharing { 142 | coefSharesTrans[j][i] = share 143 | } 144 | } 145 | 146 | return coefSharesTrans, coefComms 147 | } 148 | 149 | // RNGSharesBatch creates random valid inputs for the RNG state machine for the 150 | // given batch size. The first two return values are the outputs from BRNG, and 151 | // the last two return values are the shares from the other players that the 152 | // player corresponding to `index` is expecting. 153 | func RNGSharesBatch( 154 | indices []secp256k1.Fn, 155 | index secp256k1.Fn, 156 | b, k, threshold int, 157 | h secp256k1.Point, 158 | isZero bool, 159 | ) ( 160 | []shamir.VerifiableShares, 161 | [][]shamir.Commitment, 162 | map[secp256k1.Fn]shamir.VerifiableShares, 163 | []shamir.Commitment, 164 | ) { 165 | n := len(indices) 166 | brngComs := make([][]shamir.Commitment, b) 167 | brngShares := make([]shamir.VerifiableShares, b) 168 | coms := make([]shamir.Commitment, b) 169 | shares := make(map[secp256k1.Fn]shamir.VerifiableShares, n) 170 | for _, ind := range indices { 171 | shares[ind] = make(shamir.VerifiableShares, b) 172 | } 173 | 174 | var rngShares shamir.VerifiableShares 175 | for i := 0; i < b; i++ { 176 | brngShares[i], brngComs[i], rngShares, coms[i] = RNGShares(indices, index, k, threshold, h, isZero) 177 | 178 | for j, share := range rngShares { 179 | shares[indices[j]][i] = share 180 | } 181 | } 182 | 183 | return brngShares, brngComs, shares, coms 184 | } 185 | 186 | // RNGShares creates random valid inputs for the RNG state machine. The first 187 | // two return values are the outputs from BRNG, and the last two return values 188 | // are the shares from the other players that the player corresponding to 189 | // `index` is expecting. 190 | func RNGShares( 191 | indices []secp256k1.Fn, 192 | index secp256k1.Fn, 193 | k, threshold int, 194 | h secp256k1.Point, 195 | isZero bool, 196 | ) (shamir.VerifiableShares, []shamir.Commitment, shamir.VerifiableShares, shamir.Commitment) { 197 | n := len(indices) 198 | var coefSharesTrans []shamir.VerifiableShares 199 | var coefComms []shamir.Commitment 200 | if isZero { 201 | coefSharesTrans, coefComms = BRNGOutputFull(indices, threshold-1, k, h) 202 | } else { 203 | coefSharesTrans, coefComms = BRNGOutputFull(indices, threshold, k, h) 204 | } 205 | 206 | com := compute.ShareCommitment(index, coefComms) 207 | if isZero { 208 | com.Scale(com, &index) 209 | } 210 | 211 | shares := make(shamir.VerifiableShares, n) 212 | for i := range shares { 213 | shares[i] = compute.ShareOfShare(index, coefSharesTrans[i]) 214 | if isZero { 215 | shares[i].Scale(&shares[i], &index) 216 | } 217 | } 218 | 219 | var ind int 220 | for i := range indices { 221 | if indices[i].Eq(&index) { 222 | ind = i 223 | } 224 | } 225 | 226 | return coefSharesTrans[ind], coefComms, shares, com 227 | } 228 | -------------------------------------------------------------------------------- /rng/rzg_test.go: -------------------------------------------------------------------------------- 1 | package rng_test 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "github.com/renproject/secp256k1" 11 | "github.com/renproject/shamir" 12 | "github.com/renproject/shamir/shamirutil" 13 | 14 | "github.com/renproject/mpc/mpcutil" 15 | "github.com/renproject/mpc/rng/rngutil" 16 | ) 17 | 18 | var _ = Describe("RZG", func() { 19 | rand.Seed(int64(time.Now().Nanosecond())) 20 | 21 | Describe("Network Simulation", func() { 22 | var ids []mpcutil.ID 23 | var machines []mpcutil.Machine 24 | var indices []secp256k1.Fn 25 | var network mpcutil.Network 26 | var shuffleMsgs func([]mpcutil.Message) 27 | var isOffline map[mpcutil.ID]bool 28 | var b, k int 29 | var h secp256k1.Point 30 | 31 | JustBeforeEach(func() { 32 | // Randomise RZG network scenario 33 | n := 5 + rand.Intn(6) 34 | indices = shamirutil.RandomIndices(n) 35 | b = 3 + rand.Intn(3) 36 | k = 3 + rand.Intn(n-3) 37 | h = secp256k1.RandomPoint() 38 | isZero := true 39 | 40 | // Machines (players) participating in the RZG protocol 41 | ids = make([]mpcutil.ID, n) 42 | machines = make([]mpcutil.Machine, n) 43 | 44 | // Get BRNG outputs for all players 45 | setsOfSharesByPlayer, setsOfCommitmentsByPlayer := 46 | rngutil.BRNGOutputFullBatch(indices, b, k-1, k, h) 47 | 48 | // Append machines to the network 49 | for i, index := range indices { 50 | id := mpcutil.ID(i) 51 | rngMachine := rngutil.NewRngMachine( 52 | id, index, indices, b, k, h, isZero, 53 | setsOfSharesByPlayer[index], 54 | setsOfCommitmentsByPlayer, 55 | ) 56 | machines[i] = &rngMachine 57 | ids[i] = id 58 | } 59 | 60 | nOffline := rand.Intn(n - k + 1) 61 | shuffleMsgs, isOffline = mpcutil.MessageShufflerDropper(ids, nOffline) 62 | network = mpcutil.NewNetwork(machines, shuffleMsgs) 63 | network.SetCaptureHist(true) 64 | }) 65 | 66 | Specify("RZG machines should reconstruct zero as all random numbers", func() { 67 | err := network.Run() 68 | Expect(err).ToNot(HaveOccurred()) 69 | 70 | // ID of the first online machine 71 | i := 0 72 | for isOffline[machines[i].ID()] { 73 | i = i + 1 74 | } 75 | 76 | // Get the unbiased random numbers calculated by that RZG machine 77 | referenceRNShares := machines[i].(*rngutil.RngMachine).RandomNumbersShares() 78 | referenceCommitments := machines[i].(*rngutil.RngMachine).Commitments() 79 | 80 | for j := i + 1; j < len(machines); j++ { 81 | // Ignore if that machine is offline 82 | if isOffline[machines[j].ID()] { 83 | continue 84 | } 85 | 86 | rnShares := machines[j].(*rngutil.RngMachine).RandomNumbersShares() 87 | rnCommitments := machines[j].(*rngutil.RngMachine).Commitments() 88 | Expect(len(referenceRNShares)).To(Equal(len(rnShares))) 89 | 90 | // Every player has computed the same commitments 91 | for l, c := range rnCommitments { 92 | Expect(c.Eq(referenceCommitments[l])).To(BeTrue()) 93 | } 94 | 95 | // Verify that each machine's share is valid with respect to 96 | // the reference commitments 97 | for l, vshare := range rnShares { 98 | Expect(shamir.IsValid(h, &rnCommitments[l], &vshare)).To(BeTrue()) 99 | } 100 | } 101 | 102 | // For every batch in batch size, the shares that every player has 103 | // should be consistent 104 | for i := 0; i < b; i++ { 105 | shares := make(shamir.Shares, 0, len(machines)) 106 | 107 | for j := 0; j < len(machines); j++ { 108 | if isOffline[machines[j].ID()] { 109 | continue 110 | } 111 | 112 | vshare := machines[j].(*rngutil.RngMachine).RandomNumbersShares()[i] 113 | shares = append(shares, vshare.Share) 114 | } 115 | 116 | Expect(shamirutil.SharesAreConsistent(shares, k-1)).ToNot(BeTrue()) 117 | Expect(shamirutil.SharesAreConsistent(shares, k)).To(BeTrue()) 118 | 119 | secret := shamir.Open(shares) 120 | Expect(secret.IsZero()).To(BeTrue()) 121 | } 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /rng/transition_test.go: -------------------------------------------------------------------------------- 1 | package rng_test 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "github.com/renproject/secp256k1" 11 | "github.com/renproject/shamir" 12 | "github.com/renproject/shamir/shamirutil" 13 | 14 | "github.com/renproject/mpc/rng" 15 | "github.com/renproject/mpc/rng/rngutil" 16 | ) 17 | 18 | var _ = Describe("RNG/RZG state transitions", func() { 19 | rand.Seed(int64(time.Now().Nanosecond())) 20 | 21 | RandomTestParameters := func(isZero bool) ( 22 | int, 23 | []secp256k1.Fn, 24 | secp256k1.Fn, 25 | int, 26 | int, 27 | int, 28 | secp256k1.Point, 29 | ) { 30 | // Number of players participating in the protocol 31 | n := 5 + rand.Intn(6) 32 | 33 | // List of player indices 34 | indices := shamirutil.RandomIndices(n) 35 | 36 | // Current player's index 37 | index := indices[rand.Intn(len(indices))] 38 | 39 | // Batch size 40 | b := 3 + rand.Intn(3) 41 | 42 | // Threshold of RNG outputs 43 | k := 3 + rand.Intn(n-3) 44 | 45 | c := 3 + rand.Intn(n-3) 46 | if isZero { 47 | c = k - 1 48 | } else { 49 | c = k 50 | } 51 | 52 | // Pedersen commitment scheme parameter 53 | h := secp256k1.RandomPoint() 54 | 55 | return n, indices, index, b, c, k, h 56 | } 57 | 58 | // Here false corresponds to RNG, and true corresponds to RZG. 59 | cases := [2]bool{false, true} 60 | 61 | for _, isZero := range cases { 62 | isZero := isZero 63 | 64 | Context("creating a new RNGer", func() { 65 | Specify("when given nil shares, no initial messages should be supplied", func() { 66 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 67 | _, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 68 | _, directedOpenings, _ := rng.New(index, indices, h, nil, brngCommitmentBatch, isZero) 69 | Expect(directedOpenings).To(BeNil()) 70 | }) 71 | 72 | Specify("when given non nil shares, the initial messages should not be nil", func() { 73 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 74 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 75 | _, directedOpenings, _ := rng.New( 76 | index, indices, h, brngShareBatch, brngCommitmentBatch, isZero, 77 | ) 78 | Expect(directedOpenings).ToNot(BeNil()) 79 | }) 80 | 81 | It("should correctly compute the shares and commitments", func() { 82 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 83 | ownSetsOfShares, ownSetsOfCommitments, openingsByPlayer, _ := 84 | rngutil.RNGSharesBatch(indices, index, b, k, c, h, isZero) 85 | _, directedOpenings, _ := rng.New( 86 | index, indices, h, ownSetsOfShares, ownSetsOfCommitments, isZero, 87 | ) 88 | 89 | selfOpenings := directedOpenings[index] 90 | for i, share := range selfOpenings { 91 | Expect(share.Eq(&openingsByPlayer[index][i])).To(BeTrue()) 92 | } 93 | }) 94 | }) 95 | 96 | Context("handling share batches", func() { 97 | Specify("invalid share batches should return an error", func() { 98 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 99 | ownSetsOfShares, ownSetsOfCommitments, openingsByPlayer, _ := 100 | rngutil.RNGSharesBatch(indices, index, b, k, c, h, isZero) 101 | rnger, _, _ := rng.New(index, indices, h, ownSetsOfShares, ownSetsOfCommitments, isZero) 102 | 103 | // Pick an index other than our own. 104 | from := indices[rand.Intn(len(indices))] 105 | for from.Eq(&index) { 106 | from = indices[rand.Intn(len(indices))] 107 | } 108 | 109 | // Incorrect shares batch length. 110 | _, err := rnger.HandleShareBatch(openingsByPlayer[from][1:]) 111 | Expect(err).To(HaveOccurred()) 112 | 113 | // Invalid share (random value). 114 | openingsByPlayer[from][rand.Intn(b)].Share.Value = secp256k1.RandomFn() 115 | _, err = rnger.HandleShareBatch(openingsByPlayer[from]) 116 | Expect(err).To(HaveOccurred()) 117 | }) 118 | 119 | Specify("upon receiving the kth valid share batch, the secrets should be reconstructed", func() { 120 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 121 | ownSetsOfShares, ownSetsOfCommitments, openingsByPlayer, _ := 122 | rngutil.RNGSharesBatch(indices, index, b, k, c, h, isZero) 123 | rnger, _, _ := rng.New(index, indices, h, ownSetsOfShares, ownSetsOfCommitments, isZero) 124 | 125 | // The own player's openings have already been processed. 126 | count := 1 127 | for _, from := range indices { 128 | if from.Eq(&index) { 129 | continue 130 | } 131 | 132 | outputShares, err := rnger.HandleShareBatch(openingsByPlayer[from]) 133 | Expect(err).ToNot(HaveOccurred()) 134 | count++ 135 | 136 | if count == k { 137 | Expect(outputShares).ToNot(BeNil()) 138 | Expect(len(outputShares)).To(Equal(b)) 139 | } else { 140 | Expect(outputShares).To(BeNil()) 141 | } 142 | } 143 | }) 144 | 145 | It("the reconstructed secrets should be valid with respect to the commitments", func() { 146 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 147 | ownSetsOfShares, ownSetsOfCommitments, openingsByPlayer, _ := 148 | rngutil.RNGSharesBatch(indices, index, b, k, c, h, isZero) 149 | rnger, _, commitments := rng.New( 150 | index, indices, h, ownSetsOfShares, ownSetsOfCommitments, isZero, 151 | ) 152 | 153 | var shares shamir.VerifiableShares 154 | for _, from := range indices { 155 | shares, _ = rnger.HandleShareBatch(openingsByPlayer[from]) 156 | if shares != nil { 157 | break 158 | } 159 | } 160 | 161 | // The reconstructed verifiable shares of the batch of unbiased 162 | // random numbers should be valid against the commitments for 163 | // those unbiased random numbers. 164 | for i, c := range commitments { 165 | Expect(shamir.IsValid(h, &c, &shares[i])).To(BeTrue()) 166 | } 167 | }) 168 | }) 169 | 170 | Context("panics", func() { 171 | Specify("insecure pedersen parameter", func() { 172 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 173 | inf := secp256k1.NewPointInfinity() 174 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 175 | Expect(func() { 176 | rng.New(index, indices, inf, brngShareBatch, brngCommitmentBatch, isZero) 177 | }).To(Panic()) 178 | }) 179 | 180 | Specify("too small commitment batch size", func() { 181 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 182 | brngShareBatch, _ := rngutil.BRNGOutputBatch(index, b, c, k, h) 183 | Expect(func() { 184 | rng.New(index, indices, h, brngShareBatch, [][]shamir.Commitment{}, isZero) 185 | }).To(Panic()) 186 | }) 187 | 188 | Specify("too small output threshold (k)", func() { 189 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 190 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 191 | // For RNG, the number of coefficients that are specified needs 192 | // to be at least 2, whereas for RZG it only needs to be at 193 | // least 1. 194 | if !isZero { 195 | brngCommitmentBatch[0] = brngCommitmentBatch[0][:1] 196 | } else { 197 | brngCommitmentBatch[0] = brngCommitmentBatch[0][:0] 198 | } 199 | Expect(func() { 200 | rng.New(index, indices, h, brngShareBatch, brngCommitmentBatch, isZero) 201 | }).To(Panic()) 202 | }) 203 | 204 | Specify("incorrect share batch size", func() { 205 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 206 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 207 | brngShareBatch = brngShareBatch[1:] 208 | Expect(func() { 209 | rng.New(index, indices, h, brngShareBatch, brngCommitmentBatch, isZero) 210 | }).To(Panic()) 211 | }) 212 | 213 | Specify("inconsistent commitment dimensions", func() { 214 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 215 | // This test requires the batch size to be at least 2. 216 | if b == 1 { 217 | b++ 218 | } 219 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 220 | brngCommitmentBatch[1] = brngCommitmentBatch[1][1:] 221 | Expect(func() { 222 | rng.New(index, indices, h, brngShareBatch, brngCommitmentBatch, isZero) 223 | }).To(Panic()) 224 | }) 225 | 226 | Specify("inconsistent commitment dimensions (threshold)", func() { 227 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 228 | // Having a batch size of at least 2 ensures we can test this condition. 229 | if b == 1 { 230 | b++ 231 | } 232 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 233 | brngCommitmentBatch[1][0] = shamir.Commitment{} 234 | Expect(func() { 235 | rng.New(index, indices, h, brngShareBatch, brngCommitmentBatch, isZero) 236 | }).To(Panic()) 237 | }) 238 | 239 | Specify("inconsistent share dimensions", func() { 240 | _, indices, index, b, c, k, h := RandomTestParameters(isZero) 241 | brngShareBatch, brngCommitmentBatch := rngutil.BRNGOutputBatch(index, b, c, k, h) 242 | brngShareBatch[0] = brngShareBatch[0][1:] 243 | Expect(func() { 244 | rng.New(index, indices, h, brngShareBatch, brngCommitmentBatch, isZero) 245 | }).To(Panic()) 246 | }) 247 | }) 248 | } 249 | }) 250 | --------------------------------------------------------------------------------