package crypto import ( "bytes" "crypto" "crypto/rand" "encoding/binary" "math/big" "github.com/cloudflare/circl/sign/ed448" "github.com/iden3/go-iden3-crypto/poseidon" "github.com/pkg/errors" "go.uber.org/zap" "golang.org/x/crypto/sha3" "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/vdf" "source.quilibrium.com/quilibrium/monorepo/node/keys" "source.quilibrium.com/quilibrium/monorepo/node/protobufs" "source.quilibrium.com/quilibrium/monorepo/node/tries" ) type WesolowskiFrameProver struct { logger *zap.Logger } func NewWesolowskiFrameProver(logger *zap.Logger) *WesolowskiFrameProver { return &WesolowskiFrameProver{ logger, } } func (w *WesolowskiFrameProver) ProveMasterClockFrame( previousFrame *protobufs.ClockFrame, timestamp int64, difficulty uint32, ) (*protobufs.ClockFrame, error) { input := []byte{} input = append(input, previousFrame.Filter...) input = binary.BigEndian.AppendUint64(input, previousFrame.FrameNumber+1) input = binary.BigEndian.AppendUint32(input, difficulty) input = append(input, previousFrame.Output[:]...) b := sha3.Sum256(input) v := vdf.New(difficulty, b) v.Execute() o := v.GetOutput() previousSelectorBytes := [516]byte{} copy(previousSelectorBytes[:], previousFrame.Output[:516]) parent, err := poseidon.HashBytes(previousSelectorBytes[:]) if err != nil { return nil, errors.Wrap(err, "prove clock frame") } frame := &protobufs.ClockFrame{ Filter: previousFrame.Filter, FrameNumber: previousFrame.FrameNumber + 1, Timestamp: timestamp, Difficulty: difficulty, ParentSelector: parent.FillBytes(make([]byte, 32)), Input: previousFrame.Output, AggregateProofs: []*protobufs.InclusionAggregateProof{}, Output: o[:], } return frame, nil } func (w *WesolowskiFrameProver) VerifyMasterClockFrame( frame *protobufs.ClockFrame, ) error { input := []byte{} input = append(input, frame.Filter...) input = binary.BigEndian.AppendUint64(input, frame.FrameNumber) input = binary.BigEndian.AppendUint32(input, frame.Difficulty) input = append(input, frame.Input...) if len(frame.Input) < 516 { return errors.Wrap( errors.New("invalid input"), "verify clock frame", ) } if len(frame.AggregateProofs) > 0 { return errors.Wrap( errors.New("invalid input"), "verify clock frame", ) } if frame.PublicKeySignature != nil { return errors.Wrap( errors.New("invalid input"), "verify clock frame", ) } if len(frame.Input) != 516 { return errors.Wrap( errors.New("invalid input"), "verify clock frame", ) } if len(frame.Output) != 516 { return errors.Wrap( errors.New("invalid output"), "verify clock frame", ) } b := sha3.Sum256(input) v := vdf.New(frame.Difficulty, b) proof := [516]byte{} copy(proof[:], frame.Output) if !v.Verify(proof) { w.logger.Error("invalid proof", zap.Binary("filter", frame.Filter), zap.Uint64("frame_number", frame.FrameNumber), zap.Uint32("difficulty", frame.Difficulty), zap.Binary("frame_input", frame.Input), zap.Binary("frame_output", frame.Output), ) return errors.Wrap( errors.New("invalid proof"), "verify clock frame", ) } previousSelectorBytes := [516]byte{} copy(previousSelectorBytes[:], frame.Input[:516]) parent, err := poseidon.HashBytes(previousSelectorBytes[:]) if err != nil { return errors.Wrap(err, "verify clock frame") } selector := new(big.Int).SetBytes(frame.ParentSelector) if parent.Cmp(selector) != 0 { return errors.Wrap( errors.New("selector did not match input"), "verify clock frame", ) } return nil } func (w *WesolowskiFrameProver) CreateMasterGenesisFrame( filter []byte, seed []byte, difficulty uint32, ) ( *protobufs.ClockFrame, error, ) { b := sha3.Sum256(seed) v := vdf.New(difficulty, b) v.Execute() o := v.GetOutput() inputMessage := o[:] w.logger.Debug("proving genesis frame") input := []byte{} input = append(input, filter...) input = binary.BigEndian.AppendUint64(input, 0) input = binary.BigEndian.AppendUint32(input, difficulty) if bytes.Equal(seed, []byte{0x00}) { value := [516]byte{} input = append(input, value[:]...) } else { input = append(input, seed...) } b = sha3.Sum256(input) v = vdf.New(difficulty, b) v.Execute() o = v.GetOutput() frame := &protobufs.ClockFrame{ Filter: filter, FrameNumber: 0, Timestamp: 0, Difficulty: difficulty, Input: inputMessage, Output: o[:], ParentSelector: []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, AggregateProofs: []*protobufs.InclusionAggregateProof{}, PublicKeySignature: nil, } return frame, nil } func (w *WesolowskiFrameProver) ProveDataClockFrame( previousFrame *protobufs.ClockFrame, commitments [][]byte, aggregateProofs []*protobufs.InclusionAggregateProof, provingKey crypto.Signer, timestamp int64, difficulty uint32, ) (*protobufs.ClockFrame, error) { var pubkey []byte pubkeyType := keys.KeyTypeEd448 ed448PublicKey, ok := provingKey.Public().(ed448.PublicKey) if ok { pubkey = []byte(ed448PublicKey) } else { return nil, errors.Wrap( errors.New("no valid signature provided"), "prove clock frame", ) } h, err := poseidon.HashBytes(pubkey) if err != nil { return nil, errors.Wrap( errors.New("could not hash proving key"), "prove clock frame", ) } address := h.Bytes() input := []byte{} input = append(input, previousFrame.Filter...) input = binary.BigEndian.AppendUint64(input, previousFrame.FrameNumber+1) input = binary.BigEndian.AppendUint64(input, uint64(timestamp)) input = binary.BigEndian.AppendUint32(input, difficulty) input = append(input, address...) input = append(input, previousFrame.Output[:]...) commitmentInput := []byte{} for _, commitment := range commitments { commitmentInput = append(commitmentInput, commitment...) } input = append(input, commitmentInput...) b := sha3.Sum256(input) v := vdf.New(difficulty, b) v.Execute() o := v.GetOutput() // TODO: make this configurable for signing algorithms that allow // user-supplied hash functions signature, err := provingKey.Sign( rand.Reader, append(append([]byte{}, b[:]...), o[:]...), crypto.Hash(0), ) if err != nil { return nil, errors.Wrap( err, "prove", ) } previousSelectorBytes := [516]byte{} copy(previousSelectorBytes[:], previousFrame.Output[:516]) parent, err := poseidon.HashBytes(previousSelectorBytes[:]) if err != nil { return nil, errors.Wrap(err, "prove clock frame") } frame := &protobufs.ClockFrame{ Filter: previousFrame.Filter, FrameNumber: previousFrame.FrameNumber + 1, Timestamp: timestamp, Difficulty: difficulty, ParentSelector: parent.FillBytes(make([]byte, 32)), Input: append( append([]byte{}, previousFrame.Output...), commitmentInput..., ), AggregateProofs: aggregateProofs, Output: o[:], } switch pubkeyType { case keys.KeyTypeEd448: frame.PublicKeySignature = &protobufs.ClockFrame_PublicKeySignatureEd448{ PublicKeySignatureEd448: &protobufs.Ed448Signature{ Signature: signature, PublicKey: &protobufs.Ed448PublicKey{ KeyValue: pubkey, }, }, } default: return nil, errors.Wrap( errors.New("unsupported proving key"), "prove clock frame", ) } return frame, nil } func (w *WesolowskiFrameProver) CreateDataGenesisFrame( filter []byte, origin []byte, difficulty uint32, inclusionProof *InclusionAggregateProof, proverKeys [][]byte, preDusk bool, ) (*protobufs.ClockFrame, *tries.RollingFrecencyCritbitTrie, error) { frameProverTrie := &tries.RollingFrecencyCritbitTrie{} for _, s := range proverKeys { addr, err := poseidon.HashBytes(s) if err != nil { panic(err) } addrBytes := addr.Bytes() addrBytes = append(make([]byte, 32-len(addrBytes)), addrBytes...) frameProverTrie.Add(addrBytes, 0) } w.logger.Info("proving genesis frame") input := []byte{} input = append(input, filter...) input = binary.BigEndian.AppendUint64(input, 0) input = binary.BigEndian.AppendUint64(input, 0) input = binary.BigEndian.AppendUint32(input, difficulty) input = append(input, origin...) if !preDusk { input = append(input, inclusionProof.AggregateCommitment...) } b := sha3.Sum256(input) v := vdf.New(difficulty, b) v.Execute() o := v.GetOutput() commitments := []*protobufs.InclusionCommitment{} for i, commit := range inclusionProof.InclusionCommitments { commitments = append(commitments, &protobufs.InclusionCommitment{ Filter: filter, FrameNumber: 0, Position: uint32(i), TypeUrl: commit.TypeUrl, Data: commit.Data, Commitment: commit.Commitment, }) } frame := &protobufs.ClockFrame{ Filter: filter, FrameNumber: 0, Timestamp: 0, Difficulty: difficulty, Input: append( append([]byte{}, origin...), inclusionProof.AggregateCommitment..., ), Output: o[:], ParentSelector: []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, AggregateProofs: []*protobufs.InclusionAggregateProof{ { Filter: filter, FrameNumber: 0, InclusionCommitments: commitments, Proof: inclusionProof.Proof, }, }, PublicKeySignature: nil, } return frame, frameProverTrie, nil } func (w *WesolowskiFrameProver) VerifyDataClockFrame( frame *protobufs.ClockFrame, ) error { var pubkey []byte var signature []byte pubkeyType := keys.KeyTypeEd448 ed448PublicKey := frame.GetPublicKeySignatureEd448() if ed448PublicKey != nil { pubkey = ed448PublicKey.PublicKey.KeyValue signature = ed448PublicKey.Signature } else { return errors.Wrap( errors.New("no valid signature provided"), "verify clock frame", ) } h, err := poseidon.HashBytes(pubkey) if err != nil { return errors.Wrap( errors.New("could not hash proving key"), "verify clock frame", ) } address := h.Bytes() input := []byte{} input = append(input, frame.Filter...) input = binary.BigEndian.AppendUint64(input, frame.FrameNumber) input = binary.BigEndian.AppendUint64(input, uint64(frame.Timestamp)) input = binary.BigEndian.AppendUint32(input, frame.Difficulty) input = append(input, address...) input = append(input, frame.Input...) if len(frame.Input) < 516 { return errors.Wrap( errors.New("invalid input"), "verify clock frame", ) } if len(frame.Output) != 516 { return errors.Wrap( errors.New("invalid output"), "verify clock frame", ) } b := sha3.Sum256(input) v := vdf.New(frame.Difficulty, b) proof := [516]byte{} copy(proof[:], frame.Output) // TODO: make this configurable for signing algorithms that allow // user-supplied hash functions switch pubkeyType { case keys.KeyTypeEd448: if len(pubkey) != 57 || len(signature) != 114 || !ed448.VerifyAny( pubkey, append(append([]byte{}, b[:]...), frame.Output...), signature, crypto.Hash(0), ) { return errors.Wrap( errors.New("invalid signature for issuer"), "verify clock frame", ) } } if !v.Verify(proof) { return errors.Wrap( errors.New("invalid proof"), "verify clock frame", ) } previousSelectorBytes := [516]byte{} copy(previousSelectorBytes[:], frame.Input[:516]) parent, err := poseidon.HashBytes(previousSelectorBytes[:]) if err != nil { return errors.Wrap(err, "verify clock frame") } selector := new(big.Int).SetBytes(frame.ParentSelector) if parent.Cmp(selector) != 0 { return errors.Wrap( errors.New("selector did not match input"), "verify clock frame", ) } return nil }