diff --git a/dkg/dkg.go b/dkg/dkg.go index 141286a98..1d570fadf 100644 --- a/dkg/dkg.go +++ b/dkg/dkg.go @@ -24,11 +24,31 @@ type Actor interface { // setup has not been done. GetPublicKey() (kyber.Point, error) - Encrypt(message []byte) (K, C kyber.Point, remainder []byte, err error) - Decrypt(K, C kyber.Point) ([]byte, error) + // Encrypt encrypts the given message into kyber points + // using the DKG public key + // where K is the ephemeral DH (Diffie-Hellman) public key + // and Cs is the resulting, encrypted points + // or an error if encryption is not possible. + Encrypt(message []byte) (K kyber.Point, Cs []kyber.Point, err error) + // Decrypt decrypts a ciphertext (composed of a K and an array of C's) + // into the original message using the DKG internal private key + // where K is the ephemeral DH (Diffie-Hellman) public key + // and Cs is the encrypted points + Decrypt(K kyber.Point, Cs []kyber.Point) ([]byte, error) + + // Reencrypt reencrypts generate a temporary key from the public key + // to be able to decrypt the message by the user's private key + Reencrypt(K kyber.Point, PK kyber.Point) (XhatEnc kyber.Point, err error) + + // Reshare recreates the DKG with an updated list of participants. Reshare(co crypto.CollectiveAuthority, newThreshold int) error - VerifiableDecrypt(ciphertexts []types.Ciphertext) ([][]byte, error) + // VerifiableEncrypt encrypts the given message into a ciphertext + // using the DKG public key and a verifiable encryption function. VerifiableEncrypt(message []byte, GBar kyber.Point) (ciphertext types.Ciphertext, remainder []byte, err error) + + // VerifiableDecrypt decrypts a pair of kyber points into the original message + // using the DKG internal private key and a verifiable encryption function. + VerifiableDecrypt(ciphertexts []types.Ciphertext) ([][]byte, error) } diff --git a/dkg/pedersen/controller/action.go b/dkg/pedersen/controller/action.go index b0b62aedd..ed4987b20 100644 --- a/dkg/pedersen/controller/action.go +++ b/dkg/pedersen/controller/action.go @@ -26,6 +26,7 @@ var suite = suites.MustFind("Ed25519") const separator = ":" const authconfig = "dkgauthority" const resolveActorFailed = "failed to resolve actor, did you call listen?: %v" +const malformedEncoded = "malformed encoded: %s" type setupAction struct{} @@ -81,14 +82,14 @@ type listenAction struct { } func (a listenAction) Execute(ctx node.Context) error { - var dkg dkg.DKG + var dkgObject dkg.DKG - err := ctx.Injector.Resolve(&dkg) + err := ctx.Injector.Resolve(&dkgObject) if err != nil { return xerrors.Errorf("failed to resolve dkg: %v", err) } - actor, err := dkg.Listen() + actor, err := dkgObject.Listen() if err != nil { return xerrors.Errorf("failed to listen: %v", err) } @@ -188,12 +189,12 @@ func (a encryptAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to decode message: %v", err) } - k, c, remainder, err := actor.Encrypt(message) + k, cs, err := actor.Encrypt(message) if err != nil { return xerrors.Errorf("failed to encrypt: %v", err) } - outStr, err := encodeEncrypted(k, c, remainder) + outStr, err := encodeEncrypted(k, cs) if err != nil { return xerrors.Errorf("failed to generate output: %v", err) } @@ -215,12 +216,12 @@ func (a decryptAction) Execute(ctx node.Context) error { encrypted := ctx.Flags.String("encrypted") - k, c, err := decodeEncrypted(encrypted) + k, cs, err := decodeEncrypted(encrypted) if err != nil { return xerrors.Errorf("failed to decode encrypted str: %v", err) } - decrypted, err := actor.Decrypt(k, c) + decrypted, err := actor.Decrypt(k, cs) if err != nil { return xerrors.Errorf("failed to decrypt: %v", err) } @@ -230,28 +231,95 @@ func (a decryptAction) Execute(ctx node.Context) error { return nil } -func encodeEncrypted(k, c kyber.Point, remainder []byte) (string, error) { - kbuff, err := k.MarshalBinary() +type reencryptAction struct{} + +func (a reencryptAction) Execute(ctx node.Context) error { + var actor dkg.Actor + + err := ctx.Injector.Resolve(&actor) if err != nil { - return "", xerrors.Errorf("failed to marshal k: %v", err) + return xerrors.Errorf(resolveActorFailed, err) + } + + encrypted := ctx.Flags.String("encrypted") + + k, _, err := decodeEncrypted(encrypted) + if err != nil { + return xerrors.Errorf("failed to decode encrypted str: %v", err) } - cbuff, err := c.MarshalBinary() + publicKey := ctx.Flags.String("pubk") + + pk, err := decodePublicKey(publicKey) if err != nil { - return "", xerrors.Errorf("failed to marshal c: %v", err) + return xerrors.Errorf("failed to decode public key str: %v", err) } - encoded := hex.EncodeToString(kbuff) + separator + - hex.EncodeToString(cbuff) + separator + - hex.EncodeToString(remainder) + xhatenc, err := actor.Reencrypt(k, pk) + if err != nil { + return xerrors.Errorf("failed to reencrypt: %v", err) + } + + outStr, err := encodeReencrypted(xhatenc) + if err != nil { + return xerrors.Errorf("failed to encode the reencryption data: %v", err) + } + + fmt.Fprint(ctx.Out, outStr) + + return nil +} + +func encodeReencrypted(xhatenc kyber.Point) (string, error) { + buff, err := xhatenc.MarshalBinary() + if err != nil { + return "", xerrors.Errorf("failed to marshal xhatenc: %v", err) + } + + encoded := hex.EncodeToString(buff) return encoded, nil } -func decodeEncrypted(str string) (k kyber.Point, c kyber.Point, err error) { +func decodePublicKey(str string) (pk kyber.Point, err error) { + pkbuff, err := hex.DecodeString(str) + if err != nil { + return nil, xerrors.Errorf(malformedEncoded, str) + } + + pk = suite.Point() + + err = pk.UnmarshalBinary(pkbuff) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal pk: %v", err) + } + + return pk, nil +} + +func encodeEncrypted(k kyber.Point, cs []kyber.Point) (string, error) { + kbuff, err := k.MarshalBinary() + if err != nil { + return "", xerrors.Errorf("failed to marshal k: %v", err) + } + + encoded := hex.EncodeToString(kbuff) + + for _, c := range cs { + cbuff, err := c.MarshalBinary() + if err != nil { + return "", xerrors.Errorf("failed to marshal c: %v", err) + } + encoded += separator + hex.EncodeToString(cbuff) + } + + return encoded, nil +} + +func decodeEncrypted(str string) (kyber.Point, []kyber.Point, error) { parts := strings.Split(str, separator) if len(parts) < 2 { - return nil, nil, xerrors.Errorf("malformed encoded: %s", str) + return nil, nil, xerrors.Errorf(malformedEncoded, str) } // Decode K @@ -260,27 +328,33 @@ func decodeEncrypted(str string) (k kyber.Point, c kyber.Point, err error) { return nil, nil, xerrors.Errorf("failed to decode k point: %v", err) } - k = suite.Point() + k := suite.Point() err = k.UnmarshalBinary(kbuff) if err != nil { return nil, nil, xerrors.Errorf("failed to unmarshal k point: %v", err) } - // Decode C - cbuff, err := hex.DecodeString(parts[1]) - if err != nil { - return nil, nil, xerrors.Errorf("failed to decode c point: %v", err) - } + // Decode Cs + cs := make([]kyber.Point, 0, len(parts)-1) - c = suite.Point() + for _, p := range parts[1:] { + cbuff, err := hex.DecodeString(p) + if err != nil { + return nil, nil, xerrors.Errorf("failed to decode c point: %v", err) + } - err = c.UnmarshalBinary(cbuff) - if err != nil { - return nil, nil, xerrors.Errorf("failed to unmarshal c point: %v", err) + c := suite.Point() + + err = c.UnmarshalBinary(cbuff) + if err != nil { + return nil, nil, xerrors.Errorf("failed to unmarshal c point: %v", err) + } + + cs = append(cs, c) } - return k, c, nil + return k, cs, nil } // Verifiable encryption @@ -394,7 +468,7 @@ func (a verifiableDecryptAction) Execute(ctx node.Context) error { parts := strings.Split(ciphertextString, separator) if len(parts)%5 != 0 { - return xerrors.Errorf("malformed encoded: %s", ciphertextString) + return xerrors.Errorf(malformedEncoded, ciphertextString) } batchSize := len(parts) / 5 diff --git a/dkg/pedersen/controller/action_test.go b/dkg/pedersen/controller/action_test.go index 1606518d8..f85815da2 100644 --- a/dkg/pedersen/controller/action_test.go +++ b/dkg/pedersen/controller/action_test.go @@ -389,16 +389,14 @@ func TestEncryptAction_OK(t *testing.T) { a := encryptAction{} data := "aa" + fakeK := badPoint{data: data} + + fakeCs := make([]kyber.Point, 0, 1) + fakeCs = append(fakeCs, fakeK) + actor := fakeActor{k: fakeK, cs: fakeCs} inj := node.NewInjector() - inj.Inject(fakeActor{ - k: badPoint{ - data: data, - }, - c: badPoint{ - data: data, - }, - }) + inj.Inject(actor) flags := node.FlagSet{ "message": "aef123", @@ -417,7 +415,7 @@ func TestEncryptAction_OK(t *testing.T) { dataHex := hex.EncodeToString([]byte(data)) - require.Equal(t, dataHex+":"+dataHex+":", out.String()) + require.Equal(t, dataHex+":"+dataHex, out.String()) } func TestDecryptAction_noActor(t *testing.T) { @@ -454,7 +452,7 @@ func TestDecryptAction_decodeFail(t *testing.T) { } func TestDecryptAction_decryptFail(t *testing.T) { - encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9:" + encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9" a := decryptAction{} inj := node.NewInjector() @@ -475,8 +473,8 @@ func TestDecryptAction_decryptFail(t *testing.T) { require.EqualError(t, err, fake.Err("failed to decrypt")) } -func TestDecryptAction_decryptOK(t *testing.T) { - encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9:" +func TestDecryptAction_OK(t *testing.T) { + encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9" message := "fake" expected := hex.EncodeToString([]byte(message)) @@ -506,13 +504,134 @@ func TestDecryptAction_decryptOK(t *testing.T) { require.Equal(t, expected, out.String()) } +func TestReencryptAction_noActor(t *testing.T) { + a := reencryptAction{} + + inj := node.NewInjector() + + ctx := node.Context{ + Injector: inj, + } + + err := a.Execute(ctx) + require.EqualError(t, err, "failed to resolve actor, did you call listen?:"+ + " couldn't find dependency for 'dkg.Actor'") +} + +func TestReencryptAction_decodeEncryptedFail(t *testing.T) { + a := reencryptAction{} + + inj := node.NewInjector() + inj.Inject(fakeActor{}) + + flags := node.FlagSet{ + "encrypted": "not hex", + "pubk": "aabbcdd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9", + } + + ctx := node.Context{ + Injector: inj, + Flags: flags, + } + + err := a.Execute(ctx) + require.EqualError(t, err, "failed to decode encrypted str: malformed encoded: not hex") +} + +func TestReencryptAction_decodePubkFail(t *testing.T) { + a := reencryptAction{} + + inj := node.NewInjector() + inj.Inject(fakeActor{}) + + flags := node.FlagSet{ + "encrypted": "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9", + "pubk": "not hex", + } + + ctx := node.Context{ + Injector: inj, + Flags: flags, + } + + err := a.Execute(ctx) + require.EqualError(t, err, "failed to decode public key str: malformed encoded: not hex") +} + +func TestReencryptAction_reencryptFail(t *testing.T) { + encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + pubk := "ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9" + + a := reencryptAction{} + + inj := node.NewInjector() + inj.Inject(fakeActor{ + reencryptErr: fake.GetError(), + }) + + flags := node.FlagSet{ + "encrypted": encrypted, + "pubk": pubk, + } + + ctx := node.Context{ + Injector: inj, + Flags: flags, + } + + err := a.Execute(ctx) + require.EqualError(t, err, fake.Err("failed to reencrypt")) +} + +func TestReencryptAction_OK(t *testing.T) { + encrypted := "abea449f0ab86029c529f541cdd7f48aee3102b9c1ea2999b5d39c2cc49a4c23:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + pubk := "ae29dd65cb4ceaaf7830008b9544625a05b6dbbcd421cf8475aedbef8e8d1da9" + + sk := suite.Scalar().Pick(suite.RandomStream()) + pk := suite.Point().Mul(sk, nil) // we just need a Ed25519 valid point + expected, err := pk.MarshalBinary() + require.NoError(t, err, "unable to initialise the point") + + inj := node.NewInjector() + inj.Inject(fakeActor{ + xhatenc: pk, + }) + + flags := node.FlagSet{ + "encrypted": encrypted, + "pubk": pubk, + } + + out := &bytes.Buffer{} + + ctx := node.Context{ + Injector: inj, + Flags: flags, + Out: out, + } + + a := reencryptAction{} + err = a.Execute(ctx) + require.NoError(t, err) + + require.Equal(t, hex.EncodeToString(expected), out.String()) +} + func TestEncodeEncrypted_marshalKFail(t *testing.T) { - _, err := encodeEncrypted(badPoint{err: fake.GetError()}, nil, nil) + fakeK := badPoint{err: fake.GetError()} + fakeCs := make([]kyber.Point, 0, 1) + fakeCs = append(fakeCs, badPoint{}) + + _, err := encodeEncrypted(fakeK, fakeCs) require.EqualError(t, err, fake.Err("failed to marshal k")) } -func TestEncodeEncrypted_marshalCFail(t *testing.T) { - _, err := encodeEncrypted(badPoint{}, badPoint{err: fake.GetError()}, nil) +func TestEncodeEncrypted_marshalCSFail(t *testing.T) { + fakeK := badPoint{} + fakeCs := make([]kyber.Point, 0, 1) + fakeCs = append(fakeCs, badPoint{err: fake.GetError()}) + + _, err := encodeEncrypted(fakeK, fakeCs) require.EqualError(t, err, fake.Err("failed to marshal c")) } @@ -1382,17 +1501,19 @@ func TestReshareAction_OK(t *testing.T) { type fakeActor struct { dkg.Actor - setupErr error - encryptErr error - decryptErr error - vencryptErr error - vdecryptErr error - reshareErr error + setupErr error + encryptErr error + decryptErr error + reencryptErr error + vencryptErr error + vdecryptErr error + reshareErr error - k kyber.Point - c kyber.Point + k kyber.Point + cs []kyber.Point decryptData []byte + xhatenc kyber.Point ct types.Ciphertext vdecryptData [][]byte } @@ -1401,14 +1522,18 @@ func (f fakeActor) Setup(co crypto.CollectiveAuthority, threshold int) (pubKey k return suite.Point(), f.setupErr } -func (f fakeActor) Encrypt(message []byte) (K, C kyber.Point, remainder []byte, err error) { - return f.k, f.c, nil, f.encryptErr +func (f fakeActor) Encrypt(message []byte) (K kyber.Point, CS []kyber.Point, err error) { + return f.k, f.cs, f.encryptErr } -func (f fakeActor) Decrypt(K, C kyber.Point) ([]byte, error) { +func (f fakeActor) Decrypt(K kyber.Point, CS []kyber.Point) ([]byte, error) { return f.decryptData, f.decryptErr } +func (f fakeActor) Reencrypt(K kyber.Point, PK kyber.Point) (XhatEnc kyber.Point, err error) { + return f.xhatenc, f.reencryptErr +} + func (f fakeActor) VerifiableEncrypt(message []byte, GBar kyber.Point) (ciphertext types.Ciphertext, remainder []byte, err error) { return f.ct, nil, f.vencryptErr } diff --git a/dkg/pedersen/controller/controller.go b/dkg/pedersen/controller/controller.go index c23897e27..b9adeaa95 100644 --- a/dkg/pedersen/controller/controller.go +++ b/dkg/pedersen/controller/controller.go @@ -48,7 +48,7 @@ func (m minimal) SetCommands(builder node.Builder) { sub.SetAction(builder.MakeAction(setupAction{})) sub = cmd.SetSubCommand("encrypt") - sub.SetDescription("encrypt a message. Outputs ::") + sub.SetDescription("encrypt a message and outputs :") sub.SetFlags( cli.StringFlag{ Name: "message", @@ -58,15 +58,29 @@ func (m minimal) SetCommands(builder node.Builder) { sub.SetAction(builder.MakeAction(encryptAction{})) sub = cmd.SetSubCommand("decrypt") - sub.SetDescription("decrypt a message") + sub.SetDescription("decrypt a message and outputs the decrypted message") sub.SetFlags( cli.StringFlag{ Name: "encrypted", - Usage: "the encrypted string, as :", + Usage: "the encrypted string, as :", }, ) sub.SetAction(builder.MakeAction(decryptAction{})) + sub = cmd.SetSubCommand("reencrypt") + sub.SetDescription("reencrypt a message and outputs ") + sub.SetFlags( + cli.StringFlag{ + Name: "encrypted", + Usage: "the encrypted string, as :", + }, + cli.StringFlag{ + Name: "pubk", + Usage: "the new public key to reencrypt the message, as ", + }, + ) + sub.SetAction(builder.MakeAction(reencryptAction{})) + sub = cmd.SetSubCommand("verifiableEncrypt") sub.SetDescription("encrypt a message and provides a proof. " + "Outputs :::::") diff --git a/dkg/pedersen/dkg.go b/dkg/pedersen/dkg.go index 3902a3eb2..fb18c68fc 100644 --- a/dkg/pedersen/dkg.go +++ b/dkg/pedersen/dkg.go @@ -2,6 +2,7 @@ package pedersen import ( "context" + "crypto/sha256" "strconv" "sync" @@ -174,6 +175,14 @@ func (s *instance) handleMessage(ctx context.Context, msg serde.Message, from mi return s.handleVerifiableDecrypt(out, msg, from) + case types.ReencryptRequest: + err := s.startRes.checkState(certified) + if err != nil { + return xerrors.Errorf(badState, err) + } + + return s.handleReencryptRequest(out, msg, from) + default: return xerrors.Errorf("expected Start message, decrypt request or "+ "Deal as first message, got: %T", msg) @@ -211,7 +220,7 @@ func (s *instance) start(ctx context.Context, start types.Start, deals channel.T err = s.doDKG(ctx, deals, resps, out, from) if err != nil { - xerrors.Errorf("something went wrong during DKG: %v", err) + return xerrors.Errorf("something went wrong during DKG: %v", err) } return nil @@ -272,6 +281,8 @@ func (s *instance) deal(ctx context.Context, out mino.Sender) error { return xerrors.Errorf("failed to compute the deals: %v", err) } + participants := s.startRes.getParticipants() + for i, deal := range deals { dealMsg := types.NewDeal( deal.Index, @@ -284,7 +295,7 @@ func (s *instance) deal(ctx context.Context, out mino.Sender) error { ), ) - to := s.startRes.getParticipants()[i] + to := participants[i] s.log.Trace().Str("to", to.String()).Msg("send deal") @@ -307,13 +318,14 @@ func (s *instance) deal(ctx context.Context, out mino.Sender) error { func (s *instance) respond(ctx context.Context, deals channel.Timed[types.Deal], out mino.Sender) error { numReceivedDeals := 0 - for numReceivedDeals < len(s.startRes.getParticipants())-1 { + participants := s.startRes.getParticipants() + for numReceivedDeals < len(participants)-1 { deal, err := deals.NonBlockingReceiveWithContext(ctx) if err != nil { return xerrors.Errorf("context done: %v", err) } - err = s.handleDeal(ctx, deal, out, s.startRes.getParticipants()) + err = s.handleDeal(ctx, deal, out, participants) if err != nil { return xerrors.Errorf("failed to handle received deal: %v", err) } @@ -513,7 +525,7 @@ func (s *instance) reshare(ctx context.Context, out mino.Sender, err = s.doReshare(ctx, msg, from, out, reshares, resps) if err != nil { - xerrors.Errorf("failed to reshare: %v", err) + return xerrors.Errorf("failed to reshare: %v", err) } return nil @@ -543,7 +555,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, switch nt { case oldNode: // Update local DKG for resharing - share, err := s.dkg.DistKeyShare() + distKeyShare, err := s.dkg.DistKeyShare() if err != nil { return xerrors.Errorf("old node failed to create: %v", err) } @@ -555,7 +567,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, Longterm: s.privKey, OldNodes: s.startRes.getPublicKeys(), NewNodes: start.GetPubkeysNew(), - Share: share, + Share: distKeyShare, Threshold: start.GetTNew(), OldThreshold: s.startRes.getThreshold(), } @@ -568,7 +580,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, s.dkg = d // Send my Deals to the new and common nodes - err = s.sendDealsResharing(ctx, out, addrsNew, share.Commits) + err = s.sendDealsResharing(ctx, out, addrsNew, distKeyShare.Commits) if err != nil { return xerrors.Errorf("old node failed to send deals: %v", err) } @@ -577,7 +589,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, case commonNode: // Update local DKG for resharing - share, err := s.dkg.DistKeyShare() + distKeyShare, err := s.dkg.DistKeyShare() if err != nil { return xerrors.Errorf("common node failed to create: %v", err) } @@ -589,7 +601,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, Longterm: s.privKey, OldNodes: s.startRes.getPublicKeys(), NewNodes: start.GetPubkeysNew(), - Share: share, + Share: distKeyShare, Threshold: start.GetTNew(), OldThreshold: s.startRes.getThreshold(), } @@ -602,7 +614,7 @@ func (s *instance) doReshare(ctx context.Context, start types.StartResharing, s.dkg = d // Send my Deals to the new and common nodes - err = s.sendDealsResharing(ctx, out, addrsNew, share.Commits) + err = s.sendDealsResharing(ctx, out, addrsNew, distKeyShare.Commits) if err != nil { return xerrors.Errorf("common node failed to send deals: %v", err) } @@ -789,6 +801,46 @@ func (s *instance) handleDecrypt(out mino.Sender, msg types.DecryptRequest, return nil } +func (s *instance) handleReencryptRequest(out mino.Sender, msg types.ReencryptRequest, + from mino.Address) error { + + if !s.startRes.Done() { + return xerrors.Errorf("you must first initialize DKG. Did you call setup() first?") + } + + ui := s.getUI(msg.K, msg.PubK) + + // Calculating proofs of reencryption + si := suite.Scalar().Pick(suite.RandomStream()) + uiHat := suite.Point().Mul(si, suite.Point().Add(msg.K, msg.PubK)) + hiHat := suite.Point().Mul(si, nil) + hash := sha256.New() + ui.V.MarshalTo(hash) + uiHat.MarshalTo(hash) + hiHat.MarshalTo(hash) + ei := suite.Scalar().SetBytes(hash.Sum(nil)) + fi := suite.Scalar().Add(si, suite.Scalar().Mul(ei, s.privShare.V)) + + response := types.NewReencryptReply(msg.PubK, ui, ei, fi) + + errs := out.Send(response, from) + err := <-errs + if err != nil { + return xerrors.Errorf("got an error while sending the reencrypt reply: %v", err) + } + + return nil +} + +func (s *instance) getUI(K, pubk kyber.Point) *share.PubShare { + v := suite.Point().Mul(s.privShare.V, K) + v.Add(v, suite.Point().Mul(s.privShare.V, pubk)) + return &share.PubShare{ + I: s.privShare.I, + V: v, + } +} + func (s *instance) handleVerifiableDecrypt(out mino.Sender, msg types.VerifiableDecryptRequest, from mino.Address) error { diff --git a/dkg/pedersen/dkg_test.go b/dkg/pedersen/dkg_test.go index 4c6892ce3..a15107025 100644 --- a/dkg/pedersen/dkg_test.go +++ b/dkg/pedersen/dkg_test.go @@ -117,6 +117,17 @@ func TestDKGInstance_HandleVerifiableDecryptRequestFail(t *testing.T) { require.EqualError(t, err, "bad state: unexpected state: UNKNOWN != one of [Certified]") } +func TestDKGInstance_HandleReencryptRequestFail(t *testing.T) { + s := instance{ + startRes: &state{dkgState: 0xaa}, + } + + err := s.handleMessage(context.TODO(), types.ReencryptRequest{}, + fake.NewAddress(0), nil) + + require.EqualError(t, err, "bad state: unexpected state: UNKNOWN != one of [Certified]") +} + func TestDKGInstance_HandleUnknown(t *testing.T) { s := instance{ startRes: &state{dkgState: 0xaa}, @@ -139,7 +150,7 @@ func TestDKGInstance_StartFailNewDKG(t *testing.T) { require.EqualError(t, err, "failed to create new DKG: dkg: can't run with empty node list") } -func TestDKGInstance_Start(t *testing.T) { +func TestDKGInstance_StartFailDeal(t *testing.T) { privKey := suite.Scalar().Pick(suite.RandomStream()) pubKey := suite.Point().Mul(privKey, nil) @@ -157,16 +168,25 @@ func TestDKGInstance_Start(t *testing.T) { require.EqualError(t, err, "there should be as many participants as pubKey: 1 != 0") s.startRes.dkgState = initial + s.startRes.threshold = 2 - start = types.NewStart(2, []mino.Address{fake.NewAddress(0), - fake.NewAddress(1)}, []kyber.Point{pubKey, suite.Point()}) + start = types.NewStart(2, + []mino.Address{ + fake.NewAddress(0), + fake.NewAddress(1)}, + []kyber.Point{ + pubKey, + suite.Point()}) - ctx, cancel := context.WithCancel(context.Background()) - cancel() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() err = s.start(ctx, start, channel.Timed[types.Deal]{}, channel.Timed[types.Response]{}, from, fake.Sender{}) - require.NoError(t, err) + require.ErrorContains(t, err, + "something went wrong during DKG: failed to respond") + // this actually ensures that the start occurred successfully, + // but the DKG execution failed because deals are not sent. } func TestDKGInstance_doDKG_DealFail(t *testing.T) { diff --git a/dkg/pedersen/json/json.go b/dkg/pedersen/json/json.go index ccc87d495..ce6ba7e0a 100644 --- a/dkg/pedersen/json/json.go +++ b/dkg/pedersen/json/json.go @@ -5,10 +5,14 @@ import ( "go.dedis.ch/dela/mino" "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/share" "go.dedis.ch/kyber/v3/suites" "golang.org/x/xerrors" ) +const ERROR_MARSHAL_K = "couldn't marshal K: %v" +const ERROR_UNMARSHAL_K = "couldn't unmarshal K: %v" + func init() { types.RegisterMessageFormat(serde.FormatJSON, newMsgFormat()) } @@ -103,6 +107,19 @@ type VerifiableDecryptReply struct { Sp []ShareAndProof } +type ReencryptRequest struct { + K []byte + PubK PublicKey +} + +type ReencryptReply struct { + PubK PublicKey + UiI int + UiV []byte + Ei []byte + Fi []byte +} + type Message struct { Start *Start `json:",omitempty"` StartResharing *StartResharing `json:",omitempty"` @@ -114,6 +131,8 @@ type Message struct { DecryptReply *DecryptReply `json:",omitempty"` VerifiableDecryptReply *VerifiableDecryptReply `json:",omitempty"` VerifiableDecryptRequest *VerifiableDecryptRequest `json:",omitempty"` + ReencryptRequest *ReencryptRequest `json:",omitempty"` + ReencryptReply *ReencryptReply `json:",omitempty"` } // MsgFormat is the engine to encode and decode dkg messages in JSON format. @@ -177,6 +196,10 @@ func (f msgFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) m, err = encodeDecryptReply(in) case types.VerifiableDecryptReply: m, err = encodeVerifiableDecryptReply(in) + case types.ReencryptRequest: + m, err = encodeReencryptRequest(in) + case types.ReencryptReply: + m, err = encodeReencryptReply(in) default: return nil, xerrors.Errorf("unsupported message of type '%T'", msg) } @@ -253,6 +276,12 @@ func (f msgFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) case m.VerifiableDecryptReply != nil: return f.decodeVerifiableDecryptReply(ctx, m.VerifiableDecryptReply) + + case m.ReencryptRequest != nil: + return f.decodeReencryptRequest(ctx, m.ReencryptRequest) + + case m.ReencryptReply != nil: + return f.decodeReencryptReply(ctx, m.ReencryptReply) } return nil, xerrors.New("message is empty") @@ -504,7 +533,7 @@ func (f msgFormat) decodeStartDone(ctx serde.Context, msg *StartDone) (serde.Mes func encodeDecryptRequest(msg types.DecryptRequest) (Message, error) { k, err := msg.GetK().MarshalBinary() if err != nil { - return Message{}, xerrors.Errorf("couldn't marshal K: %v", err) + return Message{}, xerrors.Errorf(ERROR_MARSHAL_K, err) } c, err := msg.GetC().MarshalBinary() @@ -524,7 +553,7 @@ func (f msgFormat) decodeDecryptRequest(ctx serde.Context, msg *DecryptRequest) k := f.suite.Point() err := k.UnmarshalBinary(msg.K) if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal K: %v", err) + return nil, xerrors.Errorf(ERROR_UNMARSHAL_K, err) } c := f.suite.Point() @@ -538,6 +567,59 @@ func (f msgFormat) decodeDecryptRequest(ctx serde.Context, msg *DecryptRequest) return req, nil } +func encodeReencryptRequest(msg types.ReencryptRequest) (Message, error) { + k, err := msg.K.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf(ERROR_MARSHAL_K, err) + } + + pubk, err := msg.PubK.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf("couldn't marshal PubK: %v", err) + } + + req := ReencryptRequest{ + K: k, + PubK: pubk, + } + + return Message{ReencryptRequest: &req}, nil +} + +func encodeReencryptReply(msg types.ReencryptReply) (Message, error) { + pubk, err := msg.PubK.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf("couldn't marshal PubK: %v", err) + } + + i := msg.GetI() + + v, err := msg.Ui.V.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf("couldn't marshal Ui: %v", err) + } + + ei, err := msg.Ei.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf("couldn't marshal Ei: %v", err) + } + + fi, err := msg.Fi.MarshalBinary() + if err != nil { + return Message{}, xerrors.Errorf("couldn't marshal Fi: %v", err) + } + + rep := ReencryptReply{ + PubK: pubk, + UiI: i, + UiV: v, + Ei: ei, + Fi: fi, + } + + return Message{ReencryptReply: &rep}, nil +} + func encodeVerifiableDecryptRequest(msg types.VerifiableDecryptRequest) (Message, error) { ciphertexts := msg.GetCiphertexts() var encodedCiphertexts []Ciphertext @@ -545,7 +627,7 @@ func encodeVerifiableDecryptRequest(msg types.VerifiableDecryptRequest) (Message for _, cp := range ciphertexts { K, err := cp.K.MarshalBinary() if err != nil { - return Message{}, xerrors.Errorf("couldn't marshal K: %v", err) + return Message{}, xerrors.Errorf(ERROR_MARSHAL_K, err) } C, err := cp.C.MarshalBinary() @@ -602,7 +684,7 @@ func (f msgFormat) decodeVerifiableDecryptRequest(ctx serde.Context, K := f.suite.Point() err := K.UnmarshalBinary(cp.K) if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal K: %v", err) + return nil, xerrors.Errorf(ERROR_UNMARSHAL_K, err) } C := f.suite.Point() @@ -778,3 +860,63 @@ func (f msgFormat) decodeVerifiableDecryptReply(ctx serde.Context, return resp, nil } + +func (f msgFormat) decodeReencryptRequest(ctx serde.Context, request *ReencryptRequest) (serde.Message, error) { + k := f.suite.Point() + err := k.UnmarshalBinary(request.K) + if err != nil { + return nil, xerrors.Errorf(ERROR_UNMARSHAL_K, err) + } + + pubk := f.suite.Point() + err = pubk.UnmarshalBinary(request.PubK) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal PubK: %v", err) + } + + resp := types.ReencryptRequest{ + K: k, + PubK: pubk, + } + + return resp, nil +} + +func (f msgFormat) decodeReencryptReply(ctx serde.Context, reply *ReencryptReply) (serde.Message, error) { + pubk := f.suite.Point() + err := pubk.UnmarshalBinary(reply.PubK) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal PubK: %v", err) + } + + i := reply.UiI + + v := f.suite.Point() + err = v.UnmarshalBinary(reply.UiV) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal UiV: %v", err) + } + + ui := &share.PubShare{i, v} + + ei := f.suite.Scalar() + err = ei.UnmarshalBinary(reply.Ei) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal Ei: %v", err) + } + + fi := f.suite.Scalar() + err = fi.UnmarshalBinary(reply.Fi) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal Fi: %v", err) + } + + resp := types.ReencryptReply{ + PubK: pubk, + Ui: ui, + Ei: ei, + Fi: fi, + } + + return resp, nil +} diff --git a/dkg/pedersen/json/json_test.go b/dkg/pedersen/json/json_test.go index 2b298f825..267ed846b 100644 --- a/dkg/pedersen/json/json_test.go +++ b/dkg/pedersen/json/json_test.go @@ -2,6 +2,7 @@ package json import ( "fmt" + "go.dedis.ch/kyber/v3/share" "testing" "github.com/stretchr/testify/require" @@ -16,7 +17,7 @@ import ( // suite is the Kyber suite for Pedersen. var suite = suites.MustFind("Ed25519") -func TestMessageFormat_Start_Encode(t *testing.T) { +func TestMessageFormat_EncodeStart(t *testing.T) { start := types.NewStart(1, []mino.Address{fake.NewAddress(0)}, []kyber.Point{suite.Point()}) format := newMsgFormat() @@ -42,7 +43,7 @@ func TestMessageFormat_Start_Encode(t *testing.T) { require.EqualError(t, err, "unsupported message of type 'fake.Message'") } -func TestMessageFormat_StartResharing_Encode(t *testing.T) { +func TestMessageFormat_EncodeStartResharing(t *testing.T) { start := types.NewStartResharing(1, 1, []mino.Address{fake.NewAddress(0)}, []mino.Address{fake.NewAddress(1)}, []kyber.Point{suite.Point()}, []kyber.Point{suite.Point()}) @@ -72,7 +73,7 @@ func TestMessageFormat_StartResharing_Encode(t *testing.T) { require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal old public key")) } -func TestMessageFormat_Deal_Encode(t *testing.T) { +func TestMessageFormat_EncodeEncryptDeal(t *testing.T) { deal := types.NewDeal(1, []byte{1}, types.EncryptedDeal{}) format := newMsgFormat() @@ -84,7 +85,7 @@ func TestMessageFormat_Deal_Encode(t *testing.T) { require.Equal(t, expected, string(data)) } -func TestMessageFormat_Reshare_Encode(t *testing.T) { +func TestMessageFormat_EncodeReshare(t *testing.T) { reshare := types.NewReshare(types.Deal{}, []kyber.Point{suite.Point()}) format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) @@ -99,7 +100,7 @@ func TestMessageFormat_Reshare_Encode(t *testing.T) { require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal public coefficient")) } -func TestMessageFormat_Response_Encode(t *testing.T) { +func TestMessageFormat_EncodeDealerResponse(t *testing.T) { resp := types.NewResponse(1, types.DealerResponse{}) format := newMsgFormat() @@ -111,7 +112,7 @@ func TestMessageFormat_Response_Encode(t *testing.T) { require.Equal(t, expected, string(data)) } -func TestMessageFormat_StartDone_Encode(t *testing.T) { +func TestMessageFormat_EncodeStartDone(t *testing.T) { done := types.NewStartDone(suite.Point()) format := newMsgFormat() @@ -126,7 +127,7 @@ func TestMessageFormat_StartDone_Encode(t *testing.T) { require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal public key")) } -func TestMessageFormat_DecryptRequest_Encode(t *testing.T) { +func TestMessageFormat_EncodeDecryptRequest(t *testing.T) { req := types.NewDecryptRequest(suite.Point(), suite.Point()) format := newMsgFormat() @@ -146,7 +147,7 @@ func TestMessageFormat_DecryptRequest_Encode(t *testing.T) { require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal C")) } -func TestMessageFormat_VerifiableDecryptRequest_Encode(t *testing.T) { +func TestMessageFormat_EncodeVerifiableDecryptRequest(t *testing.T) { req := types.NewVerifiableDecryptRequest([]types.Ciphertext{{ K: suite.Point(), C: suite.Point(), @@ -180,7 +181,7 @@ func TestMessageFormat_VerifiableDecryptRequest_Encode(t *testing.T) { t.Run("GBar", check("GBar", types.Ciphertext{K: suite.Point(), C: suite.Point(), UBar: suite.Point(), E: suite.Scalar(), F: suite.Scalar(), GBar: badPoint{}})) } -func TestMessageFormat_DecryptReply_Encode(t *testing.T) { +func TestMessageFormat_EncodeDecryptReply(t *testing.T) { resp := types.NewDecryptReply(5, suite.Point()) format := newMsgFormat() @@ -195,10 +196,34 @@ func TestMessageFormat_DecryptReply_Encode(t *testing.T) { require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal V")) } -func TestMessageFormat_VerifiableDecryptReply_Encode(t *testing.T) { +func TestMessageFormat_EncodeReencryptReply(t *testing.T) { + resp := types.NewReencryptReply( + suite.Point(), + &share.PubShare{ + I: int(12358), + V: suite.Point(), + }, + suite.Scalar().Pick(suite.RandomStream()), + suite.Scalar(), + ) + + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) + + data, err := format.Encode(ctx, resp) + require.NoError(t, err) + + require.Regexp(t, `{"ReencryptReply":{"PubK":"[^"]+","UiI":12358,"UiV":"[^"]+","Ei":"[^"]+","Fi":"[^"]+"}}`, string(data)) + + resp.PubK = badPoint{} + _, err = format.Encode(ctx, resp) + require.EqualError(t, err, fake.Err("failed to encode message: couldn't marshal PubK")) +} + +func TestMessageFormat_EncodeVerifiableDecryptReply(t *testing.T) { req := types.NewVerifiableDecryptReply([]types.ShareAndProof{{ V: suite.Point(), - I: int64(0), + I: int64(1321), Ui: suite.Point(), Ei: suite.Scalar(), Fi: suite.Scalar(), @@ -210,7 +235,7 @@ func TestMessageFormat_VerifiableDecryptReply_Encode(t *testing.T) { data, err := format.Encode(ctx, req) require.NoError(t, err) - regexp := `{"VerifiableDecryptReply":{"Sp":\[{"V":"[^"]+","I":0,"Ui":"[^"]+","Ei":"[^"]+","Fi":"[^"]+","Hi":"[^"]+"}\]}}` + regexp := `{"VerifiableDecryptReply":{"Sp":\[{"V":"[^"]+","I":1321,"Ui":"[^"]+","Ei":"[^"]+","Fi":"[^"]+","Hi":"[^"]+"}\]}}` require.Regexp(t, regexp, string(data)) check := func(attr string, sp types.ShareAndProof) func(t *testing.T) { @@ -228,7 +253,7 @@ func TestMessageFormat_VerifiableDecryptReply_Encode(t *testing.T) { t.Run("Hi", check("H_i", types.ShareAndProof{V: suite.Point(), Ui: suite.Point(), Ei: suite.Scalar(), Fi: suite.Scalar(), Hi: badPoint{}})) } -func TestMessageFormat_Decode(t *testing.T) { +func TestMessageFormat_DecodeStart(t *testing.T) { format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) ctx = serde.WithFactory(ctx, types.AddrKey{}, fake.AddressFactory{}) @@ -256,19 +281,30 @@ func TestMessageFormat_Decode(t *testing.T) { badCtx := serde.WithFactory(ctx, types.AddrKey{}, nil) _, err = format.Decode(badCtx, []byte(`{"Start":{}}`)) require.EqualError(t, err, "invalid factory of type ''") +} +func TestMessageFormat_DecodeEncryptedDeal(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) - // Decode deal messages. deal, err := format.Decode(ctx, []byte(`{"Deal":{}}`)) require.NoError(t, err) require.Equal(t, types.NewDeal(0, nil, types.EncryptedDeal{}), deal) +} + +func TestMessageFormat_DecodeDealerResponse(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) - // Decode response messages. resp, err := format.Decode(ctx, []byte(`{"Response":{}}`)) require.NoError(t, err) require.Equal(t, types.NewResponse(0, types.DealerResponse{}), resp) +} + +func TestMessageFormat_DecodeStartDone(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) - // Decode start done messages. - data = []byte(fmt.Sprintf(`{"StartDone":{"PublicKey":"%s"}}`, testPoint)) + data := []byte(fmt.Sprintf(`{"StartDone":{"PublicKey":"%s"}}`, testPoint)) done, err := format.Decode(ctx, data) require.NoError(t, err) require.IsType(t, types.StartDone{}, done) @@ -277,9 +313,13 @@ func TestMessageFormat_Decode(t *testing.T) { _, err = format.Decode(ctx, data) require.EqualError(t, err, "couldn't unmarshal public key: invalid Ed25519 curve point") +} + +func TestMessageFormat_DecodeDecryptRequest(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) - // Decode decryption request messages. - data = []byte(fmt.Sprintf(`{"DecryptRequest":{"K":"%s","C":"%s"}}`, testPoint, testPoint)) + data := []byte(fmt.Sprintf(`{"DecryptRequest":{"K":"%s","C":"%s"}}`, testPoint, testPoint)) req, err := format.Decode(ctx, data) require.NoError(t, err) require.IsType(t, types.DecryptRequest{}, req) @@ -294,11 +334,16 @@ func TestMessageFormat_Decode(t *testing.T) { require.EqualError(t, err, "couldn't unmarshal C: invalid Ed25519 curve point") - // Decode decryption reply messages. - data = []byte(fmt.Sprintf(`{"DecryptReply":{"I":4,"V":"%s"}}`, testPoint)) - resp, err = format.Decode(ctx, data) +} + +func TestMessageFormat_DecodeDecryptReply(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) + + data := []byte(fmt.Sprintf(`{"DecryptReply":{"I":4,"V":"%s"}}`, testPoint)) + reply, err := format.Decode(ctx, data) require.NoError(t, err) - require.IsType(t, types.DecryptReply{}, resp) + require.IsType(t, types.DecryptReply{}, reply) data = []byte(`{"DecryptReply":{"V":[]}}`) _, err = format.Decode(ctx, data) @@ -312,7 +357,64 @@ func TestMessageFormat_Decode(t *testing.T) { require.EqualError(t, err, "message is empty") } -func TestMessageFormat_Decode_StartResharing(t *testing.T) { +func TestMessageFormat_DecodeReencryptRequest(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) + + data := []byte(fmt.Sprintf(`{"ReencryptRequest":{"K":"%s","PubK":"%s"}}`, testPoint, testPoint)) + request, err := format.Decode(ctx, data) + require.NoError(t, err) + require.IsType(t, types.ReencryptRequest{}, request) + + data = []byte(fmt.Sprintf(`{"ReencryptRequest":{"K":[],"PubK":"%s"}}`, testPoint)) + _, err = format.Decode(ctx, data) + require.EqualError(t, err, + "couldn't unmarshal K: invalid Ed25519 curve point") + + data = []byte(fmt.Sprintf(`{"ReencryptRequest":{"K":"%s","PubK":[]}}`, testPoint)) + _, err = format.Decode(ctx, data) + require.EqualError(t, err, + "couldn't unmarshal PubK: invalid Ed25519 curve point") +} + +func TestMessageFormat_DecodeReencryptReply(t *testing.T) { + format := newMsgFormat() + ctx := serde.NewContext(fake.ContextEngine{}) + + data := []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":"%s","UiI":13,"UiV":"%s","Ei":"%s","Fi":"%s"}}`, + testPoint, testPoint, testPoint, testPoint)) + reply, err := format.Decode(ctx, data) + require.NoError(t, err) + require.IsType(t, types.ReencryptReply{}, reply) + + data = []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":[],"UiI":13,"UiV":"%s","Ei":"%s","Fi":"%s"}}`, + testPoint, testPoint, testPoint)) + _, err = format.Decode(ctx, data) + require.ErrorContains(t, err, "couldn't unmarshal PubK") + + data = []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":"%s","UiI":"0","UiV":"%s","Ei":"%s","Fi":"%s"}}`, + testPoint, testPoint, testPoint, testPoint)) + _, err = format.Decode(ctx, data) + require.ErrorContains(t, err, "json: cannot unmarshal string into") + require.ErrorContains(t, err, "of type int") + + data = []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":"%s","UiI":13,"UiV":[],"Ei":"%s","Fi":"%s"}}`, + testPoint, testPoint, testPoint)) + _, err = format.Decode(ctx, data) + require.ErrorContains(t, err, "couldn't unmarshal UiV") + + data = []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":"%s","UiI":13,"UiV":"%s","Ei":[],"Fi":"%s"}}`, + testPoint, testPoint, testPoint)) + _, err = format.Decode(ctx, data) + require.ErrorContains(t, err, "couldn't unmarshal Ei") + + data = []byte(fmt.Sprintf(`{"ReencryptReply":{"Pubk":"%s","UiI":13,"UiV":"%s","Ei":"%s","Fi":[]}}`, + testPoint, testPoint, testPoint)) + _, err = format.Decode(ctx, data) + require.ErrorContains(t, err, "couldn't unmarshal Fi") +} + +func TestMessageFormat_DecodeStartResharing(t *testing.T) { format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) ctx = serde.WithFactory(ctx, types.AddrKey{}, fake.AddressFactory{}) @@ -349,7 +451,7 @@ func TestMessageFormat_Decode_StartResharing(t *testing.T) { "couldn't unmarshal old public key: invalid Ed25519 curve point") } -func TestMessageFormat_Decode_Reshare(t *testing.T) { +func TestMessageFormat_DecodeReshare(t *testing.T) { format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) ctx = serde.WithFactory(ctx, types.AddrKey{}, fake.AddressFactory{}) @@ -371,7 +473,7 @@ func TestMessageFormat_Decode_Reshare(t *testing.T) { require.EqualError(t, err, "couldn't unmarshal public coeff key: invalid Ed25519 curve point") } -func TestMessageFormat_Decode_VerifiableDecryptRequest(t *testing.T) { +func TestMessageFormat_DecodeVerifiableDecryptRequest(t *testing.T) { format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) ctx = serde.WithFactory(ctx, types.AddrKey{}, fake.AddressFactory{}) @@ -420,7 +522,7 @@ func TestMessageFormat_Decode_VerifiableDecryptRequest(t *testing.T) { require.EqualError(t, err, "couldn't unmarshal GBar: invalid Ed25519 curve point") } -func TestMessageFormat_Decode_VerifiableDecryptReply(t *testing.T) { +func TestMessageFormat_DecodeVerifiableDecryptReply(t *testing.T) { format := newMsgFormat() ctx := serde.NewContext(fake.ContextEngine{}) ctx = serde.WithFactory(ctx, types.AddrKey{}, fake.AddressFactory{}) diff --git a/dkg/pedersen/pedersen.go b/dkg/pedersen/pedersen.go index c8d3142ef..6b6e2a087 100644 --- a/dkg/pedersen/pedersen.go +++ b/dkg/pedersen/pedersen.go @@ -33,6 +33,9 @@ const failedStreamCreation = "failed to create stream: %v" // unexpectedStreamStop message indicating that a stream stopped unexpectedly const unexpectedStreamStop = "stream stopped unexpectedly: %v" +// unexpectedReply message indicating that a reply was not expected +const unexpectedReply = "got unexpected reply, expected %T but got: %T" + // suite is the Kyber suite for Pedersen. var suite = suites.MustFind("Ed25519") @@ -43,6 +46,9 @@ var ( // protocolNameDecrypt denotes the value of the protocol span tag // associated with the `dkg-decrypt` protocol. protocolNameDecrypt = "dkg-decrypt" + // protocolNameReencrypt denotes the value of the protocol span tag + //// associated with the `dkg-reencrypt` protocol. + protocolNameReencrypt = "dkg-reencrypt" // ProtocolNameResharing denotes the value of the protocol span tag // associated with the `dkg-resharing` protocol. protocolNameResharing = "dkg-resharing" @@ -106,26 +112,41 @@ type Actor struct { } // Setup implement dkg.Actor. It initializes the DKG. -func (a *Actor) Setup(co crypto.CollectiveAuthority, threshold int) (kyber.Point, error) { +func (a *Actor) Setup(coAuth crypto.CollectiveAuthority, threshold int) (kyber.Point, error) { if a.startRes.Done() { return nil, xerrors.Errorf("startRes is already done, only one setup call is allowed") } + nbNodes := coAuth.Len() + if nbNodes == 0 { + return nil, xerrors.Errorf("number of nodes cannot be zero") + } + + thresholdMin := 2 + if nbNodes < 2 { + thresholdMin = 1 + } + + if threshold < thresholdMin || threshold > nbNodes { + return nil, xerrors.Errorf("DKG threshold (%d) needs to be between %d and %d", + threshold, thresholdMin, nbNodes) + } + ctx, cancel := context.WithTimeout(context.Background(), setupTimeout) defer cancel() ctx = context.WithValue(ctx, tracing.ProtocolKey, protocolNameSetup) - sender, receiver, err := a.rpc.Stream(ctx, co) + sender, receiver, err := a.rpc.Stream(ctx, coAuth) if err != nil { return nil, xerrors.Errorf("failed to stream: %v", err) } - addrs := make([]mino.Address, 0, co.Len()) - pubkeys := make([]kyber.Point, 0, co.Len()) + addrs := make([]mino.Address, 0, coAuth.Len()) + pubkeys := make([]kyber.Point, 0, coAuth.Len()) - addrIter := co.AddressIterator() - pubkeyIter := co.PublicKeyIterator() + addrIter := coAuth.AddressIterator() + pubkeyIter := coAuth.PublicKeyIterator() for addrIter.HasNext() && pubkeyIter.HasNext() { addrs = append(addrs, addrIter.GetNext()) @@ -188,156 +209,179 @@ func (a *Actor) GetPublicKey() (kyber.Point, error) { } // Encrypt implements dkg.Actor. It uses the DKG public key to encrypt a -// message. -func (a *Actor) Encrypt(message []byte) (K, C kyber.Point, remainder []byte, - err error) { +// message, and returns a random, ephemeral part K +// and the cipher as an array of Kyber points +func (a *Actor) Encrypt(msg []byte) (kyber.Point, []kyber.Point, error) { if !a.startRes.Done() { - return nil, nil, nil, xerrors.Errorf(initDkgFirst) + return nil, nil, xerrors.Errorf(initDkgFirst) } - // Embed the message (or as much of it as will fit) into a curve point. - M := suite.Point().Embed(message, random.New()) - max := suite.Point().EmbedLen() - if max > len(message) { - max = len(message) - } - remainder = message[max:] - // ElGamal-encrypt the point to produce ciphertext (K,C). - k := suite.Scalar().Pick(random.New()) // ephemeral private key - K = suite.Point().Mul(k, nil) // ephemeral DH public key - S := suite.Point().Mul(k, a.startRes.getDistKey()) // ephemeral DH shared secret - C = S.Add(S, M) // message blinded with secret - - return K, C, remainder, nil -} - -// VerifiableEncrypt implements dkg.Actor. It uses the DKG public key to encrypt -// a message and provide a zero knowledge proof that the encryption is done by -// this person. -// -// See https://arxiv.org/pdf/2205.08529.pdf / section 5.4 Protocol / step 1 -func (a *Actor) VerifiableEncrypt(message []byte, GBar kyber.Point) (ciphertext types.Ciphertext, - remainder []byte, err error) { - - if !a.startRes.Done() { - return types.Ciphertext{}, nil, xerrors.Errorf("you must first initialize " + - "DKG. Did you call setup() first?") + pubK, err := a.GetPublicKey() + if err != nil { + dela.Logger.Error().Msgf("Cannot encrypt: %v", err.Error()) } - // Embed the message (or as much of it as will fit) into a curve point. - M := suite.Point().Embed(message, random.New()) + // ElGamal-encrypt the point to produce ciphertext (K,C). + r := suite.Scalar().Pick(suite.RandomStream()) - max := suite.Point().EmbedLen() - if max > len(message) { - max = len(message) - } + K := suite.Point().Mul(r, nil) + dela.Logger.Debug().Msgf("K: %v", K.String()) - remainder = message[max:] + C := suite.Point().Mul(r, pubK) + dela.Logger.Debug().Msgf("C: %v", C) - // ElGamal-encrypt the point to produce ciphertext (K,C). - k := suite.Scalar().Pick(random.New()) // ephemeral private key - K := suite.Point().Mul(k, nil) // ephemeral DH public key - S := suite.Point().Mul(k, a.startRes.getDistKey()) // ephemeral DH shared secret - C := S.Add(S, M) // message blinded with secret + // S: ephemeral DH shared secret + S := suite.Point().Mul(r, pubK) + dela.Logger.Debug().Msgf("S: %v", S.String()) - // producing the zero knowledge proof - UBar := suite.Point().Mul(k, GBar) - s := suite.Scalar().Pick(random.New()) - W := suite.Point().Mul(s, nil) - WBar := suite.Point().Mul(s, GBar) + Cs := make([]kyber.Point, 0, 16) + for len(msg) > 0 { + kp := suite.Point().Embed(msg, suite.RandomStream()) + dela.Logger.Debug().Msgf("kp: %v", kp.String()) - hash := sha256.New() - C.MarshalTo(hash) - K.MarshalTo(hash) - UBar.MarshalTo(hash) - W.MarshalTo(hash) - WBar.MarshalTo(hash) + // message blinded with secret + c := suite.Point().Add(C, kp) + dela.Logger.Debug().Msgf("c: %v", c) - E := suite.Scalar().SetBytes(hash.Sum(nil)) - F := suite.Scalar().Add(s, suite.Scalar().Mul(E, k)) + Cs = append(Cs, c) + dela.Logger.Debug().Msgf("Cs: %v", Cs) - ciphertext = types.Ciphertext{ - K: K, - C: C, - UBar: UBar, - E: E, - F: F, - GBar: GBar, + msg = msg[min(len(msg), kp.EmbedLen()):] } - return ciphertext, remainder, nil + return K, Cs, nil } // Decrypt implements dkg.Actor. It gets the private shares of the nodes and // decrypt the message. -func (a *Actor) Decrypt(K, C kyber.Point) ([]byte, error) { +func (a *Actor) Decrypt(K kyber.Point, Cs []kyber.Point) ([]byte, error) { if !a.startRes.Done() { return nil, xerrors.Errorf(initDkgFirst) } - players := mino.NewAddresses(a.startRes.getParticipants()...) - ctx, cancel := context.WithTimeout(context.Background(), decryptTimeout) defer cancel() ctx = context.WithValue(ctx, tracing.ProtocolKey, protocolNameDecrypt) + players := mino.NewAddresses(a.startRes.getParticipants()...) + sender, receiver, err := a.rpc.Stream(ctx, players) if err != nil { return nil, xerrors.Errorf(failedStreamCreation, err) } - players = mino.NewAddresses(a.startRes.getParticipants()...) iterator := players.AddressIterator() - addrs := make([]mino.Address, 0, players.Len()) + for iterator.HasNext() { addrs = append(addrs, iterator.GetNext()) } - message := types.NewDecryptRequest(K, C) - - err = <-sender.Send(message, addrs...) - if err != nil { - return nil, xerrors.Errorf("failed to send decrypt request: %v", err) - } + var decryptedMessage []byte - pubShares := make([]*share.PubShare, len(addrs)) + for _, C := range Cs { + message := types.NewDecryptRequest(K, C) - for i := 0; i < len(addrs); i++ { - src, message, err := receiver.Recv(ctx) + err = <-sender.Send(message, addrs...) if err != nil { - return []byte{}, xerrors.Errorf(unexpectedStreamStop, err) + return nil, xerrors.Errorf("failed to send decrypt request: %v", err) } - dela.Logger.Debug().Msgf("Received a decryption reply from %v", src) + pubShares := make([]*share.PubShare, len(addrs)) - decryptReply, ok := message.(types.DecryptReply) - if !ok { - return []byte{}, xerrors.Errorf("got unexpected reply, expected "+ - "%T but got: %T", decryptReply, message) + for i := 0; i < len(addrs); i++ { + src, message, err := receiver.Recv(ctx) + if err != nil { + return []byte{}, xerrors.Errorf(unexpectedStreamStop, err) + } + + dela.Logger.Debug().Msgf("Received a decryption reply from %v", src) + + decryptReply, ok := message.(types.DecryptReply) + if !ok { + return []byte{}, xerrors.Errorf(unexpectedReply, decryptReply, message) + } + + pubShares[i] = &share.PubShare{ + I: int(decryptReply.I), + V: decryptReply.V, + } } - pubShares[i] = &share.PubShare{ - I: int(decryptReply.I), - V: decryptReply.V, + res, err := share.RecoverCommit(suite, pubShares, len(addrs), len(addrs)) + if err != nil { + return []byte{}, xerrors.Errorf("failed to recover commit: %v", err) } + + decrypted, err := res.Data() + if err != nil { + return []byte{}, xerrors.Errorf("failed to get embedded data: %v", err) + } + + decryptedMessage = append(decryptedMessage, decrypted...) } + dela.Logger.Info().Msgf("Decrypted message: %v", decryptedMessage) - res, err := share.RecoverCommit(suite, pubShares, len(addrs), len(addrs)) - if err != nil { - return []byte{}, xerrors.Errorf("failed to recover commit: %v", err) + return decryptedMessage, nil +} + +// VerifiableEncrypt implements dkg.Actor. It uses the DKG public key to encrypt +// a message and provide a zero knowledge proof that the encryption is done by +// this person. +// +// See https://arxiv.org/pdf/2205.08529.pdf / section 5.4 Protocol / step 1 +func (a *Actor) VerifiableEncrypt(message []byte, GBar kyber.Point) (types.Ciphertext, + []byte, error) { + + if !a.startRes.Done() { + return types.Ciphertext{}, nil, xerrors.Errorf("you must first initialize " + + "DKG. Did you call setup() first?") } - decryptedMessage, err := res.Data() - if err != nil { - return []byte{}, xerrors.Errorf("failed to get embedded data: %v", err) + // Embed the message (or as much of it as will fit) into a curve point. + M := suite.Point().Embed(message, random.New()) + + max := suite.Point().EmbedLen() + if max > len(message) { + max = len(message) } - dela.Logger.Info().Msgf("Decrypted message: %v", decryptedMessage) + remainder := message[max:] - return decryptedMessage, nil + // ElGamal-encrypt the point to produce ciphertext (localK,localC). + localk := suite.Scalar().Pick(random.New()) // ephemeral private key + localK := suite.Point().Mul(localk, nil) // ephemeral DH public key + localS := suite.Point().Mul(localk, a.startRes.getDistKey()) // ephemeral DH shared secret + localC := localS.Add(localS, M) // message blinded with secret + + // producing the zero knowledge proof + UBar := suite.Point().Mul(localk, GBar) + s := suite.Scalar().Pick(random.New()) + W := suite.Point().Mul(s, nil) + WBar := suite.Point().Mul(s, GBar) + + hash := sha256.New() + localC.MarshalTo(hash) + localK.MarshalTo(hash) + UBar.MarshalTo(hash) + W.MarshalTo(hash) + WBar.MarshalTo(hash) + + E := suite.Scalar().SetBytes(hash.Sum(nil)) + F := suite.Scalar().Add(s, suite.Scalar().Mul(E, localk)) + + ciphertext := types.Ciphertext{ + K: localK, + C: localC, + UBar: UBar, + E: E, + F: F, + GBar: GBar, + } + + return ciphertext, remainder, nil } // VerifiableDecrypt implements dkg.Actor. It does as Decrypt() but in addition @@ -391,8 +435,7 @@ func (a *Actor) VerifiableDecrypt(ciphertexts []types.Ciphertext) ([][]byte, err shareAndProof, ok := message.(types.VerifiableDecryptReply) if !ok { - return nil, xerrors.Errorf("got unexpected reply, expected "+ - "%T but got: %T", shareAndProof, message) + return nil, xerrors.Errorf(unexpectedReply, shareAndProof, message) } responses[i] = shareAndProof @@ -490,16 +533,16 @@ func (w worker) work(jobIndex int) error { // Reshare implements dkg.Actor. It recreates the DKG with an updated list of // participants. -func (a *Actor) Reshare(co crypto.CollectiveAuthority, thresholdNew int) error { +func (a *Actor) Reshare(coAuth crypto.CollectiveAuthority, thresholdNew int) error { if !a.startRes.Done() { return xerrors.Errorf(initDkgFirst) } - addrsNew := make([]mino.Address, 0, co.Len()) - pubkeysNew := make([]kyber.Point, 0, co.Len()) + addrsNew := make([]mino.Address, 0, coAuth.Len()) + pubkeysNew := make([]kyber.Point, 0, coAuth.Len()) - addrIter := co.AddressIterator() - pubkeyIter := co.PublicKeyIterator() + addrIter := coAuth.AddressIterator() + pubkeyIter := coAuth.PublicKeyIterator() for addrIter.HasNext() && pubkeyIter.HasNext() { addrsNew = append(addrsNew, addrIter.GetNext()) diff --git a/dkg/pedersen/pedersen_test.go b/dkg/pedersen/pedersen_test.go index 0bdc82fd6..60a0bfdcf 100644 --- a/dkg/pedersen/pedersen_test.go +++ b/dkg/pedersen/pedersen_test.go @@ -1,6 +1,11 @@ package pedersen import ( + "fmt" + "go.dedis.ch/dela/mino/minoch" + "go.dedis.ch/kyber/v3/suites" + "go.dedis.ch/kyber/v3/util/key" + "golang.org/x/xerrors" "testing" "github.com/rs/zerolog" @@ -32,33 +37,39 @@ func TestPedersen_Setup(t *testing.T) { startRes: &state{}, } - fakeAuthority := fake.NewAuthority(1, fake.NewSigner) + fakeAuthority := fake.NewAuthority(0, fake.NewSigner) + _, err := actor.Setup(fakeAuthority, 1) + require.EqualError(t, err, "number of nodes cannot be zero") - _, err := actor.Setup(fakeAuthority, 0) + fakeAuthority = fake.NewAuthority(1, fake.NewSigner) + _, err = actor.Setup(fakeAuthority, 0) + require.ErrorContains(t, err, "DKG threshold (0) needs to be between") + + _, err = actor.Setup(fakeAuthority, 1) require.EqualError(t, err, fake.Err("failed to stream")) rpc := fake.NewStreamRPC(fake.NewReceiver(), fake.NewBadSender()) actor.rpc = rpc - _, err = actor.Setup(fakeAuthority, 0) + _, err = actor.Setup(fakeAuthority, 1) require.EqualError(t, err, "expected ed25519.PublicKey, got 'fake.PublicKey'") fakeAuthority = fake.NewAuthority(2, ed25519.NewSigner) - _, err = actor.Setup(fakeAuthority, 1) + _, err = actor.Setup(fakeAuthority, 2) require.EqualError(t, err, fake.Err("failed to send start")) rpc = fake.NewStreamRPC(fake.NewBadReceiver(), fake.Sender{}) actor.rpc = rpc - _, err = actor.Setup(fakeAuthority, 1) + _, err = actor.Setup(fakeAuthority, 2) require.EqualError(t, err, fake.Err("got an error from '%!s()' while receiving")) recv := fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), nil)) actor.rpc = fake.NewStreamRPC(recv, fake.Sender{}) - _, err = actor.Setup(fakeAuthority, 1) + _, err = actor.Setup(fakeAuthority, 2) require.EqualError(t, err, "expected to receive a Done message, but go the following: ") rpc = fake.NewStreamRPC(fake.NewReceiver( @@ -67,7 +78,7 @@ func TestPedersen_Setup(t *testing.T) { ), fake.Sender{}) actor.rpc = rpc - _, err = actor.Setup(fakeAuthority, 1) + _, err = actor.Setup(fakeAuthority, 2) require.Error(t, err) require.Regexp(t, "^the public keys does not match:", err) } @@ -92,13 +103,16 @@ func TestPedersen_Decrypt(t *testing.T) { participants: []mino.Address{fake.NewAddress(0)}, distrKey: suite.Point()}, } - _, err := actor.Decrypt(suite.Point(), suite.Point()) + K := suite.Point() + Cs := make([]kyber.Point, 1) + + _, err := actor.Decrypt(K, Cs) require.EqualError(t, err, fake.Err("failed to create stream")) rpc := fake.NewStreamRPC(fake.NewBadReceiver(), fake.NewBadSender()) actor.rpc = rpc - _, err = actor.Decrypt(suite.Point(), suite.Point()) + _, err = actor.Decrypt(K, Cs) require.EqualError(t, err, fake.Err("failed to send decrypt request")) recv := fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), nil)) @@ -106,7 +120,7 @@ func TestPedersen_Decrypt(t *testing.T) { rpc = fake.NewStreamRPC(recv, fake.Sender{}) actor.rpc = rpc - _, err = actor.Decrypt(suite.Point(), suite.Point()) + _, err = actor.Decrypt(K, Cs) require.EqualError(t, err, "got unexpected reply, expected types.DecryptReply but got: ") recv = fake.NewReceiver( @@ -116,7 +130,7 @@ func TestPedersen_Decrypt(t *testing.T) { rpc = fake.NewStreamRPC(recv, fake.Sender{}) actor.rpc = rpc - _, err = actor.Decrypt(suite.Point(), suite.Point()) + _, err = actor.Decrypt(K, Cs) require.EqualError(t, err, "failed to recover commit: share: not enough "+ "good public shares to reconstruct secret commitment") @@ -127,7 +141,7 @@ func TestPedersen_Decrypt(t *testing.T) { rpc = fake.NewStreamRPC(recv, fake.Sender{}) actor.rpc = rpc - _, err = actor.Decrypt(suite.Point(), suite.Point()) + _, err = actor.Decrypt(K, Cs) require.NoError(t, err) } @@ -140,7 +154,9 @@ func Test_Decrypt_StreamStop(t *testing.T) { }, } - _, err := a.Decrypt(nil, nil) + Cs := make([]kyber.Point, 1) + + _, err := a.Decrypt(nil, Cs) require.EqualError(t, err, fake.Err("stream stopped unexpectedly")) } @@ -191,9 +207,6 @@ func TestPedersen_Scenario(t *testing.T) { pubkeys[i] = pubkey } - fakeAuthority := NewAuthority(addrs, pubkeys) - - message := []byte("Hello world") actors := make([]dkg.Actor, n) for i := 0; i < n; i++ { actor, err := dkgs[i].Listen() @@ -203,11 +216,15 @@ func TestPedersen_Scenario(t *testing.T) { } // trying to call a decrypt/encrypt before a setup - _, _, _, err := actors[0].Encrypt(message) + message := []byte("Hello world") + + _, _, err := actors[0].Encrypt(message) require.EqualError(t, err, "you must first initialize DKG. Did you call setup() first?") _, err = actors[0].Decrypt(nil, nil) require.EqualError(t, err, "you must first initialize DKG. Did you call setup() first?") + fakeAuthority := NewAuthority(addrs, pubkeys) + _, err = actors[0].Setup(fakeAuthority, n) require.NoError(t, err) @@ -216,15 +233,84 @@ func TestPedersen_Scenario(t *testing.T) { // every node should be able to encrypt/decrypt for i := 0; i < n; i++ { - K, C, remainder, err := actors[i].Encrypt(message) + K, C, err := actors[i].Encrypt(message) require.NoError(t, err) - require.Len(t, remainder, 0) decrypted, err := actors[i].Decrypt(K, C) require.NoError(t, err) require.Equal(t, message, decrypted) } } +func TestPedersen_ReencryptScenario(t *testing.T) { + // Use with MINO_TRAFFIC=log + // traffic.LogItems = false + // traffic.LogEvent = false + // defer func() { + // traffic.SaveItems("graph.dot", true, false) + // traffic.SaveEvents("events.dot") + // }() + + oldLog := dela.Logger + defer func() { + dela.Logger = oldLog + }() + + dela.Logger = dela.Logger.Level(zerolog.WarnLevel) + + nbNodes := 7 + + minos := make([]mino.Mino, nbNodes) + dkgs := make([]dkg.DKG, nbNodes) + addrs := make([]mino.Address, nbNodes) + + manager := minoch.NewManager() + + for i := 0; i < nbNodes; i++ { + minos[i] = minoch.MustCreate(manager, fmt.Sprint("node", i)) + addrs[i] = minos[i].GetAddress() + } + + pubkeys := make([]kyber.Point, len(minos)) + + for i, mi := range minos { + d, pubkey := NewPedersen(mi.(*minoch.Minoch)) + dkgs[i] = d + pubkeys[i] = pubkey + } + + actors := make([]dkg.Actor, nbNodes) + for i := 0; i < nbNodes; i++ { + actor, err := dkgs[i].Listen() + require.NoError(t, err) + + actors[i] = actor + } + + fakeAuthority := NewAuthority(addrs, pubkeys) + _, err := actors[0].Setup(fakeAuthority, nbNodes) + require.NoError(t, err) + + // every node should be able to encrypt/reencrypt/decrypt + kp := key.NewKeyPair(suites.MustFind("Ed25519")) + + for i := 0; i < nbNodes; i++ { + message := []byte(fmt.Sprint("Hello world, I'm", i)) + U, Cs, err := actors[i].Encrypt(message) + require.NoError(t, err) + + XhatEnc, err := actors[i].Reencrypt(U, kp.Public) + require.NoError(t, err) + require.NotNil(t, XhatEnc) + + dkgPk, err := actors[i].GetPublicKey() + require.NoError(t, err) + + decrypted, err := decryptReencrypted(Cs, XhatEnc, dkgPk, kp.Private) + require.NoError(t, err) + require.Equal(t, message, decrypted) + } +} + func Test_Worker_BadProof(t *testing.T) { ct := types.Ciphertext{ K: suite.Point(), @@ -399,3 +485,45 @@ type fakeSigner struct { func (s fakeSigner) GetPublicKey() crypto.PublicKey { return ed25519.NewPublicKeyFromPoint(s.pubkey) } + +// decryptReencrypted helps to decrypt a reencrypted message. +func decryptReencrypted(Cs []kyber.Point, XhatEnc kyber.Point, dkgPk kyber.Point, Sk kyber.Scalar) (msg []byte, err error) { + + dela.Logger.Debug().Msgf("XhatEnc:%v", XhatEnc) + dela.Logger.Debug().Msgf("DKG pubK:%v", dkgPk) + dela.Logger.Debug().Msgf("Sk:%v", Sk) + + xcInv := suite.Scalar().Neg(Sk) + dela.Logger.Debug().Msgf("xcInv:%v", xcInv) + + sum := suite.Scalar().Add(Sk, xcInv) + dela.Logger.Debug().Msgf("xc + xcInv: %v", sum) + + XhatDec := suite.Point().Mul(xcInv, dkgPk) + dela.Logger.Debug().Msgf("XhatDec:%v", XhatDec) + + Xhat := suite.Point().Add(XhatEnc, XhatDec) + dela.Logger.Debug().Msgf("Xhat:%v", Xhat) + + XhatInv := suite.Point().Neg(Xhat) + dela.Logger.Debug().Msgf("XhatInv:%v", XhatInv) + + // Decrypt Cs to keyPointHat + for _, C := range Cs { + dela.Logger.Debug().Msgf("C:%v", C) + + keyPointHat := suite.Point().Add(C, XhatInv) + dela.Logger.Debug().Msgf("keyPointHat:%v", keyPointHat) + + keyPart, err := keyPointHat.Data() + dela.Logger.Debug().Msgf("keyPart:%v", keyPart) + + if err != nil { + e := xerrors.Errorf("Error while decrypting Cs: %v", err) + dela.Logger.Error().Msg(e.Error()) + return nil, e + } + msg = append(msg, keyPart...) + } + return +} diff --git a/dkg/pedersen/reencrypt.go b/dkg/pedersen/reencrypt.go new file mode 100644 index 000000000..9a1a31db5 --- /dev/null +++ b/dkg/pedersen/reencrypt.go @@ -0,0 +1,164 @@ +package pedersen + +import ( + "go.dedis.ch/dela" + "go.dedis.ch/dela/dkg/pedersen/types" + "go.dedis.ch/dela/internal/tracing" + "go.dedis.ch/dela/mino" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/share" + "go.dedis.ch/kyber/v3/suites" + "golang.org/x/net/context" + "golang.org/x/xerrors" +) + +type reencryptStatus struct { + K kyber.Point // K is the random part of the encrypted secret + pubk kyber.Point // The client's public key + + nbnodes int // How many nodes participate in the distributed operations + nbfailures int // How many failures occurred so far + threshold int // How many replies are needed to re-create the secret + + replies []types.ReencryptReply // replies received + Uis []*share.PubShare // re-encrypted shares +} + +// Reencrypt implements dkg.Actor. +func (a *Actor) Reencrypt(K kyber.Point, pubk kyber.Point) (XhatEnc kyber.Point, err error) { + if !a.startRes.Done() { + return nil, xerrors.Errorf(initDkgFirst) + } + + ctx, cancel := context.WithTimeout(context.Background(), decryptTimeout) + defer cancel() + ctx = context.WithValue(ctx, tracing.ProtocolKey, protocolNameReencrypt) + + players := mino.NewAddresses(a.startRes.getParticipants()...) + + sender, receiver, err := a.rpc.Stream(ctx, players) + if err != nil { + return nil, xerrors.Errorf(failedStreamCreation, err) + } + + iterator := players.AddressIterator() + addrs := make([]mino.Address, 0, players.Len()) + + for iterator.HasNext() { + addrs = append(addrs, iterator.GetNext()) + } + + txMsg := types.NewReencryptRequest(K, pubk) + + err = <-sender.Send(txMsg, addrs...) + if err != nil { + return nil, xerrors.Errorf("failed to send reencrypt request: %v", err) + } + + status := &reencryptStatus{ + K: K, + pubk: pubk, + } + status.nbnodes = len(addrs) + status.threshold = a.startRes.getThreshold() + + for i := 0; i < status.nbnodes; i++ { + src, rxMsg, err := receiver.Recv(ctx) + if err != nil { + return nil, xerrors.Errorf(unexpectedStreamStop, err) + } + + dela.Logger.Debug().Msgf("Received a decryption reply from %v", src) + + reply, ok := rxMsg.(types.ReencryptReply) + if !ok { + return nil, xerrors.Errorf(unexpectedReply, reply, rxMsg) + } + + err = status.processReencryptReply(&reply) + if err == nil { + dela.Logger.Debug().Msgf("Reencryption Uis: %v", status.Uis) + + XhatEnc, err := share.RecoverCommit(suites.MustFind("Ed25519"), status.Uis, status.threshold, status.nbnodes) + if err != nil { + return nil, xerrors.Errorf("Reencryption failed: %v", err) + } + + // we have successfully reencrypted + return XhatEnc, nil + } + } + + return nil, xerrors.Errorf("Reencryption failed: %v", err) +} + +func (s *reencryptStatus) processReencryptReply(reply *types.ReencryptReply) (err error) { + if reply.Ui == nil { + err = xerrors.Errorf("Received empty reply") + dela.Logger.Warn().Msg("Empty reply received") + s.nbfailures++ + if s.nbfailures > s.nbnodes-s.threshold { + err = xerrors.Errorf("couldn't get enough shares") + dela.Logger.Warn().Msg(err.Error()) + } + return err + } + + s.replies = append(s.replies, *reply) + + if len(s.replies) >= s.threshold { + s.Uis = make([]*share.PubShare, 0, s.nbnodes) + + for _, r := range s.replies { + + /* + // Verify proofs + ufi := suite.Point().Mul(r.Fi, suite.Point().Add(s.U, s.pubk)) + uiei := suite.Point().Mul(suite.Scalar().Neg(r.Ei), r.Ui.V) + uiHat := suite.Point().Add(ufi, uiei) + + gfi := suite.Point().Mul(r.Fi, nil) + gxi := s.poly.Eval(r.Ui.I).V + hiei := suite.Point().Mul(suite.Scalar().Neg(r.Ei), gxi) + hiHat := suite.Point().Add(gfi, hiei) + hash := sha256.New() + r.Ui.V.MarshalTo(hash) + uiHat.MarshalTo(hash) + hiHat.MarshalTo(hash) + e := suite.Scalar().SetBytes(hash.Sum(nil)) + if e.Equal(r.Ei) { + + */ + s.Uis = append(s.Uis, r.Ui) + /* + } + else + { + dela.Logger.Warn().Msgf("Received invalid share from node: %v", r.Ui.I) + s.nbfailures++ + } + */ + } + dela.Logger.Info().Msg("Reencryption completed") + return nil + } + + // If we are leaving by here it means that we do not have + // enough replies yet. We must eventually trigger a finish() + // somehow. It will either happen because we get another + // reply, and now we have enough, or because we get enough + // failures and know to give up, or because o.timeout triggers + // and calls finish(false) in its callback function. + + err = xerrors.Errorf("not enough replies") + dela.Logger.Debug().Msg(err.Error()) + return err +} + +// min is a helper functions +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/dkg/pedersen/resharing_test.go b/dkg/pedersen/resharing_test.go index eb7dea13c..4a759c442 100644 --- a/dkg/pedersen/resharing_test.go +++ b/dkg/pedersen/resharing_test.go @@ -79,9 +79,8 @@ func TestResharing_minoch(t *testing.T) { // Encrypt a message with the old committee public key. The new committee // should be able to decrypt it successfully message := []byte(testMessage) - K, C, remainder, err := actorsOld[0].Encrypt(message) + K, Cs, err := actorsOld[0].Encrypt(message) require.NoError(t, err, "encrypting the message was not successful") - require.Len(t, remainder, 0) // Setting up the second dkg nCommon is the number of nodes that are common // between the new and the old committee. @@ -153,7 +152,7 @@ func TestResharing_minoch(t *testing.T) { // The public key should remain the same require.NoError(t, err, "the public key should remain the same") newPubKey.Equal(oldPubKey) - decrypted, err := actorNew.Decrypt(K, C) + decrypted, err := actorNew.Decrypt(K, Cs) require.NoError(t, err, "decryption was not successful") require.Equal(t, message, decrypted, "the new committee should be able "+ "to decrypt the messages encrypted by the old committee") @@ -219,9 +218,8 @@ func TestResharing_minogrpc(t *testing.T) { // Encrypt a message with the old committee public key. the new committee // should be able to decrypt it successfully message := []byte(testMessage) - K, C, remainder, err := actorsOld[0].Encrypt(message) + K, Cs, err := actorsOld[0].Encrypt(message) require.NoError(t, err, "encrypting the message was not successful") - require.Len(t, remainder, 0) // Setting up the second dkg. nCommon is the number of nodes that are common // between the new and the old committee @@ -318,7 +316,7 @@ func TestResharing_minogrpc(t *testing.T) { // The public key should remain the same require.NoError(t, err, "the public key should remain the same") newPubKey.Equal(oldPubKey) - decrypted, err := actorNew.Decrypt(K, C) + decrypted, err := actorNew.Decrypt(K, Cs) require.NoError(t, err, "decryption was not successful") require.Equal(t, message, decrypted, "the new committee should be able "+ "to decrypt the messages encrypted by the old committee") @@ -374,9 +372,8 @@ func TestResharingTwice(t *testing.T) { // Encrypt a message with the old committee public key. the new committee // should be able to decrypt it successfully message := []byte(testMessage) - K, C, remainder, err := actors1[0].Encrypt(message) + K, Cs, err := actors1[0].Encrypt(message) require.NoError(t, err, "encrypting the message was not successful") - require.Len(t, remainder, 0) // Setting up the second dkg nCommon is the number of nodes that are common // between the new and the old committee @@ -448,7 +445,7 @@ func TestResharingTwice(t *testing.T) { // The public key should remain the same require.NoError(t, err, "the public key should remain the same") newPubKey.Equal(oldPubKey) - decrypted, err := actorNew.Decrypt(K, C) + decrypted, err := actorNew.Decrypt(K, Cs) require.NoError(t, err, "decryption was not successful") require.Equal(t, message, decrypted, "the new committee should be able "+ "to decrypt the messages encrypted by the old committee") @@ -524,7 +521,7 @@ func TestResharingTwice(t *testing.T) { // The public key should remain the same require.NoError(t, err, "the public key should remain the same") newPubKey.Equal(oldPubKey) - decrypted, err := actorNew.Decrypt(K, C) + decrypted, err := actorNew.Decrypt(K, Cs) require.NoError(t, err, "decryption was not successful") require.Equal(t, message, decrypted, "the new committee should be able "+ "to decrypt the messages encrypted by the old committee") diff --git a/dkg/pedersen/state.go b/dkg/pedersen/state.go index 424f0262b..f195c954a 100644 --- a/dkg/pedersen/state.go +++ b/dkg/pedersen/state.go @@ -50,8 +50,9 @@ type state struct { // participants is set once a sharing or resharing starts participants []mino.Address pubkeys []kyber.Point - threshold int - dkgState dkgState + //TODO add verifiability (poly *share.PubPoly) + threshold int + dkgState dkgState } func (s *state) switchState(new dkgState) error { diff --git a/dkg/pedersen/types/messages.go b/dkg/pedersen/types/messages.go index 64fddc1bc..2e0b58eca 100644 --- a/dkg/pedersen/types/messages.go +++ b/dkg/pedersen/types/messages.go @@ -5,9 +5,22 @@ import ( "go.dedis.ch/dela/serde" "go.dedis.ch/dela/serde/registry" "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/share" "golang.org/x/xerrors" ) +// couldntEncodeDecryptRequest indicates that a decrypt request couldn't be +// encoded +const couldntEncodeDecryptRequest = "couldn't encode decrypt request: %v" + +// couldntEncodeReencryptRequest indicates that a reencrypt request couldn't be +// encoded +const couldntEncodeReencryptRequest = "couldn't encode reencrypt request: %v" + +// couldntEncodeReencryptReply indicates that a reencrypt reply couldn't be +// encoded +const couldntEncodeReencryptReply = "couldn't encode reencrypt reply: %v" + // Ciphertext provides the verifiable encryption function. A description can be // found in https://arxiv.org/pdf/2205.08529.pdf. The equivalent of each // parameter in the paper is written in front of it. @@ -33,7 +46,6 @@ type ShareAndProof struct { Ei kyber.Scalar // e_i Fi kyber.Scalar // f_i Hi kyber.Point // h_i - } var msgFormats = registry.NewSimpleRegistry() @@ -433,12 +445,80 @@ func (req DecryptRequest) Serialize(ctx serde.Context) ([]byte, error) { data, err := format.Encode(ctx, req) if err != nil { - return nil, xerrors.Errorf("couldn't encode decrypt request: %v", err) + return nil, xerrors.Errorf(couldntEncodeDecryptRequest, err) + } + + return data, nil +} + +// ReencryptRequest share is sent to a node in order to reencrypt a secret +// using the original randomness K from the write request +// and the public key PubK from the reader +type ReencryptRequest struct { + K kyber.Point + PubK kyber.Point +} + +// NewReencryptRequest creates a new reencryption request. +func NewReencryptRequest(k, pubk kyber.Point) *ReencryptRequest { + return &ReencryptRequest{ + K: k, + PubK: pubk, + } +} + +// Serialize implements serde.Message. +func (req ReencryptRequest) Serialize(ctx serde.Context) ([]byte, error) { + format := msgFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, req) + if err != nil { + return nil, xerrors.Errorf(couldntEncodeReencryptRequest, err) } return data, nil } +// ReencryptReply returns the share to re-encrypt from one node +type ReencryptReply struct { + PubK kyber.Point + Ui *share.PubShare + Ei kyber.Scalar + Fi kyber.Scalar +} + +// NewReencryptReply creates a new decryption request. +func NewReencryptReply(pubk kyber.Point, ui *share.PubShare, ei, fi kyber.Scalar) ReencryptReply { + return ReencryptReply{ + PubK: pubk, + Ui: ui, + Ei: ei, + Fi: fi, + } +} + +// Serialize implements serde.Message. +func (reply ReencryptReply) Serialize(ctx serde.Context) ([]byte, error) { + format := msgFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, reply) + if err != nil { + return nil, xerrors.Errorf(couldntEncodeReencryptReply, err) + } + + return data, nil +} + +// GetI returns I. +func (reply ReencryptReply) GetI() int { + return reply.Ui.I +} + +// GetV returns V. +func (reply ReencryptReply) GetV() kyber.Point { + return reply.Ui.V +} + // VerifiableDecryptRequest is a message sent to request a verifiable // decryption. // diff --git a/dkg/pedersen/types/messages_test.go b/dkg/pedersen/types/messages_test.go index c86ea6f79..336d02c91 100644 --- a/dkg/pedersen/types/messages_test.go +++ b/dkg/pedersen/types/messages_test.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "go.dedis.ch/kyber/v3/share" "testing" "testing/quick" @@ -260,6 +261,18 @@ func TestDecryptRequest_Serialize(t *testing.T) { require.EqualError(t, err, fake.Err("couldn't encode decrypt request")) } +func TestReencryptRequest_Serialize(t *testing.T) { + var fakePoint kyber.Point + req := NewReencryptRequest(fakePoint, fakePoint) + + data, err := req.Serialize(fake.NewContext()) + require.NoError(t, err) + require.Equal(t, fake.GetFakeFormatValue(), data) + + _, err = req.Serialize(fake.NewBadContext()) + require.ErrorContains(t, err, fake.Err("couldn't encode reencrypt request")) +} + func TestVerifiableDecryptRequest_Get(t *testing.T) { req := NewVerifiableDecryptRequest([]Ciphertext{{}, {}}) @@ -300,6 +313,20 @@ func TestDecryptReply_Serialize(t *testing.T) { require.EqualError(t, err, fake.Err("couldn't encode decrypt reply")) } +func TestReencryptReply_Serialize(t *testing.T) { + var fpoint kyber.Point + var fscalar kyber.Scalar + var fshare share.PubShare + resp := NewReencryptReply(fpoint, &fshare, fscalar, fscalar) + + data, err := resp.Serialize(fake.NewContext()) + require.NoError(t, err) + require.Equal(t, fake.GetFakeFormatValue(), data) + + _, err = resp.Serialize(fake.NewBadContext()) + require.EqualError(t, err, fake.Err("couldn't encode reencrypt reply")) +} + func TestVerifiableDecryptReply_Get(t *testing.T) { req := NewVerifiableDecryptReply([]ShareAndProof{{}, {}}) diff --git a/dkg/pedersen/verifiable_test.go b/dkg/pedersen/verifiable_test.go index a802789ff..3f449f32e 100644 --- a/dkg/pedersen/verifiable_test.go +++ b/dkg/pedersen/verifiable_test.go @@ -189,13 +189,13 @@ func Test_VerifiableEncDec_minogrpc(t *testing.T) { workerNum = numWorkersSlice[i] - keys := make([][29]byte, batchSize) + msg := make([][29]byte, batchSize) var ciphertexts []types.Ciphertext for i := 0; i < batchSize; i++ { - _, err = rand.Read(keys[i][:]) + _, err = rand.Read(msg[i][:]) require.NoError(t, err) - ciphertext, remainder, err := actors[0].VerifiableEncrypt(keys[i][:], GBar) + ciphertext, remainder, err := actors[0].VerifiableEncrypt(msg[i][:], GBar) require.NoError(t, err) require.Len(t, remainder, 0) @@ -210,7 +210,7 @@ func Test_VerifiableEncDec_minogrpc(t *testing.T) { require.NoError(t, err) for i := 0; i < batchSize; i++ { - require.Equal(t, keys[i][:], decrypted[i]) + require.Equal(t, msg[i][:], decrypted[i]) } t.Logf("n=%d, batchSize=%d, workerNum=%d, decryption time=%s, "+ diff --git a/test/testcreatekey.sh b/test/testcreatekey.sh index beba279c8..9eb7e4812 100755 --- a/test/testcreatekey.sh +++ b/test/testcreatekey.sh @@ -8,3 +8,4 @@ NC='\033[0m' # No Color echo -e "${GREEN}[PK]${NC} create a private key for the manual test" crypto bls signer new --save private.key crypto bls signer read --path private.key --format BASE64 +crypto bls signer read --path private.key --format BASE64_PUBKEY