package channel import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha512" "crypto/subtle" "encoding/binary" "encoding/json" "fmt" "sort" "github.com/pkg/errors" "golang.org/x/crypto/hkdf" "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves" "source.quilibrium.com/quilibrium/monorepo/node/keys" "source.quilibrium.com/quilibrium/monorepo/node/protobufs" ) const TRIPLE_RATCHET_PROTOCOL_VERSION = 1 const TRIPLE_RATCHET_PROTOCOL = 2<<8 + TRIPLE_RATCHET_PROTOCOL_VERSION type TripleRatchetRound int const ( TRIPLE_RATCHET_ROUND_UNINITIALIZED = TripleRatchetRound(0) TRIPLE_RATCHET_ROUND_INITIALIZED = TripleRatchetRound(1) TRIPLE_RATCHET_ROUND_COMMITTED = TripleRatchetRound(2) TRIPLE_RATCHET_ROUND_REVEALED = TripleRatchetRound(3) TRIPLE_RATCHET_ROUND_RECONSTRUCTED = TripleRatchetRound(4) ) // Note: If an HSM with raw primitive access becomes available, the raw crypto // mechanisms should be refactored into calls in KeyManager and implemented // through the driver type TripleRatchetParticipant struct { peerKey curves.Scalar sendingEphemeralPrivateKey curves.Scalar receivingEphemeralKeys map[string]curves.Scalar receivingGroupKey curves.Point curve curves.Curve keyManager keys.KeyManager rootKey []byte sendingChainKey []byte currentHeaderKey []byte nextHeaderKey []byte receivingChainKey map[string][]byte currentSendingChainLength uint32 previousSendingChainLength uint32 currentReceivingChainLength map[string]uint32 previousReceivingChainLength map[string]uint32 peerIdMap map[string]int idPeerMap map[int]*PeerInfo skippedKeysMap map[string]map[string]map[uint32][]byte peerChannels map[string]*DoubleRatchetParticipant dkgRatchet *Feldman } type PeerInfo struct { PublicKey curves.Point IdentityPublicKey curves.Point SignedPrePublicKey curves.Point } // Weak-mode synchronous group modification TR – this is not the asynchronous // TR, does not ratchet group key automatically, know what your use case is // before adopting this. func NewTripleRatchetParticipant( peers []*PeerInfo, curve curves.Curve, keyManager keys.KeyManager, peerKey curves.Scalar, identityKey curves.Scalar, signedPreKey curves.Scalar, ) ( *TripleRatchetParticipant, map[string]*protobufs.P2PChannelEnvelope, error, ) { participant := &TripleRatchetParticipant{} participant.skippedKeysMap = make(map[string]map[string]map[uint32][]byte) participant.receivingEphemeralKeys = make(map[string]curves.Scalar) participant.receivingChainKey = make(map[string][]byte) participant.peerChannels = make(map[string]*DoubleRatchetParticipant) participant.keyManager = keyManager participant.currentSendingChainLength = 0 participant.previousSendingChainLength = 0 participant.currentReceivingChainLength = make(map[string]uint32) participant.previousReceivingChainLength = make(map[string]uint32) peerBasis := append([]*PeerInfo{}, peers...) peerBasis = append(peerBasis, &PeerInfo{ PublicKey: peerKey.Point().Generator().Mul(peerKey), IdentityPublicKey: identityKey.Point().Generator().Mul(identityKey), SignedPrePublicKey: signedPreKey.Point().Generator().Mul(signedPreKey), }) sort.Slice(peerBasis, func(i, j int) bool { return bytes.Compare( peerBasis[i].PublicKey.ToAffineCompressed(), peerBasis[j].PublicKey.ToAffineCompressed(), ) <= 0 }) initMessages := make(map[string]*protobufs.P2PChannelEnvelope) peerIdMap := map[string]int{} idPeerMap := map[int]*PeerInfo{} sender := false for i := 0; i < len(peerBasis); i++ { peerIdMap[string(peerBasis[i].PublicKey.ToAffineCompressed())] = i + 1 idPeerMap[i+1] = peerBasis[i] if bytes.Equal( peerBasis[i].PublicKey.ToAffineCompressed(), peerKey.Point().Generator().Mul(peerKey).ToAffineCompressed(), ) { sender = true } else { participant.skippedKeysMap[string( peerBasis[i].PublicKey.ToAffineCompressed(), )] = make(map[string]map[uint32][]byte) participant.currentReceivingChainLength[string( peerBasis[i].PublicKey.ToAffineCompressed(), )] = 0 participant.previousReceivingChainLength[string( peerBasis[i].PublicKey.ToAffineCompressed(), )] = 0 var sessionKey []byte if sender { sessionKey = SenderX3DH( identityKey, signedPreKey, peerBasis[i].IdentityPublicKey, peerBasis[i].SignedPrePublicKey, 96, ) } else { sessionKey = ReceiverX3DH( identityKey, signedPreKey, peerBasis[i].IdentityPublicKey, peerBasis[i].SignedPrePublicKey, 96, ) } var err error participant.peerChannels[string( peerBasis[i].PublicKey.ToAffineCompressed(), )], err = NewDoubleRatchetParticipant( sessionKey[:32], sessionKey[32:64], sessionKey[64:], sender, signedPreKey, peerBasis[i].SignedPrePublicKey, &curve, keyManager, ) if err != nil { return nil, nil, errors.Wrap(err, "new triple ratchet participant") } if sender { initMessages[string(peerBasis[i].PublicKey.ToAffineCompressed())], err = participant.peerChannels[string( peerBasis[i].PublicKey.ToAffineCompressed(), )].RatchetEncrypt([]byte("init")) if err != nil { return nil, nil, errors.Wrap(err, "new triple ratchet participant") } } } } feldman, err := NewFeldman( 2, len(peers)+1, peerIdMap[string( peerKey.Point().Generator().Mul(peerKey).ToAffineCompressed(), )], curve.NewScalar().Random(rand.Reader), curve, curve.Point.Generator(), ) if err != nil { return nil, nil, errors.Wrap(err, "new triple ratchet participant") } participant.peerIdMap = peerIdMap participant.idPeerMap = idPeerMap participant.dkgRatchet = feldman participant.curve = curve participant.peerKey = peerKey return participant, initMessages, nil } func (r *TripleRatchetParticipant) Initialize( initMessages map[string]*protobufs.P2PChannelEnvelope, ) (map[string]*protobufs.P2PChannelEnvelope, error) { for k, m := range initMessages { msg, err := r.peerChannels[k].RatchetDecrypt(m) if err != nil { return nil, errors.Wrap(err, "initialize") } if string(msg) != "init" { return nil, errors.Wrap(errors.New("invalid init message"), "initialize") } } if err := r.dkgRatchet.SamplePolynomial(); err != nil { return nil, errors.Wrap(err, "initialize") } result, err := r.dkgRatchet.GetPolyFrags() if err != nil { return nil, errors.Wrap(err, "initialize") } resultMap := make(map[string]*protobufs.P2PChannelEnvelope) for k, v := range result { if r.idPeerMap[k].PublicKey.Equal( r.peerKey.Point().Generator().Mul(r.peerKey), ) { continue } envelope, err := r.peerChannels[string( r.idPeerMap[k].PublicKey.ToAffineCompressed(), )].RatchetEncrypt(v) if err != nil { return nil, errors.Wrap(err, "initialize") } resultMap[string(r.idPeerMap[k].PublicKey.ToAffineCompressed())] = envelope } return resultMap, nil } func (r *TripleRatchetParticipant) ReceivePolyFrag( peerId []byte, frag *protobufs.P2PChannelEnvelope, ) (map[string]*protobufs.P2PChannelEnvelope, error) { b, err := r.peerChannels[string(peerId)].RatchetDecrypt(frag) if err != nil { return nil, errors.Wrap(err, "receive poly frag") } result, err := r.dkgRatchet.SetPolyFragForParty( r.peerIdMap[string(peerId)], b, ) if err != nil { return nil, errors.Wrap(err, "receive poly frag") } if len(result) != 0 { envelopes := make(map[string]*protobufs.P2PChannelEnvelope) for k, c := range r.peerChannels { envelope, err := c.RatchetEncrypt(result) if err != nil { return nil, errors.Wrap(err, "receive poly frag") } envelopes[k] = envelope } return envelopes, errors.Wrap(err, "receive poly frag") } return nil, nil } func (r *TripleRatchetParticipant) ReceiveCommitment( peerId []byte, zkcommit *protobufs.P2PChannelEnvelope, ) (map[string]*protobufs.P2PChannelEnvelope, error) { b, err := r.peerChannels[string(peerId)].RatchetDecrypt(zkcommit) if err != nil { return nil, errors.Wrap(err, "receive commitment") } result, err := r.dkgRatchet.ReceiveCommitments( r.peerIdMap[string(peerId)], b, ) if err != nil { return nil, errors.Wrap(err, "receive commitment") } d, err := json.Marshal(result) if err != nil { return nil, errors.Wrap(err, "receive commitment") } if result != nil { envelopes := make(map[string]*protobufs.P2PChannelEnvelope) for k, c := range r.peerChannels { envelope, err := c.RatchetEncrypt(d) if err != nil { return nil, errors.Wrap(err, "receive commitment") } envelopes[k] = envelope } return envelopes, errors.Wrap(err, "receive poly frag") } return nil, nil } func (r *TripleRatchetParticipant) Recombine( peerId []byte, reveal *protobufs.P2PChannelEnvelope, ) error { b, err := r.peerChannels[string(peerId)].RatchetDecrypt(reveal) if err != nil { return errors.Wrap(err, "recombine") } rev := &FeldmanReveal{} if err = json.Unmarshal(b, rev); err != nil { return errors.Wrap(err, "recombine") } done, err := r.dkgRatchet.Recombine( r.peerIdMap[string(peerId)], rev, ) if err != nil { return errors.Wrap(err, "recombine") } if !done { return nil } sess := sha512.Sum512_256(r.dkgRatchet.PublicKeyBytes()) hash := hkdf.New( sha512.New, r.dkgRatchet.PublicKeyBytes(), sess[:], []byte("quilibrium-triple-ratchet"), ) rkck := make([]byte, 96) if _, err := hash.Read(rkck[:]); err != nil { return errors.Wrap(err, "recombine") } r.rootKey = rkck[:32] r.currentHeaderKey = rkck[32:64] r.nextHeaderKey = rkck[64:] r.receivingGroupKey = r.dkgRatchet.PublicKey() r.sendingEphemeralPrivateKey = r.curve.Scalar.Random(rand.Reader) return nil } func (r *TripleRatchetParticipant) RatchetEncrypt( message []byte, ) (*protobufs.P2PChannelEnvelope, error) { envelope := &protobufs.P2PChannelEnvelope{ ProtocolIdentifier: TRIPLE_RATCHET_PROTOCOL, MessageHeader: &protobufs.MessageCiphertext{}, MessageBody: &protobufs.MessageCiphertext{}, } newChainKey, messageKey, aeadKey := ratchetKeys(r.sendingChainKey) r.sendingChainKey = newChainKey var err error header := r.encodeHeader() envelope.MessageHeader, err = r.encrypt( header, r.currentHeaderKey, nil, ) if err != nil { return nil, errors.Wrap(err, "could not encrypt header") } envelope.MessageBody, err = r.encrypt( message, messageKey, append(append([]byte{}, aeadKey...), envelope.MessageHeader.Ciphertext...), ) if err != nil { return nil, errors.Wrap(err, "could not encrypt message") } r.currentSendingChainLength++ return envelope, nil } func (r *TripleRatchetParticipant) RatchetDecrypt( envelope *protobufs.P2PChannelEnvelope, ) ([]byte, error) { plaintext, err := r.trySkippedMessageKeys(envelope) if err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } if plaintext != nil { return plaintext, nil } header, shouldRatchet, err := r.decryptHeader( envelope.MessageHeader, r.currentHeaderKey, ) if err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } senderKey, receivingEphemeralKey, previousReceivingChainLength, currentReceivingChainLength, err := r.decodeHeader(header) if err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } if shouldRatchet { if err := r.skipMessageKeys( senderKey, previousReceivingChainLength, ); err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } if err := r.ratchetReceiverEphemeralKeys( senderKey, receivingEphemeralKey, ); err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } } if err := r.skipMessageKeys( senderKey, currentReceivingChainLength, ); err != nil { return nil, errors.Wrap(err, "ratchet decrypt") } newChainKey, messageKey, aeadKey := ratchetKeys( r.receivingChainKey[string(senderKey.ToAffineCompressed())], ) r.receivingChainKey[string(senderKey.ToAffineCompressed())] = newChainKey r.currentReceivingChainLength[string(senderKey.ToAffineCompressed())]++ plaintext, err = r.decrypt( envelope.MessageBody, messageKey, append( append([]byte{}, aeadKey...), envelope.MessageHeader.Ciphertext..., ), ) return plaintext, errors.Wrap(err, "ratchet decrypt") } func (r *TripleRatchetParticipant) ratchetSenderEphemeralKeys() error { hash := hkdf.New( sha512.New, r.receivingGroupKey.Mul( r.sendingEphemeralPrivateKey, ).ToAffineCompressed(), r.rootKey, []byte("quilibrium-triple-ratchet"), ) rkck2 := make([]byte, 96) if _, err := hash.Read(rkck2[:]); err != nil { return errors.Wrap(err, "failed ratcheting root key") } r.rootKey = rkck2[:32] r.sendingChainKey = rkck2[32:64] r.nextHeaderKey = rkck2[64:] return nil } func (r *TripleRatchetParticipant) ratchetReceiverEphemeralKeys( peerKey curves.Point, newEphemeralKey curves.Scalar, ) error { r.previousSendingChainLength = r.currentSendingChainLength r.currentSendingChainLength = 0 r.currentReceivingChainLength[string(peerKey.ToAffineCompressed())] = 0 r.currentHeaderKey = r.nextHeaderKey r.receivingEphemeralKeys[string( peerKey.ToAffineCompressed(), )] = newEphemeralKey hash := hkdf.New( sha512.New, r.receivingGroupKey.Mul( newEphemeralKey, ).ToAffineCompressed(), r.rootKey, []byte("quilibrium-triple-ratchet"), ) rkck := make([]byte, 96) if _, err := hash.Read(rkck[:]); err != nil { return errors.Wrap(err, "failed ratcheting root key") } r.rootKey = rkck[:32] r.receivingChainKey[string(peerKey.ToAffineCompressed())] = rkck[32:64] r.nextHeaderKey = rkck[64:] r.sendingEphemeralPrivateKey = r.curve.NewScalar().Random(rand.Reader) return nil } func (r *TripleRatchetParticipant) trySkippedMessageKeys( envelope *protobufs.P2PChannelEnvelope, ) ([]byte, error) { for receivingHeaderKey, skippedKeys := range r.skippedKeysMap { header, _, err := r.decryptHeader( envelope.MessageHeader, []byte(receivingHeaderKey), ) if err == nil { peerKey, _, _, current, err := r.decodeHeader(header) if err != nil { return nil, errors.Wrap(err, "try skipped message keys") } messageKey := skippedKeys[string( peerKey.ToAffineCompressed(), )][current][:32] aeadKey := skippedKeys[string( peerKey.ToAffineCompressed(), )][current][32:] plaintext, err := r.decrypt( envelope.MessageBody, messageKey, append( append([]byte{}, aeadKey...), envelope.MessageHeader.Ciphertext[:]..., ), ) if err != nil { return nil, errors.Wrap(err, "try skipped message keys") } delete(r.skippedKeysMap[string( peerKey.ToAffineCompressed(), )][receivingHeaderKey], current) if len(r.skippedKeysMap[string( peerKey.ToAffineCompressed(), )][receivingHeaderKey]) == 0 { delete(r.skippedKeysMap[string( peerKey.ToAffineCompressed(), )], receivingHeaderKey) } return plaintext, nil } } return nil, nil } func (r *TripleRatchetParticipant) skipMessageKeys( senderKey curves.Point, until uint32, ) error { if r.currentReceivingChainLength[string( senderKey.ToAffineCompressed(), )]+100 < until { return errors.Wrap(errors.New("skip limit exceeded"), "skip message keys") } if r.receivingChainKey != nil { for r.currentReceivingChainLength[string( senderKey.ToAffineCompressed(), )] < until { newChainKey, messageKey, aeadKey := ratchetKeys( r.receivingChainKey[string( senderKey.ToAffineCompressed(), )], ) skippedKeys := r.skippedKeysMap[string( senderKey.ToAffineCompressed(), )][string(r.currentHeaderKey)] if skippedKeys == nil { r.skippedKeysMap[string( senderKey.ToAffineCompressed(), )][string(r.currentHeaderKey)] = make(map[uint32][]byte) } skippedKeys[r.currentReceivingChainLength[string( senderKey.ToAffineCompressed(), )]] = append( append([]byte{}, messageKey...), aeadKey..., ) r.receivingChainKey[string( senderKey.ToAffineCompressed(), )] = newChainKey r.currentReceivingChainLength[string( senderKey.ToAffineCompressed(), )]++ } } return nil } func (r *TripleRatchetParticipant) encodeHeader() []byte { header := []byte{} header = append( header, r.peerKey.Point().Generator().Mul(r.peerKey).ToAffineCompressed()..., ) header = append( header, r.sendingEphemeralPrivateKey.Bytes()..., ) header = binary.BigEndian.AppendUint32(header, r.previousSendingChainLength) header = binary.BigEndian.AppendUint32(header, r.currentSendingChainLength) return header } func (r *TripleRatchetParticipant) decryptHeader( ciphertext *protobufs.MessageCiphertext, receivingHeaderKey []byte, ) ([]byte, bool, error) { header, err := r.decrypt( ciphertext, receivingHeaderKey, nil, ) if err != nil && subtle.ConstantTimeCompare( r.currentHeaderKey, receivingHeaderKey, ) == 1 { if header, err = r.decrypt( ciphertext, r.nextHeaderKey, nil, ); err != nil { return nil, false, errors.Wrap(err, "could not decrypt header") } fmt.Println("should ratchet") return header, true, nil } return header, false, errors.Wrap(err, "could not decrypt header") } func (r *TripleRatchetParticipant) decodeHeader( header []byte, ) (curves.Point, curves.Scalar, uint32, uint32, error) { if len(header) < 9 { return nil, nil, 0, 0, errors.Wrap( errors.New("malformed header"), "decode header", ) } currentReceivingChainLength := binary.BigEndian.Uint32(header[len(header)-4:]) previousReceivingChainLength := binary.BigEndian.Uint32( header[len(header)-8 : len(header)-4], ) sender := header[:len(r.curve.Point.ToAffineCompressed())] senderKey, err := r.curve.Point.FromAffineCompressed(sender) if err != nil { return nil, nil, 0, 0, errors.Wrap(err, "decode header") } receivingEphemeralKeyBytes := header[len( r.curve.Point.ToAffineCompressed(), ) : len(header)-8] receivingEphemeralKey, err := r.curve.Scalar.Clone().SetBytes( receivingEphemeralKeyBytes, ) return senderKey, receivingEphemeralKey, previousReceivingChainLength, currentReceivingChainLength, errors.Wrap(err, "decode header") } func (r *TripleRatchetParticipant) encrypt( plaintext []byte, key []byte, associatedData []byte, ) (*protobufs.MessageCiphertext, error) { iv := [12]byte{} rand.Read(iv[:]) aesCipher, err := aes.NewCipher(key) if err != nil { return nil, errors.Wrap(err, "encrypt") } gcm, err := cipher.NewGCM(aesCipher) if err != nil { return nil, errors.Wrap(err, "encrypt") } ciphertext := &protobufs.MessageCiphertext{} if associatedData == nil { associatedData = make([]byte, 32) if _, err := rand.Read(associatedData); err != nil { return nil, errors.Wrap(err, "encrypt") } ciphertext.AssociatedData = associatedData } ciphertext.Ciphertext = gcm.Seal(nil, iv[:], plaintext, associatedData) ciphertext.InitializationVector = iv[:] return ciphertext, nil } func (r *TripleRatchetParticipant) decrypt( ciphertext *protobufs.MessageCiphertext, key []byte, associatedData []byte, ) ([]byte, error) { if associatedData == nil { associatedData = ciphertext.AssociatedData } aesCipher, err := aes.NewCipher(key) if err != nil { return nil, errors.Wrap(err, "decrypt") } gcm, err := cipher.NewGCM(aesCipher) if err != nil { return nil, errors.Wrap(err, "decrypt") } plaintext, err := gcm.Open( nil, ciphertext.InitializationVector, ciphertext.Ciphertext, associatedData, ) return plaintext, errors.Wrap(err, "decrypt") }